1 缓冲区(Buffer)
1.1 缓冲区基本类型
- 在Java NIO中,负责数据的存取。缓冲区底层实现使用数组,用于存储不同类型的数据。根据数据类型不同(boolean除外),提供了相应类型的缓冲区。
ByteBuffer
CharBuffer
ShortBuffer
IntBuffer
LongBuffer
FloatBuffer
DoubleBuffer
- 上述缓冲区的管理方式几乎一致,通过
allocate()
获取缓冲区
1.2 缓冲区存取数据的两个核心方法
put()
:存入数据到缓冲区get()
:获取缓冲区中的数据
1.3 缓冲区中的四个核心属性
capacity
:容量,表示缓冲区中最大存储数据的容量。一旦声明不能改变limit
:界限,表示缓冲区中可以操作数据的大小(limit
后数据不能进行读写)position
:位置,表示缓冲区中正在操作数据的位置mark
:标记,表示记录当前position
的值,可以通过reset()
方法,将position
的值恢复到mark
三者的大小关系:0 <= mark
<= position
<=limit
<=capacity
1.4 Buffer的常用方法
方法 | 描述 |
---|---|
Buffer clear() | 清空缓冲区并返回对缓冲区的引用 |
Buffer flip() | 将缓冲区的界限设置为当前位置,并将当前位置重置为0 |
int capacity() | 返回Buffer 的capacity大小 |
boolean hasRemaining() | 判断缓冲区中是否还有元素 |
int limit() | 返回Buffer 的界限(limit) 的位置 |
Bufferlimit(int n) | 将设置缓冲区界限为n, 并返回一个具有新limit 的缓冲区对象 |
Buffer mark() | 对缓冲区设置标记 |
int position() | 返回缓冲区的当前位置position |
Buffer position(int n) | 将设置缓冲区的当前位置为n , 并返回修改后的Buffer 对象 |
int remaining() | 返回position 和limit 之间的元素个数 |
Buffer reset() | 将位置position 转到以前设置的mark 所在的位置 |
Buffer rewind() | 将位置设为为0,取消设置的mark |
package NIO;
import java.nio.ByteBuffer;
public class TestBuffer {
// 1.分配一个缓冲区
private static ByteBuffer buffer;
public static void main(String[] args) {
test03();
}
/*
* 基本操作
*/
public static void test01() {
// 运行结果
// -----------put()-----------
// position: 5
// limit: 1024
// capacity: 1024
// -----------flip()-----------
// position: 0
// limit: 5
// capacity: 1024
// -----------get()_前-----------
// position: 0
// limit: 5
// capacity: 1024
// abcde
// -----------get()_后-----------
// position: 5
// limit: 5
// capacity: 1024
// -----------rewind()-----------
// position: 0
// limit: 5
// capacity: 1024
// -----------clear()-----------
// position: 0
// limit: 1024
// capacity: 1024
buffer = ByteBuffer.allocate(1024);
// 向缓冲区写入数据
String str = "abcde";
buffer.put(str.getBytes());
showBufferInfo("put()");
// 从写入缓冲区模式转换为读取缓冲区数据模式
buffer.flip();
showBufferInfo("flip()");
// 从缓冲区中读取数据
byte[] dst = new byte[buffer.limit()];
showBufferInfo("get()_前");
buffer.get(dst);
System.out.println(new String(dst));
showBufferInfo("get()_后");
// 重新读取缓冲区中的数据
buffer.rewind();
showBufferInfo("rewind()");
// 清空缓冲区:但是缓冲区中的数据依然存在,但是是被遗忘状态
buffer.clear();
showBufferInfo("clear()");
}
/*
* 标志(mark)方法的使用
*/
public static void test02() {
// 运行结果
// -----------mark()-----------
// position: 2
// limit: 5
// capacity: 1024
// ab
// -----------mark()_再次读取两个字节后-----------
// position: 4
// limit: 5
// capacity: 1024
// -----------reset()_回退后-----------
// position: 2
// limit: 5
// capacity: 1024
// 1 创建缓冲区
buffer = ByteBuffer.allocate(1024);
String str = "abcde";
// 2 写入缓冲区数据
buffer.put(str.getBytes());
// 3 转换读写模式
buffer.flip();
// 4 获取数据
byte[] dst = new byte[buffer.limit()];
buffer.get(dst, 0, 2);
// 5 在position为2的时候设置断点
buffer.mark();
showBufferInfo("mark()");
System.out.println(new String(dst));
// 6 设置断点后,再次从缓冲区中读取两个字节的数据
buffer.get(dst, 2, 2);
System.out.println(new String(dst));
showBufferInfo("mark()_再次读取两个字节后");
// 7 回退到之前的2
buffer.reset();
showBufferInfo("reset()_回退后");
}
/*
* 其他方法
*/
public static void test03() {
buffer = ByteBuffer.allocate(1024);
String str = "abcde";
buffer.put(str.getBytes());
if (buffer.hasRemaining()) { // 缓冲区中剩余可使用空间
System.out.println(buffer.remaining()); // 打印可用空间数
}
}
public static void showBufferInfo(String methodName) {
System.out.println("-----------" + methodName + "-----------");
// 当前缓冲区可操作的位置
System.out.println("position: " + buffer.position());
// 当前缓冲区界限
System.out.println("limit: " + buffer.limit());
// 当前缓冲区容量
System.out.println("capacity: " + buffer.capacity());
}
}
2 直接缓冲区与非直接缓冲区
字节缓冲区要么是直接的,要么是非直接的。如果为直接字节缓冲区,则Java虚拟机会尽最大努力直接在此缓冲区上执行本机I/O操作。也就是说,在每次调用基础操作系统的一个本机I/O操作之前(或之后),虚拟机都会尽量避免将缓冲区的内容复制到中间缓冲区中(或从中间缓冲区中复制内容)。
直接字节缓冲区可以通过调用此类的allocateDirect()
工厂方法来创建。此方法返回的缓冲区进行分配和取消分配所需成本通常高于非直接缓冲区。直接缓冲区的内容可以驻留在常规的垃圾回收堆之外,因此,它们对应用程序的内存需求量造成的影响可能并不明显。所以,建议将直接缓冲区主要分配给那些易受基础系统的本机I/O操作影响的大型、持久的缓冲区。一般情况下,最好仅在直接缓冲区能在程序性能方面带来明显好处时分配它们。
直接字节缓冲区还可以通过FileChannel
的map()
方法将文件区域直接映射到内存中来创建。该方法返回MappedByteBuffer
。Java平台的实现有助于通过JNI从本机代码创建直接字节缓冲区。如果以上这些缓冲区中的某个缓冲区实例指的是不可访问的内存区域,则试图访问该区域不会更改该缓冲区的内容,并且将会在访问期间或稍后的某个时间导致抛出不确定的异常。
字节缓冲区是直接缓冲区还是非直接缓冲区可通过调用其isDirect()
方法来确定。提供此方法是为了能够在性能关键型代码中执行显式缓冲区管理。
- 直接缓冲区:通过
allocateDirect()
方法进行直接缓冲区的创建,将缓冲区建立在物理内存上,提高了效率。但是不安全,对资源消耗较大,且写入物理内存映射文件后,应用程序无法再对写入的数据进行管控,何时写入物理磁盘,何时销毁,这些情况都无法确定。
- 非直接缓冲区:通过
allocate()
方法分配缓冲区,将缓冲区建立在JVM内存中