上一篇《Java输入输出系统之nio(一)通道和缓冲器》学习了通道和缓冲器的使用以及缓冲器的底层原理,本篇文章将学习缓冲器基本数据类型存取、视图缓冲器和字节序。
缓冲器基本数据类型存取
ByteBuffer除了可以存取字节以外还可以存取基本数据类型,存取基本数据类型的方法有:
public ByteBuffer putChar(char value);
public char getChar();
public ByteBuffer putShort(short value);
public short getShort();
public ByteBuffer putInt(int value);
public int getInt();
public ByteBuffer putLong(long value);
public long getLong();
public ByteBuffer putFloat(float value);
public float getFloat();
public ByteBuffer putDouble(double value);
public double getDouble();
使用示例如下:
ByteBuffer bb = ByteBuffer.allocate(512);
bb.putChar('O');
bb.putInt(1024);
bb.putDouble(10.24);
bb.flip();
System.out.println("getChar: " + bb.getChar());
System.out.println("getInt: " + bb.getInt());
System.out.println("getDouble: " + bb.getDouble());
/*输出:
getChar: O
getInt: 1024
getDouble: 10.24
上面的示例代码举例说明了char、int、double数据类型的存取,其他的基本数据类型的存取方法也是一样的。有时候需要存取一组基本数据类型,比如一个int数组,ByteBuffer没有提供直接存取一组基本数据类型的方法,只能通过循环语句一个一个的添加。
视图缓冲器
缓冲器除了ByteBuffer以外还有以下几种类型:
- CharBuffer
- ShortBuffer
- IntBuffer
- LongBuffer
- FloatBuffer
- DoubleBuffer
上面的缓冲器可以很方便的存取基本数据类型,比如可以存取一组基本数据类型。视图缓冲器是指把ByteBuffer转换为其它的缓冲器,使用其它缓冲器的方法来存取数据,但是底层的数据存储还是ByteBuffer,对视图缓冲器的任何修改都会反映在ByteBuffer上。视图缓冲器类似数据库的视图,它提供了另外的一种视角来展现数据。ByteBuffer转换为视图缓冲器的方法如下:
public CharBuffer asCharBuffer();
public ShortBuffer asShortBuffer();
public IntBuffer asIntBuffer();
public LongBuffer asLongBuffer();
public FloatBuffer asFloatBuffer();
public DoubleBuffer asDoubleBuffer();
ByteBuffer虽然可以存取基本数据类型,但是一次只能存取一个,有了视图缓冲器以后就可以存储一组基本数据类型了,存取示例如下:
ByteBuffer bb = ByteBuffer.allocate(512);
IntBuffer ib = bb.asIntBuffer();
ib.put(new int[] {1,2,3,4,5,6,7});
int[] array = new int[7];
ib.flip();
ib.get(array);
for (int i: array) {
System.out.println(i);
}
/*输出:
1
2
3
4
5
6
7
*/
当ByteBuffer转换为视图缓冲器时内部成员变量position、limit、capacity是怎么转换的呢?看下面的代码:
ByteBuffer bb = ByteBuffer.allocate(512);
bb.putInt(10);
System.out.println(String.format("ByteBuffer init: position = %d limit = %d capacity = %d",
bb.position(),
bb.limit(),
bb.capacity()));
IntBuffer ib = bb.asIntBuffer();
System.out.println(String.format("IntBuffer init: position = %d limit = %d capacity = %d",
ib.position(),
ib.limit(),
ib.capacity()));
ib.put(new int [] {1,2,3,4});
System.out.println(String.format("ByteBuffer last: position = %d limit = %d capacity = %d",
bb.position(),
bb.limit(),
bb.capacity()));
/*输出:
ByteBuffer init: position = 4 limit = 512 capacity = 512
IntBuffer init: position = 0 limit = 127 capacity = 127
ByteBuffer last: position = 4 limit = 512 capacity = 512
*/
从程序输出可以看到,ByteBuffer转换为IntBuffer时IntBuffer的limit和capacity值为ByteBuffer的剩余容量除以4,本例中为 (512 - 4) / 4 = 127。而position被设置为0,其对应ByteBuffer的位置4,见下图:
使用IntBuffer存储一组数据以后,ByteBuffer的成员变量并没有发生变化,这是因为ByteBuffer和IntBuffer的内部成员变量是相互独立变化的,例如,往ByteBuffer添加数据会改变它的position,但是不会改变它的视图缓冲器的position,反过来往视图缓冲器添加数据会改变它的position但是不会改变ByteBuffer的position。
字节序
多字节数据类型在内存中的字节存储顺序叫作字节序。例如Int型变量需要使用4个字节表示,变量1的16进制为0x00000001,占用的4个字节从高到低为分别为0x00 0x00 0x00 0x01。要把1存储在内存中有两种存放方式:先存低字节和先存高字节,先存低字节的方式称为小端存储,先存高字节的的方式称为大端存储或者网络字节序。小端存储的低字节对应内存的低地址,高字节对应高地址。大端存储的高字节对应内存的低地址,低字节对应内存的高地址。下图是大小端存储示例:
ByteBuffer可以设置字节序,默认的字节序是大端,可以通过order方法来设置和获取字节序:
//获取字节序
public ByteOrder order();
//设置字节序
public ByteBuffer order(ByteOrder bo);
下面是字节序查询和设置示例:
teBuffer bb = ByteBuffer.allocate(512);
System.out.println(bb.order());
bb.order(ByteOrder.LITTLE_ENDIAN);
System.out.println(bb.order());
/*输出:
BIG_ENDIAN
LITTLE_ENDIAN
*/
下面是大小端字节序对ByteBuffer的影响示例:
ByteBuffer bb = ByteBuffer.allocate(512);
bb.putInt(1);
bb.flip();
System.out.print(bb.order());
System.out.print(": ");
while(bb.hasRemaining()) {
System.out.print(bb.get());
System.out.print(" ");
}
System.out.println();
bb.clear();
bb.order(ByteOrder.LITTLE_ENDIAN);
bb.putInt(1);
bb.flip();
System.out.print(bb.order());
System.out.print(": ");
while(bb.hasRemaining()) {
System.out.print(bb.get());
System.out.print(" ");
}
/*输出:
BIG_ENDIAN: 0 0 0 1
LITTLE_ENDIAN: 1 0 0 0
*/
上例中,当使用大端字节序时优先存储高字节即高字节对应低地址,所以大端的输出为0 0 0 1。当使用小端字节序时优先存储低字节即低字节对应低地址,所以小端的输出为1 0 0 0。
ByteBuffer的字节序在读写二进程数据时非常有用,例如从磁盘读取一个wav格式的文件,wav文件是按照小端字节序存储数据的,读写wav文件时只需把ByteBuffer指定为小端字节序即可。
最后
使用通道和缓冲器读写文件具有很高的效率,只有理解了缓冲器的基本原理才能正确使用缓冲器。ByteBuffer是直接与通道交互的缓冲器,它可以转换为视图缓冲器来批量存储和读取基本数据类型。通过设置字节序,缓冲器可以读写特点格式的二进制文件。