首页 > 代码库 > NIO之Buffer
NIO之Buffer
Buffer
Buffer
Mark<=Position <=Limt<=Capacity
状态变量
- position:
在从通道读取时,将所读取的数据放到底层的数组中。 position 变量跟踪已经写了多少数据。它指定了下一个字节将放到数组的哪一个元素中。因此,如果从通道中读三个字节到缓冲区中,那么缓冲区的position 将会设置为3,指向数组中第四个元素。
- mark:
一个备忘标记位置调用,mark()函数设置mark=positon,调用reset()设置position=mark。
- limit
limit 变量表明还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。
- capacity
缓冲区的 capacity 表明可以储存在缓冲区中的最大数据容量。实际上,它指定了底层数组的大小或者指定了准许使用的底层数组的容量。
图形演示
初始:一个新创建的缓冲区。假设这个缓冲区的 总容量 为8个节。 Buffer 的状态如下所示:
- 读入缓冲区:写入5个字节。Buffer 的状态如下所示:
- flip():它将设置limit为它将设置position为0;它将设置position为0;可以读缓冲区的内容了:
- 写入通道
第一次写入时,从缓冲区中取四个字节并将它们写入输出通道。这使得 position 增加到 4,而 limit 不变:
再次写入,只剩下一个字节可写, limit在调用 flip() 时被设置为 5,并且 position 不能超过 limit。所以最后一次写入操作从缓冲区取出一个字节并将它写入输出通道。这使得 position 增加到 5,并保持 limit 不变。
- clear
它将limit设置为与capacity 相同;它设置 position为0;可以再次往缓冲区写数据了。
缓冲区操作
- 分配
-
/** * 分配缓冲区 */ @Test public void allocateBuffer(){ ByteBuffer bf=ByteBuffer.allocate(1024); System.out.println("position:"+bf.position()); System.out.println("limit:"+bf.limit()); System.out.println("capacity:"+bf.capacity()); }
运行结果:
- 包装
-
/** * 将原有数组包装成一个缓冲区。 */ public void wrapBuffer(){ int [] ints={1,2,3,4,5}; IntBuffer ib=IntBuffer.wrap(ints); ib.put(1,8); System.out.println(Arrays.toString(ints)); }
运行结果:
数组和缓冲区共用一分数据。
- 分片
-
@Test public void sliceBuffer() { IntBuffer ib = IntBuffer.allocate(10); for(int i=0;i<10;i++){ ib.put(i); } ib.position(3); ib.limit(6); IntBuffer sliceBuf=ib.slice(); System.out.println("before slice:"+Arrays.toString(sliceBuf.array())); for(int j=0;j<sliceBuf.capacity();j++){ sliceBuf.put(j,sliceBuf.get()*3); } System.out.println("after slice:"+Arrays.toString(sliceBuf.array())); System.out.println("old buffer"+Arrays.toString(sliceBuf.array())); }
运行结果:
原缓冲区和新的缓冲区分片共享同一个底层数据数组,并且对缓冲区分片的新缓冲区修改只影响子缓冲区。
- 压缩
-
@Test public void compactBuffer() { CharBuffer cb = CharBuffer.allocate(15); for (int i = 65; i < 75; i++) { cb.put((char) i); } cb.flip(); System.out.println("original buffer:"+Arrays.toString(cb.array())); System.out.println("original positon:"+cb.position()); System.out.println("original limit:"+cb.limit()); for(int j=0;j<6;j++){ cb.get(); } System.out.println("after get position:"+cb.position()); cb.compact(); System.out.println("after compact position:"+cb.position()); System.out.println("after compact limit:"+cb.limit()); System.out.println("after compact buffer:"+Arrays.toString(cb.array())); cb.flip(); System.out.println("after flip position:"+cb.position()); System.out.println("after flip limit:"+cb.limit()); }
运行结果:
未读的元素移动到下表0开始,position为最后一个未读元素的下一个下标,limit在这个过程中没变化,如果想读取这部分元素,执行一次翻转。
- 比较
两个缓冲区相等的充分必要条件:
- 两个缓冲区类型必须相同
- 两个缓冲区剩余元素数量必须相同,两个缓冲区容量可以不同
3. 两个缓冲区通过get()取得的元素序列必须相同
@Test public void compareBuffer(){ //cb1和cb2的capacity不相等 CharBuffer cb1=CharBuffer.allocate(10); CharBuffer cb2=CharBuffer.allocate(8); for (int i=65;i<75;i++){ cb1.put((char)i); } for (int j=69;j<75;j++){ cb2.put((char)j); } cb1.flip(); cb2.flip(); System.out.println("original cb1:"+Arrays.toString(cb1.array())); System.out.println("original cb2:"+Arrays.toString(cb2.array())); //cb1读取六个元素 for(int m=0;m<5;m++){ cb1.get(); } //cb2读取一个元素 cb2.get(); System.out.println(cb1.equals(cb2)); }
运行结果:
只读缓冲区
-
public void readOnlyBuffer() { IntBuffer ib = IntBuffer.allocate(10); for (int i = 0; i < 10; i++) { ib.put(i); } System.out.println("original buffer:"+Arrays.toString(ib.array())); IntBuffer readOnlyBuffer=ib.asReadOnlyBuffer(); readOnlyBuffer.flip(); //以下语句会接收异常:ReadOnlyBufferException。 //System.out.println("readonly buffer:"+Arrays.toString(readOnlyBuffer.array())); System.out.print("original readonly buffer:"); for (int j=0;j<readOnlyBuffer.capacity();j++){ System.out.print(readOnlyBuffer.get()); } }
只读缓冲区会和原缓冲区公用一分数据。
直接缓冲区
给定一个直接字节缓冲区,Java 虚拟机将尽最大努力直接对它执行本机 I/O 操作。也就是说,它会在每一次调用底层操作系统的本机 I/O 操作之前(或之后),尝试避免将缓冲区的内容拷贝到一个中间缓冲区中(或者从一个中间缓冲区中拷贝数据)
/** * 直接缓冲区 * @throws Exception */ @Test public void directBuffer() throws Exception { FileInputStream fi = new FileInputStream(this.sourcePath); FileOutputStream fo = new FileOutputStream(this.destPath); ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024); FileChannel fic = fi.getChannel(); FileChannel foc = fo.getChannel(); while (fic.read(directBuffer) != -1) { directBuffer.flip(); foc.write(directBuffer); directBuffer.clear(); } fi.close(); fo.close(); }
内存映像
内存映射文件 I/O 是通过使文件中的数据出现为内存数组的内容来完成的。一般来说,只有文件中实际读取或者写入的部分才会送入(或者 映射 )到内存中。
只能通过FileChannel来创建。
代码实例:
public void mappedByteBuffer() throws Exception{ FileInputStream fi = new FileInputStream(this.sourcePath); //MappedByteBuffer是ByteBuffer的子类。 MappedByteBuffer mappedByteBuffer=fi.getChannel().map(FileChannel.MapMode.READ_ONLY,0l,1024l); }
字节缓冲区
- 字节顺序
- 大端字节
2. 小端字节
- 取决于硬件设计,JVM默认是大端字节,IP协议使用大端的网络字节顺序。
- JVM对字节顺序的支持:ByteOrder
获取本地字节顺序:ByteOrder.nativeOrder();
/** * 字节顺序 */ @Test public void byteOrder(){ System.out.println("My mac pro byte order:"+ByteOrder.nativeOrder()); }
运行结果:
视图缓冲区
/** * 视图buffer */ @Test public void viewBuffer() { this.byteOrder(); ByteBuffer bb = ByteBuffer.allocate(16).order(ByteOrder.BIG_ENDIAN); CharBuffer cb=bb.asCharBuffer(); bb.put((byte)0); bb.put((byte)‘r‘); bb.put((byte)0); bb.put((byte)‘e‘); bb.put((byte)0); bb.put((byte)‘q‘); bb.put((byte)0); bb.put((byte)‘u‘); bb.put((byte)0); bb.put((byte)‘e‘); bb.put((byte)0); bb.put((byte)‘l‘); bb.put((byte)0); bb.put((byte)‘q‘); bb.put((byte)0); bb.put((byte)‘i‘); System.out.println("original bb position:"+bb.position()+",limit:"+bb.limit()+",bb is:"+Arrays.toString(bb.array())); System.out.println("view buffer cb position:"+cb.position()+",limit:"+cb.limit()+",bb is:"+cb.toString()); }
运行结果: