http://my.oschina.net/u/2263278/blog/508770中介绍的BufferedReader时提到它的一个特征----当BufferedReader读取输入流中的数据时,如果没有读到有效数据,程序将在此处阻塞该线程的执行(使用InputStream的read()方法从流中读取数据时,如果数据源中没有数据,它也会阻塞该线程),也就是前面介绍的输入流、输出流都是阻塞式的输入、输出。传统的输入流、输出流都是通过字节的移动来处理的,即使我们不直接去处理字节流,但底层的实现还是依赖于字节处理,面向流的输入/输出系统一次只能处理一个字节,因此面向流的输入/输出系统通常效率不高。从JDK1.4开始,Java提供了一些新IO,这些类都放在java.nio包及其子包下。
一、Java新IO概述
Java的NIO采用了内存映射文件的方式来处理输入/输出,新IO将文件或文件的一段区域映射到内存中,这样就可以像访问内存一样来访问文件了(这种方式模拟了操作系统上的虚拟内存的概念),通过这种方式进行输入/输出比传统的输入/输出要快的多。
Channel和Buffer是NIO中两个核心对象,Channel是对传统的输入/输出系统的模拟,在NIO系统中所有的数据都需要通过通道Channel传输。Channle和传统的InputStream、OutputStream最大的区别在于提供了一个map()方法,通过该map()方法可以直接将"一块数据"映射到内存中。如果说传统的输入/输出系统式面向流的处理,则新IO则是面向快的处理。Buffer可以被理解成一个容器,它的本质是一个数组,发送到Channel中的所有对象必须首先爱你放到Buffer中,而从Channel中读取的数据也必须先放在Buffer中。
二、Buffer的使用
1、从内部结构来看,Buffer就像一个数组,他可以保存多个类型相同的数据。BUffer是一个抽象类,其子类:ByteBuffer、CharBuffer、IntBuffer、LongBuffer等。这些Buffer类没有提供构造器,而是通过 allocat(int capacity)方法返回一个对象。ByteBuffer还有一个子类:MappedByteBuffer,它用于表示Channel将磁盘文件的部分或全部内容映射到内存后得到的结果,通常MappredByteBuffer是Channel的map()方法返回的。
2、Buffer中的3个概念:
①容量 capacity:缓冲区的容量表示该Buffer的最大数据容量,即最多可以存储多少数据。缓冲区的容量不能为负值。
②界限limit:第一个不应该被读出或写入的缓冲区位置索引。位于limit后面的数据既不可读也不可被写。
③位置position:记录指针。当使用BUffer从Channel中读取数据时,其position为0,如果从Channel中读取了2个数据到该Buffer中,则position为2,指向Buffer中第三个位置。(第一个位置的索引为0)
④mark:可选的标记
0<=mark<=position<=limit<=capacity
3、Bufferde 的主要作用就是装入数据,然后输出数据,开始的时候Buffer的position为0,Limit为capacity,程序可通过put()方法向Buffer中放入一些数据,每放入一些数据,Buffer的position相应的向后移动一些位置。当Buffer装入数据结束后,调用Buffer的flip()方法,该方法将Limit设置为position所在位置,并将position设为0,这就使得Buffer的读写指针又移到了开始位置。Buffer调用filp()方法之后,BUffer为输出数据做好了准备。当Buffer输出数据结束后,BUffer调用clear()方法,clear()方法不是清空BUffer数据,它仅仅将Position设为0,将limit设为capactiy,这样为再次向Buffer中装入数据做好准备。
Buffer中的两个中重要方法:flip()和clear(),flip()方法为从BUffer中取出数据做好准备,clear()方法为再次向Buffer中装入数据做好准备。还有put()和get()方法,用于向Buffer中放入数据和从Buffer中取出数据。
当使用put()和get()莱访问Buffer中的数据时,分为相对和绝对两种:
①、相对:从Buffer的当前position出开始读取或写入数据,然后将position的值按处理元素的个数增加。
②、绝对:直接根据索引向Buffer中读取或写入数据,使用绝对方式访问Buffer里的数据时,并不会影响位置的值。
public static void main(String[] args) { CharBuffer buff=CharBuffer.allocate(8); System.out.println(buff.capacity()+":"+buff.limit()+":"+buff.position()); buff.put(‘a‘); buff.put(‘a‘); buff.put(‘a‘); System.out.println(buff.position()); buff.flip(); System.out.println(buff.limit()); }
通过allocate()方法创建的Buffer对象是普通的Buffer,ByteBuffer还提供了一个allocateDirect()方法来创建直接Buffer。直接Buffer的创建成本比普通的Buffer创建成本高,但是直接buffer的读取效率很高。
三、Channel的使用
Channel类与传统的流对象的区别:
①、Channel可以直接将指定的 文件的部分或全部映射成buffer。
②、程序不能直接访问Channel中的数据,包括读取、写入都不行,Channel只能与Buffer进行交互,也就说,如果从Channel中取得数据,必须先用Buffer从Channel中取出一些数据,然后让程序从Buffer中取出这些数据。将数据写入Channle一样,需要先写入Buffer中。Java为Channle接口提供了DatagramChannel、FileChannel、Pipe.SinkChannel、Pepe.SourceChannel、SelectableChannel、ServerSoketChannel等。
所有的Channel都不应该通过构造器来创建,而是通过传统的节点InputStream,OutputStream的getChannel()方法来返回对应的Channle。Channle中最常用的3类方法:map()、read()、write(),其中map()方法用于将Channel对应的部分或全部数据映射成ByteBuffer,而read()和write()方法用于从Buffer中直接读取数据或写入数据。
public static void main(String[] args) { try{ File f=new File("wang.txt"); FileInputStream fileIn=new FileInputStream(new File("wang.txt")); FileChannel inChannel=fileIn.getChannel(); FileChannel outChannel=new FileOutputStream(new File("test.txt")).getChannel(); MappedByteBuffer buffer=inChannel.map(FileChannel.MapMode.READ_ONLY, 0, f.length()); outChannel.write(buffer); buffer.clear(); }catch(Exception e){ e.printStackTrace(); } }
在RandomAccessFile类中也包含了一个getChannel()方法
public static void main(String[] args) { try{ File file=new File("test.txt"); FileChannel channel=new RandomAccessFile(file, "rw").getChannel(); MappedByteBuffer buff=channel.map(FileChannel.MapMode.READ_WRITE, 0, file.length()); channel.position(file.length()); channel.write(buff); }catch(Exception e){ e.printStackTrace(); } }
四、字符集合Channel
编码Encode:把明文的字符序列转换成计算机理解的二进制序列称为编码。
解码Decode:把二进制序列转换为普通人能看懂的明文字符串称为解码。
Java默认使用Unicode字符集,JDK1.4提供了Charset来处理字节序列与字符序列之间的转换关系,该类包含了用于创建解码器和编码器的方法,还提供了Charset所支持字符集的方法,Charset类是不可变的。
CharsetDecoder和CharsetEncoder对象代表Charset的编码器和解码器,调用CharsetDecoder的decode()方法将Bytebuffer字节序列转换成Charbuff字符序列,调用CharsetEncoder的encode()方法就可以将CharBuffer或String字符序列转换成ByteBuffer字节序列。
public static void main(String[] args) throws CharacterCodingException { Charset cn=Charset.forName("gbk"); CharsetEncoder encoder=cn.newEncoder(); CharsetDecoder decoder=cn.newDecoder(); CharBuffer cbuff=CharBuffer.allocate(8); cbuff.put(‘王‘); cbuff.put(‘宁‘); cbuff.flip(); ByteBuffer bbuff=encoder.encode(cbuff); for(int i=0;i<bbuff.capacity();i++){ System.out.println(bbuff.get(i)); } System.out.println(decoder.decode(bbuff)); }
五、文件锁
文件锁在操作系统中是很平常的事情,如果多个运行程序需要并发修改同一个文件时,程序之间需要某种机制来进行通信,使用文件锁可以有效地阻止多个进程并发修改同一个文件,所以现在的大部分操作系统都提供了文件锁的功能。
Java提供了FileLock类支持文件锁功能,在FileChannel中提供的lock()/tryLock()方法可以获得文件锁FileLock对象,从而锁定文件。lock()方法和trylock()方法的区别是:当lock()试图锁定某个文件时,如果无法得到文件锁,程序将一直阻塞,而tryLock()方法时尝试锁定文件,它将直接返回而不是阻塞,如果获得了文件锁,该方法则返回该文件锁,否则将返回Null。
文件锁虽然可以用于控制并发访问,但对于高并发访问的情形,还是推荐使用数据库来保存程序信息,而不是使用文件。
六、NIO.2
Java7对原有的NIO进行了重大改变,称为NIO.2。提供了全面的文件IO和文件系统的访问支持。提供了基于异步Channel的IO。
1、Path、Paths和FIles核心API
Java提供的File类功能比较有限,他不能利用特定文件系统的特性,File所提供的方法的性能也不高,而且,其大多数方法在出错时仅仅返回失败,并不提供异常信息。NIO.2为了弥补这种不足,引入了Path接口,还提供了Files\Paths工具类。
2、FileVisitor遍历文件和目录
3、WatchService监控文件变化