ByteBuffer的几个重要方法
- [1. 分配内存(allocate vs allocateDirect)](#1. 分配内存(allocate vs allocateDirect))
- [2. 向Buffer写入数据](#2. 向Buffer写入数据)
-
- [2.1 从Channel中读取数据并写入Buffer](#2.1 从Channel中读取数据并写入Buffer)
- [2.2 调用Buffer的put方法](#2.2 调用Buffer的put方法)
- [3. 从Buffer中读取数据](#3. 从Buffer中读取数据)
-
- [3.1 从Buffer中读取数据,写入Channel](#3.1 从Buffer中读取数据,写入Channel)
- [3.2 调用Buffer的get(index)方法,获取指定索引位置的数据](#3.2 调用Buffer的get(index)方法,获取指定索引位置的数据)
- [3.3 调用Buffer的get()方法,获取当前position位置的数据](#3.3 调用Buffer的get()方法,获取当前position位置的数据)
- [4. mark & reset](#4. mark & reset)
- [5. 将字符串转为buffer](#5. 将字符串转为buffer)
-
- [5.1 将字符串转为字节数组,写入ByteBuffer](#5.1 将字符串转为字节数组,写入ByteBuffer)
- [5.2 使用Charset的encode方法,将字符串包装成ByteBuffer](#5.2 使用Charset的encode方法,将字符串包装成ByteBuffer)
- [5.3 使用ByteBuffer的wrap方法,将字符串包装成ByteBuffer](#5.3 使用ByteBuffer的wrap方法,将字符串包装成ByteBuffer)
- [6. 分散读 & 集中写(了解)](#6. 分散读 & 集中写(了解))
-
- [6.1 分散读](#6.1 分散读)
- [6.2 集中写](#6.2 集中写)
1. 分配内存(allocate vs allocateDirect)
从内存中划分一块区域给ByteBuffer有两种方式
- 方式一:从【JVM堆内存】中分配;
- 方式二:从【直接内存】中分配(操作系统内存);
JVM堆内存的读写效率比直接内存低;因为
- JVM堆内存多一次数据拷贝;
- 堆内存受到JVM垃圾回收的影响;
java
public static void main(String[] args) {
// 从【JVM堆内存】中分配
ByteBuffer allocate = ByteBuffer.allocate(16);
// 从【直接内存】中分配(物理内存)
ByteBuffer allocateDirect = ByteBuffer.allocateDirect(16);
System.out.println(allocate.getClass()); // class java.nio.HeapByteBuffer
System.out.println(allocateDirect.getClass()); // class java.nio.DirectByteBuffer
}
| 对比项 | allocate(堆) | allocateDirect(直接) |
|---|---|---|
| 内存位置 | JVM 堆 | 堆外(OS 内存) |
| 分配/回收速度 | 快 | 慢 |
| I/O 性能 | 较低(需复制) | 高(零拷贝) |
| 适用场景 | 普通场景 | 高频 I/O、大文件 |
2. 向Buffer写入数据
2.1 从Channel中读取数据并写入Buffer
java
public static void main(String[] args) {
try(FileInputStream fileInputStream = new FileInputStream("src/main/resources/Buffer01_HelloWorld.txt")) {
// 获取文件channel
FileChannel channel = fileInputStream.getChannel();
// 从内存中分配一个buffer,大小为10个字节
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
// 从channel中读取数据,并写到buffer中
channel.read(byteBuffer);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
2.2 调用Buffer的put方法
java
public static void main(String[] args) {
// 从内存中分配一个buffer,大小为10个字节
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
// 写入一个byte
byteBuffer.put((byte) 1);
// 写入byte数组
byteBuffer.put("abc".getBytes(StandardCharsets.UTF_8));
}
3. 从Buffer中读取数据
注意:在从buffer中读取数据之前,必须调用flip方法切换到读模式!
3.1 从Buffer中读取数据,写入Channel
java
public static void main(String[] args) {
try (FileOutputStream fileOutputStream = new FileOutputStream("src/main/resources/test.txt")) {
// 获取文件channel
FileChannel channel = fileOutputStream.getChannel();
// 从内存中分配一个buffer
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
// 向buffer中写入数据
byteBuffer.put("abc".getBytes(StandardCharsets.UTF_8));
// 切换到buffer的读模式
byteBuffer.flip();
// 从buffer中读取数据,写入channel
int write = channel.write(byteBuffer);
// 打印写入的字节数:3
System.out.println(write);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
3.2 调用Buffer的get(index)方法,获取指定索引位置的数据
该方法不会移动position的位置,就像操作数组一样,可以重复读取;
java
public static void main(String[] args) {
// 从内存中分配一个buffer
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
// 向buffer中写入数据
byteBuffer.put("abc".getBytes(StandardCharsets.UTF_8));
// 切换到buffer的读模式
byteBuffer.flip();
// get(index),不会移动position的位置,就像操作数组一样,可以重复读取;
System.out.println(byteBuffer.get(0)); // 97
System.out.println(byteBuffer.get(1)); // 98
System.out.println(byteBuffer.get(2)); // 99
System.out.println(byteBuffer.get(0)); // 97
}
3.3 调用Buffer的get()方法,获取当前position位置的数据
该方法会让position移动,当读取完Buffer中的数据后,position也移动到Buffer的末尾,此时再读取就会出异常:java.nio.BufferUnderflowException
java
public static void main(String[] args) {
// 从内存中分配一个buffer
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
// 向buffer中写入数据
byteBuffer.put("abc".getBytes(StandardCharsets.UTF_8));
// 切换到buffer的读模式
byteBuffer.flip();
// get(),会让position移动
System.out.println(byteBuffer.get());
System.out.println(byteBuffer.get());
System.out.println(byteBuffer.get());
System.out.println(byteBuffer.get());
}
输出结果
text
"C:\Program Files\Java\jdk-1.8\bin\java.exe" ...
97
98
99
Exception in thread "main" java.nio.BufferUnderflowException
at java.nio.Buffer.nextGetIndex(Buffer.java:524)
at java.nio.HeapByteBuffer.get(HeapByteBuffer.java:138)
at com.xxx.xxx.main(xxx.java:26)
Process finished with exit code 1
如果想重复读取数据,可以调用rewind方法,将position重新置为0;
java
public static void main(String[] args) {
// 从内存中分配一个buffer
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
// 向buffer中写入数据
byteBuffer.put("abc".getBytes(StandardCharsets.UTF_8));
// 切换到buffer的读模式
byteBuffer.flip();
// get(),会让position移动
System.out.println(byteBuffer.get()); //97
System.out.println(byteBuffer.get()); //98
System.out.println(byteBuffer.get()); //99
// 调用rewind方法,将position重新置为0
byteBuffer.rewind();
System.out.println(byteBuffer.get()); //97
}
4. mark & reset
- mark():使用变量mark,记住当前position的值;
- reset():将position的值重新设为mark;
java
public static void main(String[] args) {
// 从内存中分配一个buffer
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
// 向buffer中写入数据
byteBuffer.put("abc".getBytes(StandardCharsets.UTF_8));
// 切换到buffer的读模式
byteBuffer.flip();
// 使用变量mark,记住当前position的值,当前position 即0;
System.out.println(byteBuffer.mark());
// get(),会让position移动
System.out.println(byteBuffer.get());
System.out.println(byteBuffer.get());
System.out.println(byteBuffer.get());
// 将position的值重新设为mark,即0;
System.out.println(byteBuffer.reset());
// 此时可以再次从头读取
System.out.println(byteBuffer.get());
}
输出结果
text
"C:\Program Files\Java\jdk-1.8\bin\java.exe" ...
java.nio.HeapByteBuffer[pos=0 lim=3 cap=10]
97
98
99
java.nio.HeapByteBuffer[pos=0 lim=3 cap=10]
97
Process finished with exit code 0
5. 将字符串转为buffer
5.1 将字符串转为字节数组,写入ByteBuffer
java
public static void main(String[] args) {
// 从内存中分配一个buffer
ByteBuffer buffer = ByteBuffer.allocate(16);
// 将字符串转为字节数组,写入buffer
buffer.put("hello".getBytes(StandardCharsets.UTF_8));
// 切换到读模式
buffer.flip();
System.out.println(buffer.get()); //104
System.out.println(buffer.get()); //101
System.out.println(buffer.get()); //108
System.out.println(buffer.get()); //108
System.out.println(buffer.get()); //111
}
5.2 使用Charset的encode方法,将字符串包装成ByteBuffer
注意:在从buffer中读取数据之前,必须调用flip方法切换到读模式,
通过StandardCharsets.UTF_8.encode方法,返回ByteBuffer对象之前,已经调用过flip方法了;但是为了保证已经切换到读模式,最好再读取数据之前,显式调用flip方法;
java
public static void main(String[] args) {
// 使用Charset的encode方法,将字符串包装成ByteBuffer
// 返回ByteBuffer对象之前,已经调用过flip方法
ByteBuffer buffer = StandardCharsets.UTF_8.encode("hello");
// 显式调用flip方法,确保切换到读模式
buffer.flip();
System.out.println(buffer.get()); //104
System.out.println(buffer.get()); //101
System.out.println(buffer.get()); //108
System.out.println(buffer.get()); //108
System.out.println(buffer.get()); //111
}
5.3 使用ByteBuffer的wrap方法,将字符串包装成ByteBuffer
java
public static void main(String[] args) {
// 使用ByteBuffer的wrap方法,将字符串包装成ByteBuffer
ByteBuffer buffer = ByteBuffer.wrap("hello".getBytes(StandardCharsets.UTF_8));
// 切换到读模式
buffer.flip();
System.out.println(buffer.get()); //104
System.out.println(buffer.get()); //101
System.out.println(buffer.get()); //108
System.out.println(buffer.get()); //108
System.out.println(buffer.get()); //111
}
6. 分散读 & 集中写(了解)
6.1 分散读
读取文件中的内容,写入多个Buffer中。
文件内容:
text
owntwothree
java
public static void main(String[] args) {
try (FileInputStream fileInputStream = new FileInputStream("test.txt")) {
FileChannel channel = fileInputStream.getChannel();
ByteBuffer buffer1 = ByteBuffer.allocate(3);
ByteBuffer buffer2 = ByteBuffer.allocate(3);
ByteBuffer buffer3 = ByteBuffer.allocate(5);
long read = channel.read(new ByteBuffer[]{buffer1, buffer2, buffer3});
System.out.println("read bytes: " + read); // 11
// 第一个buffer写入【one】
// 第二个buffer写入【two】
// 第三个buffer写入【three】
} catch (IOException e) {
throw new RuntimeException(e);
}
}
6.2 集中写
将多个Buffer中的数据,写入一个文件
java
public static void main(String[] args) {
ByteBuffer buffer1 = StandardCharsets.UTF_8.encode("hello"); //5 bytes
ByteBuffer buffer2 = StandardCharsets.UTF_8.encode("world"); //5 bytes
ByteBuffer buffer3 = StandardCharsets.UTF_8.encode("你好"); //6 bytes
try (FileOutputStream fileOutputStream = new FileOutputStream("text.txt")) {
FileChannel channel = fileOutputStream.getChannel();
long write = channel.write(new ByteBuffer[]{buffer1, buffer2, buffer3});
System.out.println("write bytes: " + write); //16
} catch (IOException e) {
throw new RuntimeException(e);
}
}