一、.基本使用
使用ByteBuffer读取文件中的内容:
public class TestByteBuffer {
public static void main(String[] args) {
// 获得FileChannel
try (FileChannel channel = new FileInputStream("stu.txt").getChannel()) {
// 获得缓冲区
ByteBuffer buffer = ByteBuffer.allocate(10);
int hasNext = 0;
StringBuilder builder = new StringBuilder();
while((hasNext = channel.read(buffer)) > 0) {
// 切换模式 limit=position, position=0
buffer.flip();
// 当buffer中还有数据时,获取其中的数据
while(buffer.hasRemaining()) {
builder.append((char)buffer.get());
}
// 切换模式 position=0, limit=capacity
buffer.clear();
}
System.out.println(builder.toString());
} catch (IOException e) {
}
}
}
- 向 buffer 写入数据,调用 channel.read(buffer):从channel读取数据写入buffer
- 调用 flip() 切换至读模式
- flip会使得buffer中的limit变为position,position变为0
- 从 buffer 读取数据,调用 buffer.get()
- 调用 clear() 或者compact()切换至写模式
- 调用clear()方法时position=0,limit变为capacity
- 调用compact()方法时,会将缓冲区中的未读数据压缩到缓冲区前面
- 重复以上步骤
二、结构
bytebuffer.pngpublic class TestByteBufferReadWrite {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put((byte) 0x61); // 'a'
debugAll(buffer);
/**
* 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......... |
* +--------+-------------------------------------------------+----------------+
*/
buffer.put(new byte[]{0x62, 0x63, 0x64}); // b c d
debugAll(buffer);
/**
* position: [4], limit: [10]
* +-------------------------------------------------+
* | 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...... |
* +--------+-------------------------------------------------+----------------+
*/
//0 不切换成读模式,读不到数据
// System.out.println(buffer.get());
buffer.flip();
//97
System.out.println(buffer.get());
debugAll(buffer);
/**
* position: [1], limit: [5]
* +-------------------------------------------------+
* | 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...... |
* +--------+-------------------------------------------------+----------------+
*/
buffer.compact();
debugAll(buffer);
/**
* position: [3], limit: [10]
* +-------------------------------------------------+
* | 0 1 2 3 4 5 6 7 8 9 a b c d e f |
* +--------+-------------------------------------------------+----------------+
* |00000000| 62 63 64 64 00 00 00 00 00 00 |bcdd...... |
* +--------+-------------------------------------------------+----------------+
* +--------+-------------------- all ------------------------+----------------+
*/
buffer.put(new byte[]{0x65,0x66});
debugAll(buffer);
/**
* position: [5], limit: [10]
* +-------------------------------------------------+
* | 0 1 2 3 4 5 6 7 8 9 a b c d e f |
* +--------+-------------------------------------------------+----------------+
* |00000000| 62 63 64 65 66 00 00 00 00 00 |bcdef..... |
* +--------+-------------------------------------------------+----------------+
*/
}
}
三、常见方法
1.分配空间
可以使用 allocate 方法为 ByteBuffer 分配空间,其它 buffer 类也有该方法
import java.nio.ByteBuffer;
public class TestByteBufferAllocate {
public static void main(String[] args) {
System.out.println(ByteBuffer.allocate(16).getClass()); // class java.nio.HeapByteBuffer
System.out.println(ByteBuffer.allocateDirect(16).getClass()); // class java.nio.DirectByteBuffer
}
}
- class java.nio.HeapByteBuffer - java 堆内存,读写效率较低,受到 GC 的影响
- class java.nio.DirectByteBuffer - 直接内存,读写效率高(少一次拷贝),不会受 GC 影响,分配的效率低
2.向 buffer 写入数据
- 调用 channel 的 read 方法:
int readBytes = channel.read(buf);
- 调用 buffer 自己的 put 方法:
buf.put((byte)127);
3.从 buffer 读取数据
- 调用 channel 的 write 方法:
int writeBytes = channel.write(buf);
- 调用 buffer 自己的 get 方法:
byte b = buf.get();
get 方法会让 position 读指针向后走,如果想重复读取数据
- 可以调用 rewind 方法将 position 重新置为 0
- 或者调用 get(int i) 方法获取索引 i 的内容,它不会移动读指针
4.mark 和 reset
mark 是在读取时,做一个标记,即使 position 改变,只要调用 reset 就能回到 mark 的位置
public class TestByteBufferRead {
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put(new byte[]{'a', 'b', 'c', 'd'});
buffer.flip();
// rewind 从头开始读
/*
buffer.get(new byte[4]);
debugAll(buffer);
buffer.rewind();
System.out.println((char)buffer.get());
*/
/**
* 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...... |
* a
*/
// mark & reset
// mark 做一个标记,记录 position 位置, reset 是将 position 重置到 mark 的位置
/*
System.out.println((char) buffer.get()); //a
System.out.println((char) buffer.get()); //b
buffer.mark(); // 加标记,索引 2 的位置
System.out.println((char) buffer.get()); //c
System.out.println((char) buffer.get()); //d
buffer.reset(); // 将 position 重置到做标记的地方,即索引 2
System.out.println((char) buffer.get()); //c
System.out.println((char) buffer.get()); //d
*/
// get(i) 不会改变读索引的位置
System.out.println((char) buffer.get(3)); //d
debugAll(buffer);
/**
* 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...... |
*/
}
}
rewind 和 flip 都会清除 mark 位置
四、字符串与 ByteBuffer 互转
public class TestByteBufferString {
public static void main(String[] args) {
// 1. 字符串转为 ByteBuffer
ByteBuffer buffer1 = ByteBuffer.allocate(16);
buffer1.put("hello".getBytes());
debugAll(buffer1);
/**
* position: [5], limit: [16]
* +-------------------------------------------------+
* | 0 1 2 3 4 5 6 7 8 9 a b c d e f |
* +--------+-------------------------------------------------+----------------+
* |00000000| 68 65 6c 6c 6f 00 00 00 00 00 00 00 00 00 00 00 |hello...........|
*/
// 2. Charset 会自动切换到读模式
ByteBuffer buffer2 = StandardCharsets.UTF_8.encode("hello");
debugAll(buffer2);
/**
* position: [0], limit: [5]
* +-------------------------------------------------+
* | 0 1 2 3 4 5 6 7 8 9 a b c d e f |
* +--------+-------------------------------------------------+----------------+
* |00000000| 68 65 6c 6c 6f |hello |
*/
// 3. wrap 会自动切换到读模式
ByteBuffer buffer3 = ByteBuffer.wrap("hello".getBytes());
debugAll(buffer3);
/**
* position: [0], limit: [5]
* +-------------------------------------------------+
* | 0 1 2 3 4 5 6 7 8 9 a b c d e f |
* +--------+-------------------------------------------------+----------------+
* |00000000| 68 65 6c 6c 6f |hello |
*/
// 4. 转为字符串 读模式下可以直接转 写模式下找不到数据
String str1 = StandardCharsets.UTF_8.decode(buffer2).toString();
System.out.println(str1);//hello
// 写模式下需要切换为读模式
buffer1.flip();
String str2 = StandardCharsets.UTF_8.decode(buffer1).toString();
System.out.println(str2);//hello
}
}
五、Scattering Reads
分散读取,文本文件 3parts.txt
onetwothree
public class TestScatteringReads {
public static void main(String[] args) {
try (FileChannel channel = new RandomAccessFile("words2.txt", "r").getChannel()) {
ByteBuffer b1 = ByteBuffer.allocate(3);
ByteBuffer b2 = ByteBuffer.allocate(3);
ByteBuffer b3 = ByteBuffer.allocate(5);
channel.read(new ByteBuffer[]{b1, b2, b3});
b1.flip();
b2.flip();
b3.flip();
debugAll(b1);
debugAll(b2);
debugAll(b3);
} catch (IOException e) {
}
}
}
position: [0], limit: [3]
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 68 65 6c |hel |
+--------+-------------------------------------------------+----------------+
+--------+-------------------- all ------------------------+----------------+
position: [0], limit: [3]
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 6c 6f 77 |low |
+--------+-------------------------------------------------+----------------+
+--------+-------------------- all ------------------------+----------------+
position: [0], limit: [5]
+-------------------------------------------------+
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
+--------+-------------------------------------------------+----------------+
|00000000| 6f 72 6c 64 e4 |orld. |
六、Gathering Writes
将多个 buffer 的数据填充至 channel
public class TestGatheringWrites {
public static void main(String[] args) {
ByteBuffer b1 = StandardCharsets.UTF_8.encode("hello");
ByteBuffer b2 = StandardCharsets.UTF_8.encode("world");
ByteBuffer b3 = StandardCharsets.UTF_8.encode("你好");
try (FileChannel channel = new RandomAccessFile("words.txt", "rw").getChannel()) {
channel.write(new ByteBuffer[]{b1, b2, b3});
} catch (IOException e) {
}
}
}
减少数据拷贝,提高效率
七、网络数据传输粘包和半包
粘包:一次性发送多条数据
半包:接收方缓冲区大小限制
网络上有多条数据发送给服务端,数据之间使用 \n 进行分隔
但由于某种原因这些数据在接收时,被进行了重新组合。
将错乱的数据恢复成原始的按 \n 分隔的数据
public class TestByteBufferExam {
public static void main(String[] args) {
/*
例如原始数据有3条为
Hello,world\n
I'm zhangsan\n
How are you?\n
变成了下面的两个 byteBuffer (黏包,半包)
Hello,world\nI'm zhangsan\nHo
w are you?\n
将错乱的数据恢复成原始的按 \n 分隔的数据
*/
ByteBuffer source = ByteBuffer.allocate(32);
source.put("Hello,world\nI'm zhangsan\nHo".getBytes());
split(source);
source.put("w are you?\n".getBytes());
split(source);
}
private static void split(ByteBuffer source) {
source.flip();
for (int i = 0; i < source.limit(); i++) {
// 找到一条完整消息
if (source.get(i) == '\n') {
int length = i + 1 - source.position();
// 把这条完整消息存入新的 ByteBuffer
ByteBuffer target = ByteBuffer.allocate(length);
// 从 source 读,向 target 写
for (int j = 0; j < length; j++) {
target.put(source.get());
}
debugAll(target);
}
}
source.compact();
}
}
网友评论