本文共 4315 字,大约阅读时间需要 14 分钟。
NIO buffers和 channels交互使用。正如你所知,数据总是从channel 读入buffer,从buffer写入channel。
buffer本质上是一个你可以先写入,然后读出数据的内存块,这个内存块用NIO buffer对象包装了起来以方便使用。
使用Buffer类读写数据典型的套路写法有四步:
【译注: 读写buffer,是以buffer为主体的。写模式:实际是buffer.read() ,即将数据从channel读入buffer; 读模式,实际是buffer.write() ,即将数据从buffer读出写入channel】
写数据到buffer的时候,buffer会记录程序写入了多少数据。一旦开始读数据了,需要使用flip()从写模式切换到读模式。读模式允许你把所有已写的数据读出来。
一旦读完所有数据,需要clear(清理)buffer,以待再次写,NIO提供了两种方式:
clear()或者compact()。Clear()方法会“清理”掉buffer中所有内容;compact()仅仅会“清理“掉已读的内容,而所有未读内容会被移动到buffer内容器(数组)开始的一端,以后数据会被追加写到该buffer。下面是一个简单的buffer使用案例:
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");FileChannel inChannel = aFile.getChannel();//create buffer with capacity of 48 bytesByteBuffer buf = ByteBuffer.allocate(48);int bytesRead = inChannel.read(buf); //读出数据写入到buffer (写模式)while (bytesRead != -1) { buf.flip(); //buffer正待被读出 (切换为 读模式 ) while(buf.hasRemaining()){ System.out.print((char) buf.get()); // 逐个字节读出 } buf.clear(); //buffer正待被写入 bytesRead = inChannel.read(buf);}aFile.close();
Buffer 这个内存块有三个需要重点熟悉的属性:
position 、 limit 的含义取决于Buffer是读、还是写模式。capacity在这两种模式下倒是含义相同。
下图解释了这三个属性在读、写模式下的模型:buffer作为内存块,有着固定的内存大小,也称“capacity”。程序写capacity这个容量的字节数据、长整型、字符数据到buffer里。buffer满了以后,如果想继续写入数据,需要首先清空它(将数据读出来,或者清理掉)
程序是在一个特定的位置(position)把数据写入buffer的。初始的position=0。写入一个byte、long数据到buffer后,position指针会向后移动一位。position指针最大=capacity-1.
从buffer读数据时也是从一个特定position开始的。当程序flip()一个buffer,将模式从写改为读时,position被重置为0。当你从buffer读取数据时,也会移动position指针到下一个要读的位置。
在Buffer的写模式下,一个Buffer的limit指程序最多能往buffer中写入多少数据,此时limit= buffer的capacity。
在buffer的读模式下,limit 指能从buffer读取的最多数据。因此,flip一个buffer变成读模式后,limit 被置为 写模式下position的位置。换言之,程序写了多少数据最多就可以读多少数据(limit 被置为已写的字节数,这也被称为“Mark”)
Buffer有多种类型可供使用,MappedByteBuffer有点特殊,容后详解。
实例化一个Buffer对象首先要使用allocate()方法为其分配内存。下面是一个容量为48个字节的ByteBuffer对象内存是如何分配的.
ByteBuffer buf = ByteBuffer.allocate(48);
这里有一个容量为1024个字符的CharBuffer内存是如何分配的:
CharBuffer buf = CharBuffer.allocate(1024);
有两中方式向Buffer中写入数据:
下面是如何通过Channel向Buffer写入数据的:
int bytesRead = inChannel.read(buf);
这里是展示如何手动向Buffer写入数据的:
buf.put(127);
put()有很多实现,写数据到Buffer的方式也各式各样。比如说,写入特定的position间数据,或者将一组字节写入到Buffer。可参考JavaDOC获取更多细节。
Flip()方法将一个Buffer 从写模式转为读模式。 调用flip()把position置为0,并把limit置为原position所在位置。
换言之:此时,position 标志了起读的位置(0),limit 则说明可以最多写多少字节、字符等数据到Buffer里。从Buffer中读取数据有两种方式:
下面是如何将数据从Buffer读到Channel:
int bytesWritten = inChannel.write(buf);
这个是如何使用get()获取Buffer中数据:
byte aByte = buf.get();
get()方法也有多种实现。比如,从特定position间读,或者从buffer中读取字节数组,等等。
可查看JavaDOC获取详情。(英文原指:rewind-倒带,退卷)
Buffer .rewind()方法将position指针置为0,这样可以**重读**buffer中所有数据。limit 不变,仍然可以标志最多可以从Buffer中获取多少字节、字符等。从Buffer中读完数据后,必须重设Buffer的状态才能写入数据。可以通过clear() compact()做到。
调用clear()方法,position指针置为0,limit = capacity。换言之:Buffer被“clear”了,但实际上Buffer中数据并没有被真正“clear”掉,真正被“clear”掉的是那些表明从哪里可以清除数据的标志(markers).
调用clear()时如果buffer里还有未读数据,这些数据将会被“遗忘”掉,这意味着将没有标志(markers)说明哪些数据已读、哪些未读。
如果Buffer里还有未读数据,而且你后续想读,只是现在需要写入一些数据的话,调用compact(),而不是clear()。
compact()会把未读数据copy到Buffer内存块(数组)。然后,把position置为未读内容的最后元素所在处。limit仍被置为capacity,就像clear()一样。这样,Buffer内就可继续写数据,但你不会覆盖掉未读的数据了。
你可以通过Buffer.mark() “mark”一下 buffer中某个position。然后通过Buffer.reset() 把position重设到 mark的位置。示例:
buffer.mark();//call buffer.get() a couple of times, e.g. during parsing.buffer.reset(); //set position back to mark.
可以通过equals()、compareTo()方法比较两个buffer。
equals():
两个buffer在满足下列条件下是equal的:所有剩余的字节、字符都是equal的
如你所见,equals()只比较Buffer的部分元素,而不是逐个比较buffer中的单个元素。事实上,本方法只比较Buffer中剩余元素。
CompareTo()
CompareTo() 比较两个buffer的 剩余元素(bytes 、chars等),比如在排序规则中会调用本方法。一般很少用到。