Buffer
Buffer到底是什么?从JDK源码文档中,可以一窥究竟:
A container for data of a specific primitive type.
Buffer是某种基本类型数据的容器。
A buffer is a linear, finite sequence of elements of a specific primitive type.
Buffer是某种基本类型元素的线性有限序列。
关键字:线性容器、容量固定、元素必须是基本数据类型。(这不就是数组么!)
在NIO语境中,Buffer是这样的:
A buffer is an object that stores a fixed amount of data to be sent to or received from an I/O service .
简单点,Buffer是应用程序和Channels读写数据的媒介。
所有Buffer都继承抽象类Buffer,它定义了所有Buffer共同的属性和方法。除了Boolean之外,每种基本数据类型都有对应的Buffer类,如ByteBuffer、IntBuffer、CharBuffer等等。
Buffer四大属性
- capacity:可存储的元素个数,非负,不可变
- limit:第一个不可读/写元素的位置,非负,小于等于capacity
- position:下一个可读/写元素的位置(可以类比游标,代表当前访问位置),非负,小于等于limit
- mark:调用reset()方法后position的位置,Buffer初始化的时候是未定义mark的。
0 <= mark <= position <= limit <= capacity
创建Buffer
由于IO操作最终都是基于Byte的,所以ByteBuffer与其它Buffer大有不同。
ByteBuffer
ByteBuffer的创建主要有两种形式:allocate和wrap
allocate方法创建ByteBuffer并为它分配内存,元素初始化为0:
ByteBuffer allocate(int capacity);//backing array
ByteBuffer allocateDirect(int capacity);//direct byte buffer,Whether or not it has a backing array is unspecified.
wrap方法将现有的字节数组包装成一个ByteBuffer,所以这个数组就是它的支撑数组(backing array)。它们是绑定的,对Buffer对象的修改会影响到backing array,反之亦然。
ByteBuffer wrap(byte[] array); //position=0,limit=capacity=array.length
ByteBuffer wrap(byte[] array, int offset, int length); //capacity=array.length,position=offset, limit=offset+capacity
View Buffers
其它Buffers都不能直接IO,它们本质上都是按ByteBuffer存储的,只是为了方便我们使用基本数据类型操作Buffer,所以可以理解为是ByteBuffer的“视图”。
View Buffers意思就是视图Buffer,视图Buffer由原Buffer复制而来,它们的内容也是公用的,但拥有独立的属性。
ByteBuffer buffer = ByteBuffer.allocate(10);
ByteBuffer bufferView = buffer.duplicate();
CharBuffer charBuffer = buffer.asCharBuffer();
Buffer读写
Buffer读写分为两种:绝对读写和相对读写。
带index参数的为绝对读写,例如:
ByteBuffer put(int index, byte b) ;
byte get(int index);
不带index参数的为相对读写,例如:
ByteBuffer put(byte b) ;
byte get();
绝对读写基于传入index操作,不会改变position属性。
相对读写基于position属性操作,操作完之后position自增。
Clearing, flipping, and rewinding
方法 | 动作 | 用法 |
---|---|---|
clear() | limit=capacity,position=0 | 准备新一次的channel read或相对put |
flip() | limit=position(),position=0 | 准备新一次的channel write或相对get |
rewind() | limit不变,position=0 | 重复读 |
compact
compact()方法把从position到limit之间的元素移动到buffer开始位置。假设这之间有n个元素,全部移动完后,position=n, limit=capacity.
Invoke this method after writing data from a buffer in case the write was incomplete.
假设这种情况,通过一个buffer从in channel复制数据到out channel:
buf.clear(); // Prepare buffer for use
while (in.read(buf) >= 0 || buf.position != 0) {
buf.flip();
out.write(buf);
buf.compact(); // In case of partial write
}
Direct Byte Buffer
操作系统可以直接访问进程的地址空间。例如,操作系统可以直接访问JVM的地址空间(堆),基于字节数组来转移数据。然而JVM可能并不会连续地存储字节数组,并且GC也会移动这些内存(某些使用Compact的回收器)。
A byte buffer is either direct or non-direct.
A direct byte buffer is a byte buffer that interacts with channels and native code to perform I/O.
direct byte buffer是性能最高的,JVM会尽量直接执行操作系统的native IO,避免了JVM缓冲区复制到系统内核缓冲区,反之亦然。
当给channel传递一个non-direct byte buffer时,channel可能要先创建一个临时的direct byte buffer,把non-direct byte buffer的内容复制到这个临时direct byte buffer, 在这个临时direct byte buffer上执行IO操作,然后再把数据复制回non-direct byte buffer,最后这个临时的direct byte buffer可以被GC。
A direct byte buffer may be created by invoking the allocateDirect factory method of this class.
direct byte buffer的内存分配和回收代价都更高。它可能位于堆外内存,因此对JVM堆内存影响可能不明显,在分配内存时要特别注意。
因此建议直接内存只用于依赖于操作系统native IO的长期存活的大内存。只有当它们确实能很大提升性能的时候才决定使用。
A direct byte buffer may also be created by mapping a region of a file directly into memory.
JVM实现中,可能会使用JNI来分配direct byte buffer,如果这些类型的缓冲区之一的实例引用了一个不可访问的内存区域,那么访问该区域的尝试将不会更改缓冲区的内容,并且会导致在访问时或之后某个时间抛出未指定的异常。
Byte Ordering
Java中基本数据类型字节长度:
- byte和boolean:1个字节
- char和short:2个字节(所以char是可以存一个中文字的)
- int和float:4个字节
- long和double:8个字节
在计算机中,数据以二进制01序列表示,1个字节占8位,两个字节16位 …
计算机用一个字节(8位)作为最小寻址单元。
内存中每一个字节都有一个唯一标识,叫做地址
所有可能的地址集合叫做地址空间
对于单字节数据类型,所以可以存储在一个地址单元里。
而对于多字节数据类型,就要存储在多个连续的地址单元里,并且最小的地址单元作为数据的地址。
这种情况下,就涉及到字节序(byte order)的问题,不同的系统可能会采用不同的字节序。
- 大端(big endian):低字节优先,即它的地址是低8位所在的地址
- 小端(little endian):高字节优先,即它的地址是高8位所在的地址
例如,int num=0x01234567,它在内存中可能是这样的:
byte order问题在一般开发情况下可以不用理会,但如果通过网络IO相互通信时,就必须在通信协议中考虑两个系统byte order不一致的情况!
java.nio.ByteOrder 类提供了处理byte order相关问题的支持。
ByteOrder nativeOrder();//返回操作系统使用的字节序,ByteOrder.BIG_ENDIAN或ByteOrder.LITTLE_ENDIAN
每种多字节类型对应的Buffer都提供了order()方法,返回这个Buffer使用的byte order。
ByteOrder order();
如果是allocate或wrap创建的buffer,byte order是操作系统默认。
如果是view buffer,byte order是原始的buffer的byte order,且view buffer的byte order不可改变。
ByteBuffer则不同,它们的默认byte order是大端的。
因为Java默认使用的byte order是大端的,ByteBuffer与之保持一致的话方便类加载和序列化等操作。
为了适应小端系统,ByteBuffer提供了order方法改变byte order:
ByteBuffer order(ByteOrder bo);