ByteBuffer 不仅仅存放Byte类型 还能存放其他类型,并且必须按照对应的顺序解析 否则将报错。(类似于自定义协议,按照规则去解析)
/**
* ByteBuffer类型化的put 与 get
*/
public class NioTest5 {
public static void main(String[] args) {
ByteBuffer byteBuffer = ByteBuffer.allocate(10000);
byteBuffer.putInt(15);
byteBuffer.putLong(50000000L);
byteBuffer.putChar('哈');
byteBuffer.flip();
System.out.println(byteBuffer.getInt());
System.out.println(byteBuffer.getLong());
System.out.println(byteBuffer.getChar());
}
}
打印结果:
15
50000000
哈
/** * Creates a new byte buffer whose content is a shared subsequence of * this buffer's content. * * <p> The content of the new buffer will start at this buffer's current * position. Changes to this buffer's content will be visible in the new * buffer, and vice versa; the two buffers' position, limit, and mark * values will be independent. * * <p> The new buffer's position will be zero, its capacity and its limit * will be the number of bytes remaining in this buffer, and its mark * will be undefined. The new buffer will be direct if, and only if, this * buffer is direct, and it will be read-only if, and only if, this buffer * is read-only. </p> * * @return The new byte buffer */ public abstract ByteBuffer slice();
通过slice 生成的新的buffer 与老的buffer 共享buffer内容
package nio;
import java.nio.ByteBuffer;
/**
* Slice Buffer
*/
public class NioTest6
{
public static void main(String[] args) {
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
for (int i = 0; i < byteBuffer.capacity(); i++) {
byteBuffer.put((byte)i);
}
byteBuffer.position(2);
byteBuffer.limit(6);
ByteBuffer sliceBuffer = byteBuffer.slice();
for (int i = 0; i <sliceBuffer.capacity() ; i++) {
byte b = sliceBuffer.get(i);
b *=2;
sliceBuffer.put(i,b);
}
byteBuffer.position(0);
byteBuffer.limit(byteBuffer.capacity());
while (byteBuffer.hasRemaining()){
System.out.println(byteBuffer.get());
}
}
}
打印结果:
0
1
4
6
8
10
6
7
8
9
下面在看只读buffer,为什么使用只读buffer 因为与客户端交换数据时,防止客户端修改buffer,为调用端提供数据的读取
/**
* 只读buffer,我们可以随时将一个普通buffer调用asReadOnlyBuffer 方法返回一个只读buffer,但不能将一个只读Buffer转换成读写buffer
* @author lm
*
*/
public class NioTest {
/**
* @param args
*/
public static void main(String[] args) {
ByteBuffer buffer = ByteBuffer.allocate(10);
for (int i = 0; i < buffer.capacity(); i++) {
buffer.put((byte)i);
}
ByteBuffer readOnlyBuffer =buffer.asReadOnlyBuffer();
System.out.println(readOnlyBuffer.getClass());
readOnlyBuffer.position(0);
readOnlyBuffer.put((byte)2);
}
}
打印结果:class java.nio.HeapByteBufferR
Exception in thread "main" java.nio.ReadOnlyBufferException
at java.nio.HeapByteBufferR.put(HeapByteBufferR.java:166)
at com.nio.NioTest.main(NioTest.java:24)
我们分析一下 查看ByteBuffer源码 ByteBuffer。allocate 方法返回的是HeapByteBuffer
/**
* Allocates a new byte buffer.
*
* <p> 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 bytes
*
* @return The new byte buffer
*
* @throws IllegalArgumentException
* If the <tt>capacity</tt> is a negative integer
*/
public static ByteBuffer allocate(int capacity) {
if (capacity < 0)
throw new IllegalArgumentException();
return new HeapByteBuffer(capacity, capacity);
}
asReadOnlyBuffer 是一个抽象方法
解释一下注释:创建一个新的只读buffer 与之前的buffer共享 它的内容 新的只读buffer内容是不能修改的,并且两个buffer的limti,position,mark都是独立的
/**
* Creates a new, read-only byte buffer that shares this buffer's
* content.
*
* <p> The content of the new buffer will be that of this buffer. Changes
* to this buffer's content will be visible in the new buffer; the new
* buffer itself, however, will be read-only and will not allow the shared
* content to be modified. The two buffers' position, limit, and mark
* values will be independent.
*
* <p> The new buffer's capacity, limit, position, and mark values will be
* identical to those of this buffer.
*
* <p> If this buffer is itself read-only then this method behaves in
* exactly the same way as the {@link #duplicate duplicate} method. </p>
*
* @return The new, read-only byte buffer
*/
public abstract ByteBuffer asReadOnlyBuffer();
再看HeapByteBuffer 的asReadOnlyBuffer 方法 返回的是 HeapByteBufferR 它是一个只读R表示只读
public ByteBuffer asReadOnlyBuffer() {
return new HeapByteBufferR(hb,
this.markValue(),
this.position(),
this.limit(),
this.capacity(),
offset);
}
再看 HeapByteBufferR put方法 因为是只读的所以直接抛异常 就可以解释 上面的代码为什么抛异常
public ByteBuffer putChar(int i, char x) {
throw new ReadOnlyBufferException();
}
// wrap方法会包裹一个数组: 一般这种用法不会先初始化缓存对象的长度,因为没有意义,最后还会被wrap所包裹的数组覆盖掉。
// 并且wrap方法修改缓冲区对象的时候,数组本身也会跟着发生变化。
int[] arr = new int[]{1,2,5};
IntBuffer buf1 = IntBuffer.wrap(arr);
System.out.println(buf1);
IntBuffer buf2 = IntBuffer.wrap(arr, 0 , 2);//截取1-2下标
//这样使用表示容量为数组arr的长度,但是可操作的元素只有实际进入缓存区的元素长度
System.out.println(buf2);
java.nio.HeapIntBuffer[pos=0 lim=3 cap=3]
java.nio.HeapIntBuffer[pos=0 lim=2 cap=3]
下面我们看一下直接缓冲buffer
public class NioTest2 {
/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
FileInputStream fielInput = new FileInputStream("input.txt");
FileOutputStream fielOutput = new FileOutputStream("output.txt");
FileChannel inputChannel =fielInput.getChannel();
FileChannel outChannel =fielOutput.getChannel();
//直接缓冲
ByteBuffer buffer = ByteBuffer.allocateDirect(10);
while (true) {
buffer.clear();
int read =inputChannel.read(buffer);
System.out.println("read:"+read);
if(-1==read){
break;
}
buffer.flip();
outChannel.write(buffer);
}
inputChannel.close();
outChannel.close();
}
}
我们看一下 ByteBuffer.allocateDirect(10); 源码
注释:分配一个直接缓冲btyebuffer 其他的和之前一样 不进行解释le
/**
* Allocates a new direct byte buffer.
*
* <p> 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. Whether or not it has a
* {@link #hasArray backing array} is unspecified.
*
* @param capacity
* The new buffer's capacity, in bytes
*
* @return The new byte buffer
*
* @throws IllegalArgumentException
* If the <tt>capacity</tt> is a negative integer
*/
public static ByteBuffer allocateDirect(int capacity) {
return new DirectByteBuffer(capacity);
}
再看DirectByteBuffer 里面 有很多sun公司 提供的未公开的方法
// 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;
}
之前的HeapByteBuffer 都是由java 来维护底层数组 来操作数据的 都是jvm来可以操作也就是堆内存,
为什么使用堆外内存 由于DirectByteBuffer 通过父类的address 是一个直接内存 来标记堆外内存数据的地址
进行IO操作的时候 会直接在堆外内存中(是有操作系统来管理的) 读取数据 提高速度
// Used only by direct buffers
// NOTE: hoisted here for speed in JNI GetDirectBufferAddress
long address;
而我们使用HeapByteBuffer 来进行IO操作的时候 需要把JAVA内存中的数据 拷贝到 操作系统的内存中进行操作,相比DirectByteBuffer 多了一步数据拷贝过程。
其实我们的操作系统可以直接操作JAVA内存模型中的数据,但是JVM中有GC回收,会导致数据问题等,所有为了快速进行零拷贝,才把数据拷贝到堆外的操作系统中。