前面三篇文章中分别总结了标准Java IO系统中的File、RandomAccessFile、I/O流系统,对于I/O系统从其继承体系入手,力求对类数量繁多的的I/O系统有一个清晰的认识,而后结合一些I/O的常规用法来加深对标准I/O系统的掌握,感兴趣的同窗能够看一下:html
<<Java I/O系统学习系列一:File和RandomAccessFile>>数组
<<Java I/O系统学习系列二:输入和输出>>网络
<<Java I/O系统学习系列三:I/O流的典型使用方式>>app
从本文开始我会开始总结NIO部分,Java NIO(注意,这里的NIO其实叫New IO)是用来替换标准Java IO以及Java 网络API的,其提供了一系列不一样与标准IO API的方式来处理IO,从JDK1.4开始引入,其目的在于提升速度。dom
之因此可以提升速度是由于其所使用的结构更接近于操做系统执行I/O的方式:通道和缓冲器。咱们能够把它想象成一个煤矿,通道是一个包含煤层(数据)的矿藏,而缓冲器则是派送到矿藏的卡车。卡车满载煤炭而归,咱们再从卡车上得到煤炭。也就是说,咱们并无直接和通道交互,而是和缓冲器交互,并把缓冲器派送到通道。通道要么从缓冲器得到数据,要么向缓冲器发送数据。学习
在标准IO的API中,使用字节流和字符流。而在Java NIO中是使用Channel(通道)和Buffer(缓冲区),数据从channel中读取到buffer中,或从buffer写入到channel中。Java NIO类库中的核心组件为:spa
本文中咱们会着重总结Buffer相关的知识点(后面的文章中会继续介绍Channel即Selector),本文主要会围绕以下几个方面展开:操作系统
Buffer简介code
Buffer的内部结构 xml
Java NIO中的Buffer通常和Channel配对使用。能够从Channel中读取数据到Buffer,或者写数据到Channel中。一个Buffer其实就是表明一个内存块,你能够往里面写数据或者从中读取数据。这个内存块被包装成一个Buffer对象,而且提供了一系列方法使得操做内存块更便捷。
经过Buffer来读写数据一般包括以下4步:
当往Buffer中写数据时,Buffer可以记录写了多少数据。当要从Buffer中读取数据时,就须要经过调用flip()方法将Buffer从写模式切换到读模式。一旦读完全部数据,须要清空Buffer,让它再次处于写状态。能够经过调用clear()或compact()方法来完成这一步:
以下是一个简单的使用例子,经过FileChannel和ByteBuffer读取pom.xml文件,并逐字节输出:
public class BufferDemo { public static void main(String[] args) { try { RandomAccessFile raf = new RandomAccessFile("pom.xml","r"); FileChannel channel = raf.getChannel(); ByteBuffer buffer = ByteBuffer.allocate(48); int byteReaded = channel.read(buffer); while(byteReaded != -1) { buffer.flip(); while(buffer.hasRemaining()) { System.out.print((char)buffer.get()); } buffer.clear(); byteReaded = channel.read(buffer); } raf.close(); }catch (Exception e) { e.printStackTrace(); } } }
上面说到Buffer封装了一块内存块,并提供了一系列的方法使得能够方便地操纵内存中的数据。至于如何操纵?Buffer提供了4个索引。要理解Buffer的工做原理,就须要从这些索引提及:
其中position和limit的含义取决于Buffer是处于什么模式(读或者写模式),capacity的含义则和模式无关,而mark则只是一个标记,能够经过mark()方法进行设置。下图描述了读写模式下三种属性分别表明的含义,详细解释见下文:
Buffer表明一个内存块,因此其是有肯定大小的,也叫“容量”。能够往buffer中写入各类数据如byte、long、chars等,当Buffer被写满了则须要将其清空(能够经过读取数据或者清空数据)以后才能继续写入数据。
当往Buffer中写数据时,写入的地方就是所谓的position,其初始值为0,最大值为capacity-1。当往Buffer中写入一个byte或者long的数据时,position会前移以指向下一个即将被插入的位置。
当从Buffer中读取数据时,读取数据的地方就是所谓的position。当执行flip将Buffer从写模式切换到读模式时,position会被重置为0。随着不断从Buffer读取数据,position也会不断后移指向下一个将被读取的数据。
在写模式下,Buffer的limit是指可以往Buffer中写入多少数据,其值等于Buffer的capacity。
在读模式下,Buffer的limit是指可以从Buffer读取多少数据出来。所以当从写模式切换到读模式下时,limit就被设置为写模式下的position的值(这很好理解,写了多少才能读到多少)。
mark其实就是一个标记,能够经过mark()方法设置,设置值为当前的position。
下面是用于设置和复位索引以及查询它们值的方法:
capacity() 返回缓冲区容量
clear() 清空缓冲区,将position设置为0,limit设置为容量。咱们能够调用此方法覆写缓冲区
flip() 将limit设置为position,position设置为0。此方法用于准备从缓冲区读取已经写入的数据
limit() 返回limit值
limit(int lim) 设置limit值
mark() 将mark设置为position
position() 返回position值
position(int pos) 设置position值
remaining() 返回(limit - position)
hasRemaining() 如有介于position和limit之间的元素,则返回true
除了如上和索引相关的方法以外,Buffer还提供了一些其余的方法用于写入、读取等操做。
要得到一个Buffer对象就能够经过Buffer类的allocate()方法来实现,以下分别是分配一个48字节的ByteBuffer和1024字符的CharBuffer:
ByteBuffer buf = ByteBuffer.allocate(48);
CharBuffer buf = CharBuffer.allocate(1024);
有两种方式往Buffer中写入数据:
int bytesRead = inChannel.read(buf); // read into buffer buf.put(127);
put()方法有多个重载版本,好比从指定位置写入数据,或写入字节数组等。
flip()方法将Buffer从写模式切换到读模式。调用flip()方法会将position设为0,limit设为position以前的值。
也有两种方法从Buffer读取数据:
int bytesWritten = inChannel.write(buf); // read from buffer into channel byte aByte = buf.get();
rewind()方法将position设置为0,能够从头开始读数据。
当从Buffer读取数据结束以后要将其切换回写模式,能够调用clear()、compact()这两个方法,二者之间的区别以下:
调用clear(),会将position设为0,limit设为capacity,也就是说Buffer被清空了,可是里面的数据仍然存在,只是这时没有标记能够告诉你哪些数据是已读,哪些是未读。
若是读取到一半须要写入数据,可是未读的数据稍后还须要读取,这时可使用compact(),其会将全部未读取的数据复制到Buffer的前面,将position设置到这些数据后面,limit设置为capacity,因此此时是从未读的数据后面开始写入新的数据。
调用mark()方法能够标志一个指定的位置(即设置mark值),以后调用reset()方法时position又会回到以前标记的位置。
ByteBuffer是一个比较基础的缓冲器,继承自Buffer,是能够存储未加工字节的缓冲器,而且也是惟一直接与通道交互的缓冲器。能够经过ByteBuffer的allocate()方法来分配一个固定大小的ByteBuffer,而且其还有一个方法选择集,用于以原始的字节形式或基本类型输出和读取数据。可是,没办法输出或读取对象,即便是字符串对象也不行。这种处理虽然很低级,但却正好,由于这是大多数操做系统中更有效的映射方式。
ByteBuffer也分为直接和非直接缓冲器,经过allocate()建立的就是非直接缓冲器,而经过allocateDirect()方法就能够建立出一个缓冲器直接缓冲器,这是一个与操做系统有更高耦合性的缓冲器,也就意味着它可以带来更高的速度,可是分配的开支也会更大。
尽管ByteBuffer只能保存字节类型的数据,可是它具备能够从其所容纳的字节中产生出各类不一样基本类型值的方法。下面的例子展现怎样使用这些方法来插入和抽取各类数值:
public class GetData { private static final int BSIZE = 1024; public static void main(String[] args){ ByteBuffer bb = ByteBuffer.allocate(BSIZE); int i = 0; while(i++ < bb.limit()) if(bb.get() != 0) System.out.println("nonzero"); System.out.println("i = " + i); bb.rewind(); // store and read a char array: bb.asCharBuffer().put("Howdy!"); char c; while((c = bb.getChar()) != 0) System.out.print(c + " "); System.out.println(); bb.rewind(); // store and read a short: bb.asShortBuffer().put((short)471142); System.out.println(bb.getShort()); bb.rewind(); // sotre and read an int: bb.asIntBuffer().put(99471142); System.out.println(bb.getInt()); bb.rewind(); // store and read a long: bb.asLongBuffer().put(99471142); System.out.println(bb.getLong()); bb.rewind(); // store and read a float: bb.asFloatBuffer().put(99471142); System.out.println(bb.getFloat()); bb.rewind(); // store and read a double: bb.asDoubleBuffer().put(99471142); System.out.println(bb.getDouble()); bb.rewind(); } }
Java NIO中包含了以下几种Buffer:
这些Buffer类型表明着不一样的数据类型,使得能够经过Buffer直接操做如char、short等类型的数据而不是字节数据。其中MappedByteBuffer略有不一样,后面会专门总结。
经过ByteBuffer咱们只能往Buffer直接写入或者读取字节数组,可是经过对应类型的Buffer好比CharBuffer、DoubleBuffer等咱们能够直接往Buffer写入char、double等类型的数据。或者利用ByteBuffer的asCharBuffer()、asShorBuffer()等方法获取其视图,而后再使用其put()方法便可直接写入基本数据类型,就像上面的例子。
这就是视图缓冲器(view buffer)可让咱们经过某个特定的基本数据类型的视窗查看其底层的ByteBuffer。ByteBuffer依然是实际存储数据的地方,“支持”着前面的视图,所以对视图的任何修改都会映射成为对ByteBuffer中数据的修改。这使得咱们能够很方便地向ByteBuffer插入数据。视图还容许咱们从ByteBuffer一次一个地(与ByteBuffer所支持的方式相同)或者成批地(经过放入数组中)读取基本类型值。在下面的例子中,经过IntBuffer操纵ByteBuffer中的int型数据:
public class IntBufferDemo { private static final int BSIZE = 1024; public static void main(String[] args){ ByteBuffer bb = ByteBuffer.allocate(BSIZE); IntBuffer ib = bb.asIntBuffer(); // store an array of int: ib.put(new int[]{11,42,47,99,143,811,1016}); // absolute location read and write: System.out.println(ib.get(3)); ib.put(3,1811); // setting a new limit before rewinding the buffer. ib.flip(); while(ib.hasRemaining()){ int i = ib.get(); System.out.println(i); } } }
上例中先用重载后的put()方法存储一个整数数组。接着get()和put()方法调用直接访问底层ByteBuffer中的某个整数位置。这些经过直接与ByteBuffer对话访问绝对位置的方式也一样适用于基本类型。
本文简单总结了Java NIO(Java New IO),其目的在于提升速度。Java NIO类库中主要包括Buffer、Channel、Selector,本文主要总结了Buffer相关的知识点:
基础知识的总结也许是比较枯燥的,可是若是你已经看到这里说明你颇有耐心,若是以为对你有帮助的话,不妨点个赞关注一下吧^_^