NIO:
使用InputStream的read()方法从流中读取数据时,若数据源中没有数据,它会阻塞该线程,而OutputStream流,也是如此。因为它们都是阻塞式的输入与输出。不仅仅如此,传统的输入流,输出流都是通过字节的移动来处理的(即使不直接去处理字节流,但镀层的实现还是依赖于字节处理),也就是说,面向流的输入/输出,系统一次只能处理一个字节,因此面向流的输入/输出效率不高,为了提高效率,Java提供了一系列改进的输入/输出处理的新功能,这些新功能统称为New IO,简写为NIO。
NIO使用了不同的方式来处理输入/输出,它采用内存映射文件的方式来处理输入/输出,NIO将文件或者文件的一段区域映射到了内存中,这样就可以想访问内存一样来访问文件了,通过这种方式来进行输入/输出比传统的输入/输出要快得多。
在NIO中,有两个核心对象,一个是Channel (通道),另外一个则是Buffer(缓存)。
Channel是对传统的输入/输出系统的模拟,在NIO系统中,所有的数据都需要通过Channel来进行传输(包括输出与输入),与传统的InputStream和OutputStream相比,它提供了一个map()方法,通过该map方法可以直接将“一块数据”映射到内存中。
但程序是不能直接访问Channel中的数据,包括读取,写入都不行,Channel只与Buffer进行交互。也就是说,如果要从Channel中取得数据,就必须先用Buffer从Channel中取出一些数据,然后让程序从Buffer中取出这些数据;同理,若要对通道写入东西,也是先写入Buffer中,然后将Buffer写入Channel中。
Channel是一个抽象基类,按功能可以分成以下几大类:
1. 用于文件的Channel: FileChannel
2. 用于TCP网络的Channel: ServerSocketChannel,SocketChannel
3. 用于UDP网络的Channel:DatagramChannel
4. 用于支持线程间通信的Channel:Pipe.SinkChannel,PipeSourceChannel
以上的Channel都不能通过构造方法来直接创建,而是调用InputStream/OutputStream的getChannel()方法来返回对应的Channel,根据不同的节点流返回的Channel也不一样,比如说FileInputStream/FileOutputStream返回的就是FileChannel。
Channel中最常用的三类方法,map(),read(),write(),其中map()方法用于将Channel对应的部分或者全部数据映射成了ByteBuffer;而read()方法与write()方法都有一系列重载形式,这些方法用于从Bufer中读取数据或向Buffer中写入数据。
关于map()方法中,有三个参数,第一个是FileChannel.MapMode参数,该参数执行映射时的模式,有只读,读写等模式,该FileChannel.MapMode下有对应常量,调用对应的常量作为参数便可;第二个,第三个参数用于控制将Channel的那些数据映射成ByteBuffer。
下面通过NIO来实现拷贝文件:
public class NIO { public void copyFile(String path ,String copyPath){ File f1 = new File(path); File f2 = new File(copyPath); try{ if(!f1.exists()){ System.out.println("拷贝文件不存在"); } if(!f2.exists()){ f2.createNewFile(); } if(f1.isFile() && f2.isFile()){ FileChannel inChannel = new FileInputStream(f1).getChannel(); FileChannel outChannel = new FileOutputStream(f2).getChannel(); //一次性取完,用于小文件的拷贝 // MappedByteBuffer buffer = inChannel.map(FileChannel.MapMode.READ_ONLY, 0, f1.length()); // System.out.println(buffer); // outChannel.write(buffer); // System.out.println(buffer); //分段取数据,用于大文件的拷贝 ByteBuffer byteBuff = ByteBuffer.allocate(1024); while(inChannel.read(byteBuff) != -1){ //锁定空白区 byteBuff.flip(); outChannel.write(byteBuff); //释放空白区 byteBuff.clear(); } }catch(Exception e){} } public static void main(String[] args) { // TODO Auto-generated method stub NIO nio = new NIO(); String charPath = "D:\\data\\file\\book.txt"; String charCopyPath = "D:\\data\\file\\book_1.txt"; nio.copyFile(charPath, charCopyPath); } }
在上述代码中,有注释的适用于小文件的拷贝,另外一种是适用于大文件的拷贝
NIO的随机访问文件
在IO流中,有种可以任意访问文件位置并且可以在任意位置写入的RandomAccess流;在NIO中,也可以通过Buffer和Channel来实现。
public class NIO { //下面代码实现了将文件内容进行复制,追加在该文件后面 public void RandomFileChannelTest(String path){ File file = new File(path); if(!file.exists()){ System.out.println("要拷贝文件的路径不存在"); }else{ try{ RandomAccessFile raf = new RandomAccessFile(file,"rw"); FileChannel fileChannel = raf.getChannel(); ByteBuffer buffer =fileChannel.map(FileChannel.MapMode.READ_ONLY, 0, file.length()); fileChannel.position(file.length()); //FileChannel.position(),改变记录指针值 fileChannel.write(buffer); }catch(Exception e){} } } public static void main(String[] args) { // TODO Auto-generated method stub NIO nio = new NIO(); String path = "D:\\data\\file\\book_1.txt"; nio.RandomFileChannelTest(path); } }
字符集与Charset:
计算机底层是没有文本文件和图片之分的,它只是忠实地记录每个文件的二进制序列而已。对于文本文档而言,当需要保存时,程序/系统需要将文件的每个字符翻译成二进制序列;当需要读取时,程序/系统便会需要将二进制序列转换为一个个的字符。
但由于各个国家的语言不同,各个国家都有自己的一套字符集,也就是说,当我们将中国的字符集翻译成了二进制序列,在用这个二进制序列翻译成了其他国家的字符集,那么便会出来乱码。
在Java中,提供Charset类来处理字节序列和字符序列之间的转换关系。该类中,包含用于创建解码器和码编器的方法,还提供了获取Charset所支持的字符集方法。
public class NIO { public void charseTest(){ //获取Java支持的全部字符集 SortedMap<String ,Charset> map = Charset.availableCharsets(); for(String str : map.keySet()){ System.out.println(str +"------>"+map.get(str)); } } public static void main(String[] args) { nio.charseTest(); } }
对于中国而言,我们常用以下的字符集:
GBK:简体中文字符集
BIG5: 繁体中文字符集
ISO-8859-1: ISO拉丁字母表NO.1,也叫做ISO-LATIN-1
UTF-8 :8位UCS转换格式
UTF-16BE:16位UCS转换格式
UTF-16:16位UCS转换格式
UTF-16LE:16位UCS转换格式
Charset不能通过构造方法进行实例,而是通过Charset.forName(String 字符集)的方法来获取实例。获取实例后,便可以调用newDecoder()/newEncoder()方法获取CharsetDecoder/CharsetEncoder对象,分别代表着Charset的解码器与编码器。
调用CharsetDecoder的decode()方法可以将ByteBuffer(字节序列)转换为CharBuffer(字符序列),调用CharsetEncode的encode()可以将CharBuffer(字符序列)转化为ByteBuffer(字节序列):
public class NIO { public void charseTransform(){ CharBuffer chars = CharBuffer.allocate(8); chars.put("你"); chars.put("是"); chars.put("谁"); chars.flip(); Charset charset = Charset.forName("utf-8"); CharsetDecoder decoder = charset.newDecoder(); CharsetEncoder encoder = charset.newEncoder(); try{ ByteBuffer bytes = encoder.encode(chars); System.out.println("\""+decoder.decode(bytes)+"\"的编码如下:"); for(int i=0;i<bytes.capacity();i++){ System.out.print(bytes.get(i)+"\t"); } }catch(Exception e){} } public static void main(String[] args) { // TODO Auto-generated method stub NIO nio = new NIO(); nio.charseTransform(); } }
运行效果如下:
"你是谁"的编码如下:
-28 -67 -96 -26 -104 -81 -24 -80 -127
文件锁:
在操作系统中,如果多个运行程序需要并发修改同一个文件时,程序之间需要某种机制来进行通信,而使用文件锁可以有效地阻止多个进程并发修改同一个文件。
在Java中,提供了FileLock来支持文件锁定的功能,在FileChannel中提供的lock()/tryLock()方法可以获得文件锁的FileLock对象,从而锁定文件。
lock和tryLock的区别在于:当lock()试图锁定某个文件时,如果无法得到文件锁,程序将一直阻塞;而tryLock()是尝试锁定文件,它将直接返回而不是阻塞,如果获得了文件锁,则返回该文件锁,若没有获得,则返回null。
lock()/lock(long position ,long size,boolean shared)方法:对文件进行加锁/对文件从position 开始,长度为size的内容加锁,该方法时阻塞式的
tryLock/tryLock(long position ,long size,boolean shared)方法:对文件进行加锁/对文件从position 开始,长度为size的内容加锁,该方法时非阻塞式的
上述方法有参数的情况下,当参数shared为true时,表明该锁时共享锁,可以运行多个进程来读取该文件,但组织其他进程来获得对该文件的排他锁;当参数shared为false时,表明该锁是一个排他锁。它将锁住对该文件的读写。
处理完文件后,记得使用FileLock()的release()方法来释放文件锁。下面程序示范了使用FileLock锁定文件的示例:
public class NIO { public void fileLock(){ try{ FileChannel fileChannel = new FileOutputStream("out.txt").getChannel(); FileLock fileLock = fileChannel.tryLock(); System.out.println("对当前目录下out.txt文件添加文件锁"); Thread.sleep(3600000); //持续一分钟 fileLock.release(); System.out.println("对当前目录下out.txt文件释放文件锁"); }catch(Exception e){} } public static void main(String[] args) { nio.fileLock(); } }
运行上述代码后,我们对当前目录下得out.txt进行编辑,保存时,提示另一个程序正在使用此文件,进程无法访问,说明上述程序已经对该文件上锁了,其他进程在上述程序执行完毕之前是无法对该文件后进行操作得。
关于文件锁有几点需要指出:
1. 在某些平台上,文件锁仅仅是建议性,并不是强制性的。着意味着即使一个程序不能获得文件锁,它也可以对该文件进行读写。
2. 在某些平台上,不能同步地锁定一个文件并把它映射到内存中
3. 文件锁是由Java虚拟机所持有的,如果两个Java程序使用同一个Java虚拟机运行,则它们不能对同一个文件进行加锁
4. 在某些平台上关闭FileChannel时,会释放Java虚拟机在该文件上的所有锁,因此应该避免对同一个被锁定的文件打开多个FileChannel。
原文:https://www.cnblogs.com/hjlin/p/11436770.html