缓冲区 Buffer
缓冲区就是数组,用于存储不同类型的数据,最常用的是ByteBuffer,因为所有数据都是字节类型的。
常见的缓冲区类型:
- ByteBuffer
- CharBuffer
- ShortBuffer
- IntBuffer
- LongBuffer
- FloatBuffer
- DoubleBuffer
缓冲区基本用法
- allocate() 分区缓冲区
- get() 获取数据
- put() 放置数据
- flip() 切换到读数据模式
- mark() 记录position的位置
- reset() 恢复position上一个的位置
- clear() 清空缓冲区
Buffer的基本用法
使用Buffer读写数据一般遵循以下四个步骤:
- 写入数据到
Buffer
- 调用
flip()
方法 - 从
Buffer
中读取数据 - 调用
clear()
方法或者compact()
方法
当向buffer
写入数据时,buffer
会记录下写了多少数据。一旦要读取数据,需要通过flip()
方法将Buffer
从写模式切换到读模式。在读模式下,可以读取之前写入到buffer
的所有数据。
一旦读完了所有的数据,就需要清空缓冲区,让它可以再次被写入。有两种方式能清空缓冲区:调用clear()
或compact()
方法。clear()
方法会清空整个缓冲区。compact()
方法只会清除已经读过的数据。任何未读的数据都被移到缓冲区的起始处,新写入的数据将放到缓冲区未读数据的后面。
下面是一个使用Buffer的例子:
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
//创建容量为48byte的buffer
ByteBuffer 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()); // 每次读取1byte,也就是一个字节
}
buf.clear(); //清空buffer,准备再次写入
bytesRead = inChannel.read(buf);
}
aFile.close();
Buffer的capacity,position和limit
缓冲区本质上是一块可以写入数据,然后可以从中读取数据的内存。这块内存被包装成NIO Buffer对象,并提供了一组方法,用来方便的访问该块内存。
为了理解Buffer的工作原理,需要熟悉它的三个属性:
- capacity 容量
- position 位置
- limit 限制
position
和limit
的含义取决于Buffer处在读模式还是写模式。不管Buffer处在什么模式,capacity
的含义总是一样的。
这里有一个关于capacity
,position
和limit
在读写模式中的说明,详细的解释在插图后面。
capacity
作为一个内存块,Buffer有一个固定的大小值,也叫“capacity
”.你只能往里写capacity
个byte
、long
,char
等类型数据。一旦Buffer
满了,需要将其清空(通过读数据或者清除数据)才能继续往里写数据。
position
当你写数据到Buffer
中时,position
表示当前的位置。初始的position
值为0。当一个byte
、long
等数据写到Buffer
后, position
会向前移动到下一个可插入数据的Buffer
单元。position
最大可为capacity – 1
.
当读取数据时,也是从某个特定位置读。当将Buffer
从写模式切换到读模式,position
会被重置为0. 当从Buffer
的position
处读取数据时,position
向前移动到下一个可读的位置。
limit
在写模式下,Buffer
的limit
表示你最多能往Buffer
里写多少数据。 写模式下,limit
等于Buffer
的capacity
。
当切换Buffer
到读模式时, limit
表示你最多能读到多少数据。因此,当切换Buffer
到读模式时,limit
会被设置成写模式下的position
值。换句话说,你能读到之前写入的所有数据(limit
被设置成已写数据的数量,这个值在写模式下就是position
)
Buffer的类型
Java NIO 有以下Buffer类型
ByteBuffer
MappedByteBuffer
CharBuffer
DoubleBuffer
FloatBuffer
IntBuffer
LongBuffer
ShortBuffer
如你所见,这些Buffer
类型代表了不同的数据类型。换句话说,就是可以通过char
,short
,int
,long
,float
或double
类型来操作缓冲区中的字节。MappedByteBuffer
有些特别,在涉及它的专门章节中再讲。
Buffer的分配
要想获得一个Buffer
对象首先要进行分配。 每一个Buffer
类都有一个allocate
方法。下面是一个分配48字节capacity
的ByteBuffer
的例子。
ByteBuffer buf = ByteBuffer.allocate(48);
这是分配一个可存储1024个字符的CharBuffer
:
CharBuffer buf = CharBuffer.allocate(1024);
向Buffer中写数据
写数据到Buffer
有两种方式:
- 从
Channel
写到Buffer
。 - 通过
Buffer
的put()
方法写到Buffer
里。
从Channel写到Buffer的例子
int bytesRead = inChannel.read(buf); //read into buffer.
通过put方法写Buffer的例子:
buf.put(127);
put方法有很多版本,允许你以不同的方式把数据写入到Buffer中。例如, 写到一个指定的位置,或者把一个字节数组写入到Buffer。 更多Buffer实现的细节参考JavaDoc。
flip()方法
flip
方法将Buffer
从写模式切换到读模式。调用flip()
方法会将position
设回0,并将limit
设置成之前position
的值。
换句话说,position
现在用于标记读的位置,limit
表示之前写进了多少个byte
、char
等 —— 现在能读取多少个byte
、char
等。
从Buffer中读取数据
从Buffer
中读取数据有两种方式:
- 从
Buffer
读取数据到Channel
。 - 使用
get()
方法从Buffer
中读取数据。
从Buffer
读取数据到Channel
的例子:
//从buffer中读取数据到channel.
int bytesWritten = inChannel.write(buf);
使用get()
方法从Buffer
中读取数据的例子
byte aByte = buf.get();
get
方法有很多版本,允许你以不同的方式从Buffer
中读取数据。例如,从指定position
读取,或者从Buffer
中读取数据到字节数组。更多Buffer
实现的细节参考JavaDoc。
rewind()方法
Buffer.rewind()
将position
设回0,所以你可以重读Buffer
中的所有数据。limit
保持不变,仍然表示能从Buffer
中读取多少个元素(byte
、char
等)。
clear()与compact()方法
一旦读完Buffer
中的数据,需要让Buffer
准备好再次被写入。可以通过clear()
或compact()
方法来完成。
如果调用的是clear()
方法,position
将被设回0,limit
被设置成 capacity
的值。换句话说,Buffer
被清空了。Buffer
中的数据并未清除,只是这些标记告诉我们可以从哪里开始往Buffer里写数据。
如果Buffer
中有一些未读的数据,调用clear()
方法,数据将“被遗忘”,意味着不再有任何标记会告诉你哪些数据被读过,哪些还没有。
如果Buffer
中仍有未读的数据,且后续还需要这些数据,但是此时想要先写些数据,那么使用compact()
方法。compact()
方法将所有未读的数据拷贝到Buffer
起始处。然后将position
设到最后一个未读元素正后面。limit
属性依然像clear()
方法一样,设置成capacity
。现在Buffer
准备好写数据了,但是不会覆盖未读的数据。
mark()与reset()方法
通过调用Buffer.mark()
方法,可以标记Buffer
中的一个特定position
。之后可以通过调用Buffer.reset()
方法恢复到这个position
。例如:
buffer.mark();
//调用几次buffer.get()方法。例如在解析过程中
buffer.reset(); //设置position恢复到标记的位置.
equals()与compareTo()方法
可以使用equals()
和compareTo()
方法比较两个Buffer
。
equals()
当满足下列条件时,表示两个Buffer相等:
- 有相同的类型(
byte
、char
、int
等)。 Buffer
中剩余的byte
、char
等的个数相等。Buffer
中所有剩余的byte
、char
等都相同。
如你所见,equals
只是比较Buffer
的一部分,不是每一个在它里面的元素都比较。实际上,它只比较Buffer
中的剩余元素。
compareTo()方法
compareTo()
方法比较两个Buffer
的剩余元素(byte
、char
等), 如果满足下列条件,则认为一个Buffer
“小于”另一个Buffer
:
- 第一个不相等的元素小于另一个Buffer中对应的元素 。
- 所有元素都相等,但第一个Buffer比另一个先耗尽(第一个Buffer的元素个数比另一个少)。
(译注:剩余元素是从 position到limit之间的元素)。
//分配内存
ByteBuffer buffer = ByteBuffer.allocate(1024);
//直接分配内存(物理内存)
ByteBuffer bf = ByteBuffer.allocateDirect(1024);
// Invariants: 0 <= mark <= position <= limit <= capacity
// private int mark = -1; 标记,记录当前position位置,reset()可以恢复到上一个位置
// private int position = 0; 位置,缓冲区正在操作的数据的位置
// private int limit; 界限,缓冲区可以操作数据的大小,limit后的数据不可操作。
// private int capacity; 容量,缓冲区最大存储数据容量,一旦声明不可改变。
System.out.println("刚创建:"+buffer);
String str = "hello";
buffer.put(str.getBytes());
System.out.println("放入数据hello:"+buffer);
//切换到读数据模式
//limit = position;
//position = 0;
//mark = -1;
buffer.flip();
System.out.println("flip之后:"+buffer);
byte[] bytes = new byte[buffer.limit()];
buffer.get(bytes);
System.out.println(new String(bytes));
System.out.println("读取之后:"+buffer);
//可重复读
//position = 0;
//mark = -1;
buffer.rewind();
System.out.println("rewind之后"+buffer);
byte[] bytes1 = new byte[buffer.limit()-1];
buffer.get(bytes1);
System.out.println(new String(bytes1));
System.out.println("读取之后:"+buffer);
//清空缓冲区
// position = 0;
// limit = capacity;
// mark = -1;
buffer.clear();
System.out.println("清空缓冲区clear:"+buffer);
////输出如下:
刚创建:java.nio.HeapByteBuffer[pos=0 lim=1024 cap=1024]
放入数据hello:java.nio.HeapByteBuffer[pos=5 lim=1024 cap=1024]
flip之后:java.nio.HeapByteBuffer[pos=0 lim=5 cap=1024]
hello
读取之后:java.nio.HeapByteBuffer[pos=5 lim=5 cap=1024]
rewind之后java.nio.HeapByteBuffer[pos=0 lim=5 cap=1024]
hell
读取之后:java.nio.HeapByteBuffer[pos=4 lim=5 cap=1024]
清空缓冲区clear:java.nio.HeapByteBuffer[pos=0 lim=1024 cap=1024]
直接缓冲区和非直接缓冲区
- 直接缓冲区就是操作系统控制的内存
- 非直接缓冲区就是jvm控制的内存