- Java NIO Buffer
当我们需要与NIO Channel进行交互时,我们就需要使用到NIO Buffer,即数据从Buffer读取到Channel中,并且从Channel中写入到Buffer中。
实际上,一个Buffer其实就是一块内存区域,我们可以在这个内存区域中进行数据的读写。NIO Buffer其实是这样的内存块的一个封装,并提供了一些操作方法让我们能够方便地进行数据的读写。 - Buffer类型有:
- ByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
- NIO Buffer的基本使用
使用NIO Buffer的步骤如下:
-
将数据写入Buffer中
-
调用Buffer.flip()方法,将NIO Buffer转换为读模式
-
从Buffer中读取数据
-
调用Buffer.clear()或Buffer.compact()方法,将Buffer转换为写模式。
当我们将数据写入到Buffer中时,Buffer会记录我们已经写了多少的数据,当我们需要从Buffer中读取数据时,必须调用Buffer.flip()将Buffer切换为度模式。
一旦读取了所有的Buffer数据,那么我们必须清理Buffer,让其从新可写,清理Buffer可以调用Buffer.clear()或Buffer.compact()public class Test { public static void main(String[] args) { IntBuffer intBuffer = IntBuffer.allocate(2); intBuffer.put(124342); intBuffer.put(3); intBuffer.flip(); System.out.println(intBuffer.get()); System.out.println(intBuffer.get()); } }
分配了两个单位大小的IntBuffer,因此他可以写入两个int值
我们使用put方法将int值写入,然后使用flip方法将buffer转换为读模式,然后连续使用get方法从buffer中获取这两个int值。
没当调用一次get方法读取数据时,buffer的读指针就会向前移动一个单位长度(在这里是一个int长度)。
- Buffer属性
一个Buffer由三个属性:
- capacity
- position
- limit
其中position和limit的含义和Buffer处于读模式或写模式有关,而capacity的含义与Buffer所处的模式无关
-
capacity
一个内存块会有一个固定的大小,即容量(capacity),我们最多写入capacity个单位的数据到Buffer中,例如一个DoubleBuffer,其Capacity是100,那么我们做多可以写入100个double数据。 -
position
当从一个Buffer中写入数据时,我们是从Buffer的一个确定的位置(position)开始写入。在最初的状态时,position的值是0。每当我们写入了一个单位的数据后,position就会递增一。
当我们从Buffer中读取数据时,我们也是从某个特定的位置开始读取。当我们调用了flip()方法将Buffer从写模式转换到读模式时,position的值会自动被设置为0,每当我们读取一个单位的数据,position的值递增1。
position表示了读写操作的位置指针。 -
limit
limit-position表示此时还可以写入/读取多少单位的数据 -
分配Buffer
为了获取一个Buffer对象,我们首先需要分配内存空间。每个类型的Buffer都有一个allocate()方法,我们可以通过这个方法分配BufferByteBuffer buf = ByteBuffer.allocate(48);
这里我们分配了48*sizeof(Byte)字节的内存空间
- 关于Direct Buffer和Non-Direct Buffer的区别
-
Direct Buffer:
- 所分配的内存不在JVM堆上,不受GC的管理(但是Direct Buffer的java对象是由GC管理的,因此当发生GC,对象被回收时,Direct Buffer也会被释放)
- 因为Direct Buffer不在JVM堆上分配,因此Direct Buffer对应用程序的内存占用的影响就不那么明显(实际上还是镇用了那么多内存,但是JVM不好统计到非JVM管理的内存)
- 申请和释放Direct Buffer的开销比较大,因此正确的使用Direct Buffer的方式是在初始化时申请一个Buffer,然后不断复用此Buffer,在程序结束后才释放此Buffer
- 使用Direct Buffer时,当进行一些底层的系统IO操作时,效率会比较高,因为此时JVM不需要拷贝buffer中的内存到中间临时缓冲区。
-
Non-Direct buffer
- 直接在JVM堆上进行内存的分配,本质上市byte[]数组的封装
- 因为Non-Direct Buffer在JVM堆中,因此当进行操作系统底层IO操作时,会将次Buffer的内存复制到中间临时缓冲区,因此Non-Direct buffer的效率就较低
-
写数据到Buffer
int bytesRead = inChannel.read(buf); //read into buffer. buf.put(127);
-
从Buffer中读取数据
//read from buffer into channel. int bytesWritten = inChannel.write(buf); byte aByte = buf.get();
-
重置position
Buffer.rewind()方法可以重置position的值为0,因此我们可以重新读取/写入Buffer了,如果是读模式,则重置的是读模式的position,如果是写模式,则重置的是写模式的position -
mark()和reset()
我么可以通过调用Buffer.mark()将当前的position的值保存起来,随后可以通过调用Buffer.reset()方法将position的值恢复回来public class Test { public static void main(String[] args) { IntBuffer intBuffer = IntBuffer.allocate(2); intBuffer.put(1); intBuffer.put(2); intBuffer.flip(); System.out.println(intBuffer.get()); intBuffer.mark(); System.out.println(intBuffer.get()); System.out.println(intBuffer.position()); intBuffer.reset(); System.out.println(intBuffer.position()); System.out.println(intBuffer.get()); } }
这里我们写入两个int值,然后首先读取了一个值,此时读position的值为1
接着我们调用mark()方法将当前的position保存起来(在读模式,因此保存的是position),然后再次读取,此时position就是2了,接着使用reset()恢复原来的读position,因此读position就为1,可以再次读取数据。
- flip,rewind和clear的区别
-
flip
源码public final Buffer flip() { limit = position; position = 0; mark = -1; return this; }
Buffer的读/写模式公用一个position和limit变量
当从写模式变为读模式时,原来的写position就变成了读模式的limit
-
rewind
源码public final Buffer rewind() { position = 0; mark = -1; return this; }
rewind,即倒带,这个方法仅仅是将position置为0
-
clear
源码public final Buffer clear() { position = 0; limit = capacity; mark = -1; return this; }
根据源码我们知道,clear将position设置为0,将limit设置为capacity
clear方法使用场景
1 在一个已经写满数据的Buffer中,调用clear,可以从头读取buffer的数据。
2 为了将一个Buffer填充慢数据,可以调用clear,然后一直写入,直到达到limit
网友评论