ByteBuffer的使用
- 一、认识ByteBuffer
- 二、ByteBuffer使用
- 1、读取文本数据
- 2、正确使用ByteBuffer
- 3、ByteBuffer的结构
- 4、ByteBuffer的常见方法
- (1)分配空间
- (2)向 buffer 写入数据
- (3)从 buffer 读取数据
- (4)mark 和 reset
- 5、练习
一、认识ByteBuffer
Bytebuffer
官方解释A byte buffer,一个字节缓冲区。
二、ByteBuffer使用
1、读取文本数据
有一普通文本文件 data.txt,内容为
1234567890abcd
使用 FileChannel 来读取文件内容
@Slf4j
public class ChannelDemo1 {
public static void main(String[] args) {
/**
* FileChannel获取的方法 1、输入输出流 2、RandomAccessFile
*/
try (RandomAccessFile file = new RandomAccessFile("data.txt", "rw")) {
FileChannel channel = file.getChannel();
// 准备一个缓冲区,大小可以自定义
ByteBuffer buffer = ByteBuffer.allocate(10);
do {
// 从channel中读取数据,向缓冲区中写入
int len = channel.read(buffer);
log.debug("读到字节数:{}", len);
if (len == -1) {
break;
}
// 打印读出的数据即buffer中的内容
// 切换至ByteBuffer的读模式
buffer.flip();
while(buffer.hasRemaining()) {
log.debug("{}", (char)buffer.get());
}
// 切换 buffer 写模式
buffer.clear();
} while (true);
} catch (IOException e) {
e.printStackTrace();
}
}
}
输出
10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 读到字节数:10
10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 1
10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 2
10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 3
10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 4
10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 5
10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 6
10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 7
10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 8
10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 9
10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 0
10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 读到字节数:4
10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - a
10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - b
10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - c
10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - d
10:39:03 [DEBUG] [main] c.i.n.ChannelDemo1 - 读到字节数:-1
2、正确使用ByteBuffer
1、 向 buffer 写入数据,例如调用 channel.read(buffer)
2、调用 flip() 切换至读模式
3、从 buffer 读取数据,例如调用 buffer.get()
4、 调用 clear() 或 compact() 切换至写模式
5、 重复 1~4 步骤
3、ByteBuffer的结构
ByteBuffer 有以下重要属性
- capacity
- position
- limit
一开始
写模式下,position 是写入位置,limit 等于容量,下图表示写入了 4 个字节后的状态
flip 动作发生后,position 切换为读取位置,limit 切换为读取限制
读取 4 个字节后,状态
clear 动作发生后,状态
compact 方法,是把未读完的部分向前压缩,然后切换至写模式
4、ByteBuffer的常见方法
为了方便控制台输出和查看,提供了一个工具类,ByteBufferUtil
,稍后会放在文章末尾。
(1)分配空间
/**
* class java.nio.HeapByteBuffer Java堆内存,读写效率低,受到GC影响
* class java.nio.DirectByteBuffer 直接内存,读写效率高(少一次拷贝),不会受到GC影响,
* 分配的效率低,使用不当会造成内存泄漏
*/
System.out.println(ByteBuffer.allocate(16).getClass());
System.out.println(ByteBuffer.allocateDirect(16).getClass());
(2)向 buffer 写入数据
向buffer中写入数据可以使用以下两种方式:
- 调用 channel 的 read 方法
- 调用 buffer 自己的 put 方法
// 调用 channel 的 read 方法
int readBytes = channel.read(buffer);
// 调用 buffer 自己的 put 方法
buffer.put((byte) 0x61);
如下例:
ByteBuffer buffer = ByteBuffer.allocate(10); // 创建一个ByteBuffer缓冲区
buffer.put((byte) 0x61); //写入单个字节数据
ByteBufferUtil.debugAll(buffer); //查看buffer结构的工具方法
buffer.put(new byte[]{0x62, 0x63}); //写入字节数组数据
ByteBufferUtil.debugAll(buffer); //查看buffer结构的工具方法
从下面的输出可以看到,第一次写入了一个a,在索引为0的地方;第二个写入了一个数组,两个字符 a和b,分别在下标0和1的地方。
同时也能看到buffer中的position和limit的值。
+--------+-------------------- all ------------------------+----------------+
position: [1], limit: [10]
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 61 00 00 00 00 00 00 00 00 00 |a......... |
+--------+-------------------------------------------------+----------------+
+--------+-------------------- all ------------------------+----------------+
position: [3], limit: [10]
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 61 62 63 00 00 00 00 00 00 00 |abc....... |
+--------+-------------------------------------------------+----------------+
Process finished with exit code 0
(3)从 buffer 读取数据
同样有两种办法
- 调用 channel 的 write 方法
- 调用 buffer 自己的 get 方法
int writeBytes = channel.write(buf);
和
byte b = buf.get();
get 方法会让 position 读指针向后走,如果想重复读取数据
- 可以调用 rewind 方法将 position 重新置为 0
- 或者调用 get(int i) 方法获取索引 i 的内容,它不会移动读指针
如下示例:
ByteBuffer buffer = ByteBuffer.allocate(10); // 创建一个buffer缓冲区
buffer.put(new byte[]{'a', 'b', 'c', 'd'}); // 向缓冲区输入四个字符
buffer.flip(); //转换为bytebuffer读模式
buffer.get(new byte[4]); //get,一次读取多个字节
ByteBufferUtil.debugAll(buffer); //查看buffer结构的工具方法
buffer.rewind(); //重置position
ByteBufferUtil.debugAll(buffer); //查看buffer结构的工具方法
执行结果如下:
可以看到,当get多个数据的时候,position的值会改变。当执行了rewind()方法后,position的值被重置为0。
+--------+-------------------- all ------------------------+----------------+
position: [4], limit: [4]
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 61 62 63 64 00 00 00 00 00 00 |abcd...... |
+--------+-------------------------------------------------+----------------+
+--------+-------------------- all ------------------------+----------------+
position: [0], limit: [4]
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 61 62 63 64 00 00 00 00 00 00 |abcd...... |
+--------+-------------------------------------------------+----------------+
(4)mark 和 reset
mark 是在读取时,做一个标记,即使 position 改变,只要调用 reset 就能回到 mark 的位置。
注意
rewind 和 flip 都会清除 mark 位置
如下示例:
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put(new byte[]{'a', 'b', 'c', 'd'});
buffer.flip();
System.out.println((char) buffer.get());
System.out.println((char) buffer.get());
buffer.mark();
System.out.println((char) buffer.get());
System.out.println((char) buffer.get());
ByteBufferUtil.debugAll(buffer);
buffer.reset();
ByteBufferUtil.debugAll(buffer);
执行结果如下:
可以看到,当get了两个数据后,执行了mark()方法,然后再将最后的两个数据进行读取完毕,最后position的值为4,当执行过reset()方法之后,position的值变成了2。
a
b
c
d
+--------+-------------------- all ------------------------+----------------+
position: [4], limit: [4]
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 61 62 63 64 00 00 00 00 00 00 |abcd...... |
+--------+-------------------------------------------------+----------------+
+--------+-------------------- all ------------------------+----------------+
position: [2], limit: [4]
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 61 62 63 64 00 00 00 00 00 00 |abcd...... |
+--------+-------------------------------------------------+----------------+
5、练习
网络上有多条数据发送给服务端,数据之间使用 \n 进行分隔
但由于某种原因这些数据在接收时,被进行了重新组合,例如原始数据有3条为
- Hello,world\n
- I’m zhangsan\n
- How are you?\n
变成了下面的两个 byteBuffer (黏包,半包)
- Hello,world\nI’m zhangsan\nHo
- w are you?\n
现在要求你编写程序,将错乱的数据恢复成原始的按 \n 分隔的数据。
public static void main(String[] args) {
/**
* 网络上有多条数据发送给服务端,数据之间使用 \n 进行分割
* 但是由于某种原因,数据在接收时,被进行了重新组合,例如:
* Hello World\n
* I'm zhangsan\n
* How are you\n
* 变成了下面两个byteBuffer (黏包,半包)
* Hello World\n I'm zhangsan\nHo
* w are you\n
* 现在要求将错乱的程序恢复原始的正常的数据
*/
ByteBuffer source = ByteBuffer.allocate(32);
source.put("HelloWorld\nI'm zhangsan\nHo".getBytes());
split(source);
source.put("w are you?\n".getBytes());
split(source);
}
private static void split(ByteBuffer source) {
// ByteBuffer切换到读模式
source.flip();
for (int i = 0; i < source.limit(); i++) {
// 找到一条完整的消息
if (source.get(i) == '\n') {
int len = i + 1 - source.position();
// 把这条消息完整的存入新的ByteBuffer
ByteBuffer target = ByteBuffer.allocate(len);
for (int j = 0; j < len; j++) {
target.put(source.get());
}
ByteBufferUtil.debugAll(target);
}
}
// 标记当前位置,将后续字节整理后放在下次读取的数据前面
source.compact();
}
执行后的结果如下:
+--------+-------------------- all ------------------------+----------------+
position: [11], limit: [11]
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 48 65 6c 6c 6f 57 6f 72 6c 64 0a |HelloWorld. |
+--------+-------------------------------------------------+----------------+
+--------+-------------------- all ------------------------+----------------+
position: [13], limit: [13]
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 49 27 6d 20 7a 68 61 6e 67 73 61 6e 0a |I'm zhangsan. |
+--------+-------------------------------------------------+----------------+
+--------+-------------------- all ------------------------+----------------+
position: [13], limit: [13]
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 48 6f 77 20 61 72 65 20 79 6f 75 3f 0a |How are you?. |
+--------+-------------------------------------------------+----------------+