基本概念
一、缓冲区(Buffer):在 Java NIO 中负责数据的存取,缓冲区底层就是数组。用于存储不同数据类型的数据
1)根据数据类型的不同,提供对应类型的缓冲区(boolean类型除外)
- ByteBuffer (最常用,也只有它支持直接缓冲区)
- CharBuffer
- ShortBuffer
- IntBuffer
- LongBuffer
- FloatBuffer
- DoubleBuffer
上述缓冲区的管理方法几乎一致,通过allocate() 获取缓冲区
二、缓冲区存储数据的两个核心方法
- 1、put() :存入数据到缓冲区中
- 2、get() :获取缓冲区中的数据
三、缓冲区的核心属性(mark <= position <= limit <= capacity)
- position :位置,表示缓冲区中正在操作数据的位置(默认值 0)
- limit :界限,表示缓冲区中可以操作数据的大小(limit后面的数据不能读写)
- capacity :容量,表示缓冲区中可以最大存储数据的容量,一旦声明就不可修改
- mark :标记上次position的位置,需要使用 mark() 方法标记(不标记默认值 -1)
使用 reset() 恢复 position 到标记位置,之前必须使用 mark() 方法标记过
如果 mark < 0 会抛出异常,说明你就没有 mark() 过,当然不能reset()
四、直接缓冲区与非直接缓冲区
- 非直接缓冲区:通过 allocate() 方法分配缓冲区,将缓冲区建立在JVM的内存中
- 直接缓冲区:通过 allocateDirect() 方法分配直接缓冲区,将缓冲区建立在操作系统的物理内存中。可以提高效率
使用示例
下面就来调用一下各个方法,并且查看Buffer在不同状态时各个属性的值吧
简单的不指定长度的读取、写入、重置、清空
@Test
public void test1(){
String str = "abcde";
//1.分配一个指定大小的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(1024);
System.out.println("=============初始================");
System.out.println("position:" + buffer.position());
System.out.println("limit:"+ buffer.limit());
System.out.println("capacity:" + buffer.capacity());
//2.向缓冲区中存入数据
buffer.put(str.getBytes());
System.out.println("=============put()================");
System.out.println("position:" + buffer.position());
System.out.println("limit:"+ buffer.limit());
System.out.println("capacity:" + buffer.capacity());
//3.设置缓冲区状态为读取
buffer.flip();
System.out.println("=============flip()================");
System.out.println("position:" + buffer.position());
System.out.println("limit:"+ buffer.limit());
System.out.println("capacity:" + buffer.capacity());
//4.从缓冲区中读取数据
byte[] bytes = new byte[buffer.limit()];
buffer.get(bytes);//获取全部数据
System.out.println("=============get()================");
System.out.println("position:" + buffer.position());
System.out.println("limit:"+ buffer.limit());
System.out.println("capacity:" + buffer.capacity());
System.out.println(new String(bytes));
//5.可以重新读取数据
buffer.rewind();//调用该方法会将position重新指向缓冲区的头部,并清空mark
buffer.get(bytes);//获取全部数据
System.out.println("=============rewind()================");
System.out.println("position:" + buffer.position());
System.out.println("limit:"+ buffer.limit());
System.out.println("capacity:" + buffer.capacity());
System.out.println(new String(bytes));
//6.清空缓冲区,只是重置各个指针的位置,数据没有真正清除
buffer.clear();
System.out.println("=============clear()================");
System.out.println("position:" + buffer.position());
System.out.println("limit:"+ buffer.limit());
System.out.println("capacity:" + buffer.capacity());
System.out.println("clear后读取第一个字符还是有数据的:" + (char)buffer.get());
}
输出
=============初始================
position:0
limit:1024
capacity:1024
=============put()================
position:5
limit:1024
capacity:1024
=============flip()================
position:0
limit:5
capacity:1024
=============get()================
position:5
limit:5
capacity:1024
abcde
=============rewind()================
position:5
limit:5
capacity:1024
abcde
=============clear()================
position:0
limit:1024
capacity:1024
clear后读取第一个字符还是有数据的:a
指定长度的读取、mark、reset、hasRemaining、remaining
Buffer可以制定长度的读取和写入,两种模式一样,这里就写一遍get,同样的方式也可以用在put上
@Test
public void test2(){
String str = "abcde";
ByteBuffer buf = ByteBuffer.allocate(1024);
buf.put(str.getBytes());
System.out.println("=============put()================");
System.out.println("position:" + buf.position());
System.out.println("limit:"+ buf.limit());
System.out.println("capacity:" + buf.capacity());
buf.flip();// 切换到读模式,否则position往后走啥也读不到
// 1.先读取前两位数据,并mark
byte[] bytes = new byte[buf.limit()];
// 一定要注意这个方法的三个参数所表示的含义
// 这里的第二个参数 0 也就是 offset 指定的是从数组 bytes 的什么位置开始写
// 而第三个参数 2 也就是 length 表示缓冲区 buf 会从position往后读length长度的数据
buf.get(bytes, 0 , 2);
buf.mark(); // 标记一下
System.out.println("=============get(byte[], int, int)================");
System.out.println("position:" + buf.position());
System.out.println("limit:"+ buf.limit());
System.out.println("capacity:" + buf.capacity());
System.out.println(new String(bytes));
// 2.从缓冲区继续读取两位数据
buf.get(bytes,2, 2);
System.out.println("=============get(byte[], int, int)================");
System.out.println("position:" + buf.position());
System.out.println("limit:"+ buf.limit());
System.out.println("capacity:" + buf.capacity());
System.out.println(new String(bytes));
// 3.查看缓冲区是否还有可操作的位置
if(buf.hasRemaining()){
// 输出缓冲区中可以操作的数量
System.out.println("=============remaining()================");
System.out.println("remaining:" + buf.remaining());
}
// 4.恢复读取的指针到mark标记的位置,使用reset(),并且读取剩余数据
buf.reset();
// 这里长度要指定好,不要超出缓冲区可以读取的长度,就是当前的 (limit - position)
// 否则后面读取的时候会报错的
byte[] bytes2 = new byte[buf.limit() - buf.position()];
buf.get(bytes2);//将剩余数据全部读取出来
System.out.println("=============reset()================");
System.out.println("position:" + buf.position());
System.out.println("limit:"+ buf.limit());
System.out.println("capacity:" + buf.capacity());
System.out.println(new String(bytes2));
}
输出
=============put()================
position:5
limit:1024
capacity:1024
=============get(byte[], int, int)================
position:2
limit:5
capacity:1024
ab
=============get(byte[], int, int)================
position:4
limit:5
capacity:1024
abcd
=============reset()================
position:5
limit:5
capacity:1024
cde
创建直接缓冲区
- 直接缓冲区要消耗更多的资源来创建,除非有更大的好处,否则创建 非直接缓冲区
- 只有 ByteBuffer 支持 直接缓冲区
public void test3(){
//创建直接缓冲区,只有 ByteBuffer 支持
ByteBuffer buf = ByteBuffer.allocateDirect(1024);
//该方法能判断缓冲区类型,true 直接缓冲区, false 非直接缓冲区
System.out.println(buf.isDirect());
}
输出
true
方法总结
- get(byte[]) 缓冲区中读取数据写入指定数组
- put(bute[]) 指定数组中读取数据并写入缓冲区
- flip() 翻转读写模式,并清空mark
- rewind() position置为0,并清空mark
- clear() 清空缓冲区,实际上只是重各个属性的值,并没有清空缓冲区内的数组,也会将读模式切回写模式
- compact() 如果有数据未读完,但是想切换回写模式先写,可以使用此方法,此方法会将未读数据copy到缓冲区的头部,并将position指到未读数据的尾部,limit重新设置为capacity
- get(byte[] bytes, int offset, int length) 从缓冲区中读取length长度的数据写入bytes,并从bytes的offset位置开始写
- put(byte[] bytes, int offset, int length) 从数组的offset位置读取length长度的数据写入缓冲区
- hasRemaining() 数组中是否还有可以操作的位置,读写都可以调用这个方法,写表示是否还有空间可以写,读表示是否还有数据未读
- remaining() 获取剩余可以操作的空间大小
- mark() 记录当前正在操作的位置
- reset() 恢复到上次mark的位置,注意,必须在调用了mark之后才能调用此方法,而且在上次mark和调用reset之间必须没有调用过清空mark的方法,也就是mark不能为 -1