/*
* 功能描述:
分析 option limit capacity的变化
* 〈〉
* @Param: []
* @Return: void
* @Author: LeoLee
* @Date: 2020/9/18 11:19
*/
public static void testIntBuffer () {
IntBuffer intBuffer = IntBuffer.allocate(10);
System.out.println("init buffer:");
System.out.println("limit:" + intBuffer.limit());
System.out.println("position:" + intBuffer.position());
System.out.println("capacity:" + intBuffer.capacity());
for (int i = 0; i < 5; i++) {
int randomNumber = new SecureRandom().nextInt(20);
intBuffer.put(randomNumber);
System.out.println("put data into buffer:");
System.out.println("limit:" + intBuffer.limit());
System.out.println("position:" + intBuffer.position());
System.out.println("capacity:" + intBuffer.capacity());
}
intBuffer.flip();
System.out.println("after flip():");
System.out.println("limit:" + intBuffer.limit());
System.out.println("position:" + intBuffer.position());
System.out.println("capacity:" + intBuffer.capacity());
while (intBuffer.hasRemaining()){
System.out.println(intBuffer.get());
}
System.out.println("after get():");
System.out.println("limit:" + intBuffer.limit());
System.out.println("position:" + intBuffer.position());
System.out.println("capacity:" + intBuffer.capacity());
}
由 testIntBuffer() 示例可知:
allocate(n) 初始化源码如下:
/**
* Allocates a new int buffer.
*
* The new buffer's position will be zero, its limit will be its
* capacity, its mark will be undefined, and each of its elements will be
* initialized to zero. It will have a {@link #array backing array},
* and its {@link #arrayOffset array offset} will be zero.
*
* @param capacity
* The new buffer's capacity, in ints
*
* @return The new int buffer
*
* @throws IllegalArgumentException
* If the capacity is a negative integer
*/
public static IntBuffer allocate(int capacity) {
if (capacity < 0)
throw new IllegalArgumentException();
return new HeapIntBuffer(capacity, capacity);
}
// For speed these fields are actually declared in X-Buffer;
// these declarations are here as documentation
/*
protected final int[] hb;
protected final int offset;
*/
HeapIntBuffer(int cap, int lim) { // package-private
super(-1, 0, lim, cap, new int[cap], 0);
/*
hb = new int[cap];
offset = 0;
*/
}
可以看到返回了一个 HeapIntBuffer 对象,该 HeapIntBuffer 构造方法接收 capacity 和 limit 两个参数,初始化的时候,limit = capacity,并且调用了父类 IntBuffer 的构造方法,从该构造方法可以得出,IntBuffer 底层实质上是一个数组。
/*
* 功能描述:
ByteBuffer提供的基本数据类型存放
* 〈〉
* @Param: []
* @Return: void
* @Author: LeoLee
* @Date: 2020/9/18 11:34
*/
public static void testByteBuffer () {
ByteBuffer byteBuffer = ByteBuffer.allocate(64);
byteBuffer.putShort((short) 3213);
byteBuffer.putInt(1);
byteBuffer.putLong(112343242312312L);
byteBuffer.putChar('你');
byteBuffer.putFloat(12.232f);
byteBuffer.putDouble(34.131d);
byteBuffer.flip();
System.out.println(byteBuffer.getShort());
System.out.println(byteBuffer.getInt());
System.out.println(byteBuffer.getLong());
System.out.println(byteBuffer.getChar());
System.out.println(byteBuffer.getFloat());
System.out.println(byteBuffer.getDouble());
}
/*
* 功能描述:
验证buffer slice,切片buffer
* 〈slice产生的新buffer拥有独立的option limit capacity,新的buffer的元素改变会影响源buffer,同样源buffer的改变也会影响新的buffer〉
* 原因就是sliceBuffer和源buffer共享底层的数组
* @Param: []
* @Return: void
* @Author: LeoLee
* @Date: 2020/9/18 13:09
*/
public static void testBufferSlice () {
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
for (int i = 0; i < byteBuffer.capacity(); i++) {
byteBuffer.put((byte) i);
}
System.out.println("---------------验证新buffer元素改变对源buffer的影响----------------");
byteBuffer.position(4);
byteBuffer.limit(6);
ByteBuffer sliceBuffer1 = byteBuffer.slice();
for (int i = 0; i < sliceBuffer1.capacity(); i++) {
sliceBuffer1.put(i, (byte) (sliceBuffer1.get(i) * 2));
}
byteBuffer.clear();//等价于byteBuffer.position(0); byteBuffer.limit(10);
while (byteBuffer.hasRemaining()) {
System.out.println(byteBuffer.get());
}
System.out.println("---------------验证源buffer元素改变对新buffer的影响----------------");
byteBuffer.position(4);
byteBuffer.limit(6);
ByteBuffer sliceBuffer2 = byteBuffer.slice();
System.out.println("源buffer改变前:");
while (sliceBuffer2.hasRemaining()) {
System.out.println(sliceBuffer2.get());
}
byteBuffer.put(4, (byte) 7);
byteBuffer.put(5, (byte) 8);
sliceBuffer2 = byteBuffer.slice();
System.out.println("源buffer改变后:");
while (sliceBuffer2.hasRemaining()) {
System.out.println(sliceBuffer2.get());
}
}
由示例中 testBufferSlice() 方法运行结果可知:
slice() 源码如下:
public ByteBuffer slice() {
return new HeapByteBuffer(hb,
-1,
0,
this.remaining(),
this.remaining(),
this.position() + offset);
}
/**
* Returns the number of elements between the current position and the
* limit.
*
* @return The number of elements remaining in this buffer
*/
public final int remaining() {
return limit - position;
}
protected HeapByteBuffer(byte[] buf,
int mark, int pos, int lim, int cap,
int off)
{
super(mark, pos, lim, cap, buf, off);
/*
hb = buf;
offset = off;
*/
}
// These fields are declared here rather than in Heap-X-Buffer in order to
// reduce the number of virtual method invocations needed to access these
// values, which is especially costly when coding small buffers.
//
final byte[] hb; // Non-null only for heap buffers
final int offset;
boolean isReadOnly; // Valid only for heap buffers
// Creates a new buffer with the given mark, position, limit, capacity,
// backing array, and array offset
//
ByteBuffer(int mark, int pos, int lim, int cap, // package-private
byte[] hb, int offset)
{
super(mark, pos, lim, cap);
this.hb = hb;
this.offset = offset;
}
由源码可以看出:
/*
* 功能描述:
只读buffer
* 〈〉一个普通的buffer可以调用asReadOnlyBuffer()方法返回一个只读buffer对象。此对象不可逆
* 任何对只读buffer的内容操作,都会返回一个ReadOnlyBufferException
* @Param: []
* @Return: void
* @Author: LeoLee
* @Date: 2020/9/18 14:35
*/
public static void onlyReadBuffer () {
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
System.out.println(byteBuffer.getClass());
ByteBuffer readOnlyBuf = byteBuffer.asReadOnlyBuffer();
System.out.println(readOnlyBuf.getClass());
}
protected HeapByteBufferR(byte[] buf,
int mark, int pos, int lim, int cap,
int off)
{
super(buf, mark, pos, lim, cap, off);
this.isReadOnly = true;
}
public ByteBuffer put(byte x) {
throw new ReadOnlyBufferException();
}
由源码可知:
/*
* 功能描述:
测试直接缓冲buffer
* 〈〉
* @Param: []
* @Return: void
* @Author: LeoLee
* @Date: 2020/9/19 19:51
*/
public static void testDirectBuffer () throws IOException {
FileInputStream fileInputStream = new FileInputStream("file1.txt");
FileOutputStream fileOutputStream = new FileOutputStream("file2.txt");
FileChannel inputChannel = fileInputStream.getChannel();
FileChannel outputChannel = fileOutputStream.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(3);
while (true) {
byteBuffer.clear();
int i = inputChannel.read(byteBuffer);
if (i == -1) {
break;
}
byteBuffer.flip();
outputChannel.write(byteBuffer);
}
inputChannel.close();
outputChannel.close();
fileInputStream.close();
fileOutputStream.close();
}
Java NIO引入了用于通道的缓冲区的ByteBuffer。 ByteBuffer有三个主要的实现:
HeapByteBuffer
在调用ByteBuffer.allocate()时使用。 它被称为堆,因为它保存在JVM的堆空间中,因此你可以获得所有优势,如GC支持和缓存优化。 但是,它不是页面对齐的,这意味着如果你需要通过JNI与本地代码交谈,JVM将不得不复制到对齐的缓冲区空间。
DirectByteBuffer
在调用ByteBuffer.allocateDirect()时使用。 JVM将使用malloc()在堆空间之外分配内存空间。 因为它不是由JVM管理的,所以你的内存空间是页面对齐的,不受GC影响,这使得它成为处理本地代码的完美选择。 然而,你要C程序员一样,自己管理这个内存,必须自己分配和释放内存来防止内存泄漏。
说人话,就是 DirectByteBuffer 的操作方法底层都是调用了 native 修饰的方法(和计算机系统交互的方法),对JVM意外的内存空间直接操作,不需要将JVM堆中的独享内存,复制到系统内存中,再进行操作。
这就是为什么很多网络变成的框架底层使用NIO的原因!
// Primary constructor
//
DirectByteBuffer(int cap) { // package-private
super(-1, 0, cap, cap);
boolean pa = VM.isDirectMemoryPageAligned();
int ps = Bits.pageSize();
long size = Math.max(1L, (long)cap + (pa ? ps : 0));
Bits.reserveMemory(size, cap);
long base = 0;
try {
base = unsafe.allocateMemory(size);
} catch (OutOfMemoryError x) {
Bits.unreserveMemory(size, cap);
throw x;
}
unsafe.setMemory(base, size, (byte) 0);
if (pa && (base % ps != 0)) {
// Round up to page boundary
address = base + ps - (base & (ps - 1));
} else {
address = base;
}
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
att = null;
}
MappedByteBuffer
在调用FileChannel.map()时使用。 与DirectByteBuffer类似,这也是JVM堆外部的情况。 它基本上作为OS mmap()系统调用的包装函数,以便代码直接操作映射的物理内存数据。