NIO与Socket编程技术指南
上QQ阅读APP看书,第一时间看更新

1.4 ByteBuffer类的使用

ByteBuffer类是Buffer类的子类,可以在缓冲区中以字节为单位对数据进行存取,而且它也是比较常用和重要的缓冲区类。在使用NIO技术时,有很大的概率使用ByteBuffer类来进行数据的处理。

在前面的示例中已经使用过ByteBuffer类,该类的API列表如图1-19所示。

图1-19 ByteBuffer类的API列表

ByteBuffer类提供了6类操作。

1)以绝对位置和相对位置读写单个字节的get()和put()方法。

2)使用相对批量get(byte[] dst)方法可以将缓冲区中的连续字节传输到byte[] dst目标数组中。

3)使用相对批量put(byte[] src)方法可以将byte[]数组或其他字节缓冲区中的连续字节存储到此缓冲区中。

4)使用绝对和相对getType和putType方法可以按照字节顺序在字节序列中读写其他基本数据类型的值,方法getType和putType可以进行数据类型的自动转换。

5)提供了创建视图缓冲区的方法,这些方法允许将字节缓冲区视为包含其他基本类型值的缓冲区,这些方法有asCharBuffer()、asDoubleBuffer()、asFloatBuffer()、asIntBuffer()、asLongBuffer()和asShortBuffer()。

6)提供了对字节缓冲区进行压缩(compacting)、复制(duplicating)和截取(slicing)的方法。

字节缓冲区可以通过allocation()方法创建,此方法为缓冲区的内容分配空间,或者通过wrapping方法将现有的byte[]数组包装到缓冲区中来创建。

本节将把ByteBuffer类所有API的功能进行展示,以促进对该类的学习与掌握。

1.4.1 创建堆缓冲区与直接缓冲区

字节缓冲区分为直接字节缓冲区与非直接字节缓冲区。

如果字节缓冲区为直接字节缓冲区,则JVM会尽量在直接字节缓冲区上执行本机I/O操作,也就是直接对内核空间进行访问,以提高运行效率。提高运行效率的原理就是在每次调用基于操作系统的I/O操作之前或之后,JVM都会尽量避免将缓冲区的内容复制到中间缓冲区中,或者从中间缓冲区中复制内容,这样就节省了一个步骤。

工厂方法allocateDirect()可以创建直接字节缓冲区,通过工厂方法allocateDirect()返回的缓冲区进行内存的分配和释放所需的时间成本通常要高于非直接缓冲区。直接缓冲区操作的数据不在JVM堆中,而是在内核空间中,根据这个结构可以分析出,直接缓冲区善于保存那些易受操作系统本机I/O操作影响的大量、长时间保存的数据。

allocateDirect(int capacity)方法的作用:分配新的直接字节缓冲区。新缓冲区的位置将为零,其界限将为其容量,其标记是不确定的。无论它是否具有底层实现数组,其标记都是不确定的。

allocate(int capacity)方法的作用:分配一个新的非直接字节缓冲区。新缓冲区的位置为零,其界限将为其容量,其标记是不确定的。它将具有一个底层实现数组,且其数组偏移量将为零。

在JDK中,可以查看一下allocate()方法的源代码,从中会发现其会创建一个新的数组,而wrap()方法是使用传入的数组作为存储空间,说明对wrap()关联的数组进行操作会影响到缓冲区中的数据,而操作缓冲区中的数据也会影响到与wrap()关联的数组中的数据,原理其实就是引用同一个数组对象。

示例代码如下:

public class Test1 {
public static void main(String[] args) {
    ByteBuffer bytebuffer1 = ByteBuffer.allocateDirect(100);
    ByteBuffer bytebuffer2 = ByteBuffer.allocate(200);
    System.out.println("bytebuffer1 position=" + bytebuffer1.position() +
        " limit=" + bytebuffer1.limit());
    System.out.println("bytebuffer2 position=" + bytebuffer2.position() +
        " limit=" + bytebuffer2.limit());
    System.out.println("bytebuffer1=" + bytebuffer1 + " isDirect=" + bytebuffer1.
        isDirect());
    System.out.println("bytebuffer2=" + bytebuffer2 + " isDirect=" + bytebuffer2.
        isDirect());
}
}

程序运行结果如下:

bytebuffer1 position=0 limit=100
bytebuffer2 position=0 limit=200
bytebuffer1=java.nio.DirectByteBuffer[pos=0 lim=100 cap=100] isDirect=true
bytebuffer2=java.nio.HeapByteBuffer[pos=0 lim=200 cap=200] isDirect=false

使用allocateDirect()方法创建出来的缓冲区类型为DirectByteBuffer,使用allocate()方法创建出来的缓冲区类型为HeapByteBuffer。

使用allocateDirect()方法创建ByteBuffer缓冲区时,capacity指的是字节的个数,而创建IntBuffer缓冲区时,capacity指的是int值的数目,如果要转换成字节,则capacity的值要乘以4,来算出占用的总字节数。

使用allocateDirect()方法创建的直接缓冲区如何释放内存呢?有两种办法,一种是手动释放空间,另一种就是交给JVM进行处理。先来看第一种:手动释放空间,示例代码如下:

public class Test1 {
public static void main(String[] args) throws NoSuchMethodException, Security
    Exception, IllegalAccessException,
        IllegalArgumentException,  InvocationTargetException,  Interrupted
            Exception {
    System.out.println("A");
    ByteBuffer buffer = ByteBuffer.allocateDirect(Integer.MAX_VALUE);
    System.out.println("B");
    byte[] byteArray = new byte[] { 1 };
    System.out.println(Integer.MAX_VALUE);
    for (int i = 0; i < Integer.MAX_VALUE; i++) {
        buffer.put(byteArray);
    }
    System.out.println("put end! ");
    Thread.sleep(1000);
    Method cleanerMethod = buffer.getClass().getMethod("cleaner");
    cleanerMethod.setAccessible(true);
    Object returnValue = cleanerMethod.invoke(buffer);
    Method cleanMethod = returnValue.getClass().getMethod("clean");
    cleanMethod.setAccessible(true);
    cleanMethod.invoke(returnValue);
    // 此程序运行的效果就是1秒钟之后立即回收内存
    // 也就是回收“直接缓冲区”所占用的内存
}
}

程序运行后,可在“Windows任务管理器”的“性能”标签页的“物理内存”选项的“可用”节点中查看内存的使用情况。

另一种就是由JVM进行自动化的处理,示例代码如下:

public class Test2 {
public static void main(String[] args) throws NoSuchMethodException, Security
    Exception, IllegalAccessException,
        IllegalArgumentException, InvocationTargetException, InterruptedException {
    System.out.println("A");
    ByteBuffer buffer = ByteBuffer.allocateDirect(Integer.MAX_VALUE);
    System.out.println("B");
    byte[] byteArray = new byte[] { 1 };
    System.out.println(Integer.MAX_VALUE);
    for (int i = 0; i < Integer.MAX_VALUE; i++) {
        buffer.put(byteArray);
    }
    System.out.println("put end! ");
    // 此程序多次运行后,一直在耗费内存,
    // 进程结束后,也不会马上回收内存,
    // 而是会在某个时机触发GC垃圾回收器进行内存的回收
}
}

在Windows 7系统中出现的现象就是进程结束后,Windows 7并不立即回收内存,而是在某一个时机回收。

此*.java类可以运行多次,产生多个进程,然后再查看内存使用情况会更加直观。

1.4.2 直接缓冲区与非直接缓冲区的运行效率比较

直接缓冲区会直接作用于本地操作系统的I/O,处理数据的效率相比非直接缓冲区会快一些。

可以创建两者性能比较用的测试程序,如使用直接缓冲区来看看用时是多少,源代码如下:

public class Test1_2 {
public static void main(String[] args) {
    long beginTime = System.currentTimeMillis();
    ByteBuffer buffer = ByteBuffer.allocateDirect(1900000000);
    for (int i = 0; i < 1900000000; i++) {
        buffer.put((byte) 123);
    }
    long endTime = System.currentTimeMillis();
    System.out.println(endTime - beginTime);
}
}

程序运行结果如下:

1840

使用非直接缓冲区的测试代码如下:

public class Test1_3 {
public static void main(String[] args) {
    long beginTime = System.currentTimeMillis();
    ByteBuffer buffer = ByteBuffer.allocate(1900000000);
    for (int i = 0; i < 1900000000; i++) {
        buffer.put((byte) 123);
    }
    long endTime = System.currentTimeMillis();
    System.out.println(endTime - beginTime);
}
}

程序运行结果如下:

2309

从运行结果来看,直接缓冲区比非直接缓冲区在运行效率上要高一些,是什么原因造成这样的结果呢?直接缓冲区是使用DirectByteBuffer类进行实现的,而非直接缓冲区是使用HeapByteBuffer类进行实现的。直接缓冲区的实现类DirectByteBuffer的put(byte)方法的源代码如下:

public ByteBuffer put(byte x) {
    unsafe.putByte(ix(nextPutIndex()), ((x)));
    return this;
}

直接缓冲区(DirectByteBuffer)在内部使用sun.misc.Unsafe类进行值的处理。Unsafe类的作用是JVM与操作系统进行直接通信,提高程序运行的效率,但正如其类的名称Unsafe一样,该类在使用上并不是安全的,如果程序员使用不当,那么极有可能出现处理数据上的错误,因此,该类并没有公开化(public),仅由JDK内部使用。

而非直接缓冲区的实现类HeapByteBuffer的put(byte)方法的源代码如下:

public ByteBuffer put(byte x) {
    hb[ix(nextPutIndex())] = x;
    return this;
}

非直接缓冲区(HeapByteBuffer)在内部直接对byte[] hb字节数组进行操作,而且还是在JVM的堆中进行数据处理,因此运行效率相对慢一些。

1.4.3 包装wrap数据的处理

wrap(byte[] array)方法的作用:将byte数组包装到缓冲区中。新的缓冲区将由给定的byte数组支持,也就是说,缓冲区修改将导致数组修改,反之亦然。新缓冲区的capacity和limit将为array.length,其位置position将为0,其标记mark是不确定的。其底层实现数组将为给定数组,并且其arrayOffset将为0。

wrap(byte[] array, int offset, int length)方法的作用:将byte数组包装到缓冲区中。新的缓冲区将由给定的byte数组支持,也就是说,缓冲区修改将导致数组修改,反之亦然。新缓冲区的capacity将为array.length,其position将为offset,其limit将为offset + length,其标记是不确定的。其底层实现数组将为给定数组,并且其arrayOffset将为0。

相关参数的解释如下。

1)array:缓冲区中关联的字节数组。

2)offset:设置位置(position)值,该值必须为非负且不大于array.length。

3)length:将新缓冲区的界限设置为offset + length,该值必须为非负且不大于array. length-offset。

注意:wrap(byte[] array, int offset, int length)方法并不具有subString()方法截取的作用,它的参数offset只是设置缓冲区的position值,而length确定limit值。

示例代码如下:

public class Test2 {
public static void main(String[] args) {
    byte[] byteArray = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
    ByteBuffer bytebuffer1 = ByteBuffer.wrap(byteArray);
    ByteBuffer bytebuffer2 = ByteBuffer.wrap(byteArray, 2, 4);
    System.out.println("bytebuffer1 capacity=" + bytebuffer1.capacity() + "
    limit=" + bytebuffer1.limit()
            + " position=" + bytebuffer1.position());
    System.out.println();
    System.out.println("bytebuffer2 capacity=" + bytebuffer2.capacity() + "
    limit=" + bytebuffer2.limit()
            + " position=" + bytebuffer2.position());
}
}

程序运行结果如下:

bytebuffer1 capacity=8 limit=8 position=0
bytebuffer2 capacity=8 limit=6 position=2

Buffer类的每个子类都定义了两种get(读)和put(写)操作,分别对应相对位置操作和绝对位置操作。

相对位置操作是指在读取或写入一个或多个元素时,它从“当前位置开始”,然后将位置增加所传输的元素数。如果请求的传输超出限制,则相对get操作将抛出BufferUnder flowException异常,相对put操作将抛出BufferOverflowException异常,也就是说,在这两种情况下,都没有数据传输。

绝对位置操作采用显式元素索引,该操作不影响位置。如果索引参数超出限制,则绝对get操作和绝对put操作将抛出IndexOutOfBoundsException异常。

abstract ByteBuffer put(byte b)方法的作用:使用相对位置的put()操作,将给定的字节写入此缓冲区的“当前位置”,然后该位置递增。

abstract byte get()方法的作用:使用相对位置的get()操作,读取此缓冲区“当前位置”的字节,然后该位置递增。

示例代码如下:

public class Test3 {
public static void main(String[] args) {
    ByteBuffer buffer1 = ByteBuffer.allocate(10);
    System.out.println(
            "A1 capacity=" + buffer1.capacity() + " limit=" + buffer1.limit()
                  + " position=" + buffer1.position());
    buffer1.put((byte) 125);
    System.out.println(
            "A2 capacity=" + buffer1.capacity() + " limit=" + buffer1.limit()
                  + " position=" + buffer1.position());
    buffer1.put((byte) 126);
    System.out.println(
            "A3 capacity=" + buffer1.capacity() + " limit=" + buffer1.limit()
                  + " position=" + buffer1.position());
    buffer1.put((byte) 127);
    System.out.println(
            "B capacity=" + buffer1.capacity() + " limit=" + buffer1.limit()
                  + " position=" + buffer1.position());
    buffer1.rewind();
    System.out.println(
            "C capacity=" + buffer1.capacity() + " limit=" + buffer1.limit()
                  + " position=" + buffer1.position());
    System.out.println(buffer1.get());
    System.out.println(
            "D capacity=" + buffer1.capacity() + " limit=" + buffer1.limit()
                  + " position=" + buffer1.position());
    System.out.println(buffer1.get());
    System.out.println(
            "E capacity=" + buffer1.capacity() + " limit=" + buffer1.limit()
                + " position=" + buffer1.position());
    System.out.println(buffer1.get());
    System.out.println(
            "F capacity=" + buffer1.capacity() + " limit=" + buffer1.limit()
                + " position=" + buffer1.position());
    System.out.println(buffer1.get());
    byte[] getByteArray = buffer1.array();
    for (int i = 0; i < getByteArray.length; i++) {
        System.out.print(getByteArray[i] + " - ");
    }
}
}

程序运行结果如下:

A1 capacity=10 limit=10 position=0
A2 capacity=10 limit=10 position=1
A3 capacity=10 limit=10 position=2
B capacity=10 limit=10 position=3
C capacity=10 limit=10 position=0
125
D capacity=10 limit=10 position=1
126
E capacity=10 limit=10 position=2
127
F capacity=10 limit=10 position=3
0
125-126-127-0-0-0-0-0-0-0-

从运行结果可以看出,在执行相对位置读或写操作后,位置(position)呈递增的状态,位置自动移动到下一个位置上,也就是位置的值是++position的效果,以便进行下一次读或写操作。

1.4.5 put(byte[] src, int offset, int length)和get(byte[] dst, int offset, int length)方法的使用

put(byte[] src, int offset, int length)方法的作用:相对批量put方法,此方法将把给定源数组中的字节传输到此缓冲区当前位置中。如果要从该数组中复制的字节多于此缓冲区中的剩余字节(即length > remaining()),则不传输字节且将抛出BufferOverflowException异常。否则,此方法将给定数组中的length个字节复制到此缓冲区中。将数组中给定off偏移量位置的数据复制到缓冲区的当前位置,从数组中复制的元素个数为length。换句话说,调用此方法的形式为dst.put(src, offset, length),效果与以下循环语句完全相同:

for (int i = offset; i < offset + length; i++)
    dst.put(a[i]);

区别在于它首先检查此缓冲区中是否有足够空间,这样可能效率更高。

put(byte[] src, int offset, int length)方法的参数的介绍如下。

1)src:缓冲区中当前位置的数据来自于src数组。

2)offset:要读取的第一个字节在“数组中的偏移量”,并“不是缓冲区的偏移”,必须为非负且不大于src.length。

3)length:要从给定数组读取的字节的数量,必须为非负且不大于src.length-offset。

get(byte[] dst, int offset, int length)方法的作用:相对批量get方法,此方法将此缓冲区当前位置的字节传输到给定的目标数组中。如果此缓冲中剩余的字节少于满足请求所需的字节(即length > remaining()),则不传输字节且抛出BufferUnderflowException异常。否则,此方法将此缓冲区中的length个字节复制到给定数组中。从此缓冲区的当前位置和数组中的给定偏移量位置开始复制。然后,此缓冲区的位置将增加length。换句话说,调用此方法的形式为src.get(dst, off, len),效果与以下循环语句完全相同:

for (int i = offset; i < offset + length; i++)
    dst[i] = src.get();

区别在于get(byte[] dst, int offset, int length)方法首先检查此缓冲区中是否具有足够的字节,这样可能效率更高。

get(byte[] dst, int offset, int length)方法的参数介绍如下。

1)dst:将缓冲区中当前位置的数据写入dst数组中。

2)offset:要写入的第一个字节在“数组中的偏移量”,并“不是缓冲区的偏移”,必须为非负且不大于dst.length。

3)length:要写入到给定数组中的字节的最大数量,必须为非负且不大于dst.length -offset。

下面来看看这两个方法的基本使用情况,示例代码如下:

public class Test5 {
public static void main(String[] args) {
    byte[] byteArrayIn1 = { 1, 2, 3, 4, 5, 6, 7, 8 };
    byte[] byteArrayIn2 = { 55, 66, 77, 88 };
    // 开辟10个空间
    ByteBuffer bytebuffer = ByteBuffer.allocate(10);
    // 将1,2,3,4,5,6,7,8放入缓冲区的前8个位置中
    bytebuffer.put(byteArrayIn1);
    // 执行put()方法后位置发生改变,将位置设置成2
    bytebuffer.position(2);
    // 将数组55,66,77,88中的66,77,88放入缓冲区的第3位
    // 值变成1,2,66,77,88,6,7,8
    //说明方法put(byte[] src, int offset, int length)放入的位置参考
    // 的是Buffer当前的position位置
    bytebuffer.put(byteArrayIn2, 1, 3);
    System.out.print("A=");
    byte[] getByte = bytebuffer.array();
    for (int i = 0; i < getByte.length; i++) {
        System.out.print(getByte[i] + " ");
    }
    System.out.println();
    bytebuffer.position(1);
    // 创建新的byte[]数组byteArrayOut,目的是将缓冲区中的数据导出来
    byte[] byteArrayOut = new byte[bytebuffer.capacity()];
    // 使用get()方法从缓冲区position值为1的位置开始,向byteArrayOut数组的
    // 索引为3处一共复制4个字节
    //说明方法get(byte[] dst, int offset, int length)获得数据的位置参考
    // 的是Buffer当前的position位置
    bytebuffer.get(byteArrayOut, 3, 4);
    System.out.print("B=");
    // 打印byteArrayOut数组中的内容
    for (int i = 0; i < byteArrayOut.length; i++) {
        System.out.print(byteArrayOut[i] + " ");
    }
}
}

程序运行结果如下:

A=1 2 66 77 88 6 7 8 0 0
B=0 0 0 2 66 77 88 0 0 0

在使用put(byte[] src, int offset, int length)方法的过程中,需要注意两种出现异常的情况:

1)当offset+length的值大于src.length时,抛出IndexOutOfBoundsException异常;

2)当参数length的值大于buffer.remaining时,抛出BufferOverflowException异常。

也就是说,在上述两种异常情况下都不传输字节。

先来测试第1种情况,代码如下:

public class Test5_1 {
public static void main(String[] args) {
    byte[] byteArrayIn1 = { 1, 2, 3, 4, 5, 6, 7 };
    ByteBuffer bytebuffer = ByteBuffer.allocate(10);
    bytebuffer.put(byteArrayIn1, 0, bytebuffer.capacity());
}
}

程序运行结果如下:

Exception in thread "main" java.lang.IndexOutOfBoundsException
    at java.nio.Buffer.checkBounds(Buffer.java:567)
    at java.nio.HeapByteBuffer.put(HeapByteBuffer.java:187)
    at ByteBufferAPITest.Test5_1.main(Test5_1.java:9)

再来测试第2种情况,代码如下:

public class Test5_2 {
public static void main(String[] args) {
    byte[] byteArrayIn1 = { 1, 2, 3, 4, 5, 6, 7 };
    ByteBuffer bytebuffer = ByteBuffer.allocate(10);
    bytebuffer.position(9);
    bytebuffer.put(byteArrayIn1, 0, 4);
}
}

程序运行结果如下:

Exception in thread "main" java.nio.BufferOverflowException
    at java.nio.HeapByteBuffer.put(HeapByteBuffer.java:189)
    at ByteBufferAPITest.Test5_2.main(Test5_2.java:10)

在调用put(byte[] src, int offset, int length)方法时,如果遇到这种向缓冲区中写入数据时有可能写多或写少的情况,那么可以使用如下的示例代码进行解决:

public class Test5_3 {
public static void main(String[] args) {
    byte[] byteArrayIn1 = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
    ByteBuffer bytebuffer = ByteBuffer.allocate(10);
    int getArrayIndex = 0;
    while (getArrayIndex < byteArrayIn1.length) {
          // 下面代码的作用就是判断:缓冲区的剩余和数组的剩余谁少
        int readLength = Math.min(bytebuffer.remaining(), byteArrayIn1.length -
            getArrayIndex);
        bytebuffer.put(byteArrayIn1, getArrayIndex, readLength);
        bytebuffer.flip();
        byte[] getArray = bytebuffer.array();
        for (int i = 0; i < bytebuffer.limit(); i++) {
            System.out.print(getArray[i] + " ");
        }
        getArrayIndex = getArrayIndex + readLength;
        System.out.println();
        bytebuffer.clear();
    }
}
}

程序运行结果如下:

1 2 3 4 5 6 7 8 9 10
11 12

上面的代码在byte[]的length大于或等于缓冲区的remaining()时,或者小于或等于remaining()时都可以正确运行。

在使用get(byte[] dst, int offset, int length)方法的过程中,需要注意两种出现异常的情况:

1)当offset+length的值大于dst.length时,抛出IndexOutOfBoundsException异常;

2)当参数length的值大于buffer.remaining时,抛出BufferUnderflowException异常。

也就是说,在上述两种异常情况下都不传输字节。

先来测试第1种情况,代码如下:

public class Test5_4 {
public static void main(String[] args) {
    byte[] byteArrayIn1 = { 1, 2, 3, 4, 5, 6, 7 };
    ByteBuffer bytebuffer = ByteBuffer.wrap(byteArrayIn1);
    byte[] byteArrayOut = new byte[5];
    bytebuffer.get(byteArrayOut, 0, 7);
}
}

运行效果如下:

Exception in thread "main" java.lang.IndexOutOfBoundsException
    at java.nio.Buffer.checkBounds(Buffer.java:567)
    at java.nio.HeapByteBuffer.get(HeapByteBuffer.java:149)
    at ByteBufferAPITest.Test5_4.main(Test5_4.java:10)

再来测试第2种情况,代码如下:

public class Test5_5 {
public static void main(String[] args) {
    byte[] byteArrayIn1 = { 1, 2, 3, 4, 5, 6, 7 };
    ByteBuffer bytebuffer = ByteBuffer.wrap(byteArrayIn1);
    bytebuffer.position(5);
    byte[] byteArrayOut = new byte[500];
    bytebuffer.get(byteArrayOut, 0, 50);
}
}

运行效果如下:

Exception in thread "main" java.nio.BufferUnderflowException
    at java.nio.HeapByteBuffer.get(HeapByteBuffer.java:151)
    at ByteBufferAPITest.Test5_5.main(Test5_5.java:11)

在调用get(byte[] dst, int offset, int length)方法时,如果遇到这种从缓冲区中获得数据时有可能取多或取少的情况,那么可以使用如下的示例代码进行解决:

public class Test5_6 {
public static void main(String[] args) {
    byte[] byteArrayIn = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
    ByteBuffer bytebuffer = ByteBuffer.wrap(byteArrayIn);
    byte[] byteArrayOut = new byte[5];
    while (bytebuffer.hasRemaining()) {
        int readLength = Math.min(bytebuffer.remaining(), byteArrayOut.length);
        bytebuffer.get(byteArrayOut, 0, readLength);
        for (int i = 0; i < readLength; i++) {
            System.out.print(byteArrayOut[i] + " ");
        }
        System.out.println();
    }
}
}

运行效果如下:

1 2 3 4 5
6 7 8 9 10
11 12

总结一下本小节介绍的put(byte[] src, int offset, int length)和get(byte[] dst, int offset, int length)方法的执行流程,核心流程代码如下:

public ByteBuffer put(byte[] src, int offset, int length) {
// 能从数组中取出指定长度的数据,就不报错(size是src数组的长度)
        if ((off | len | (off + len) | (size - (off + len))) < 0)
            throw new IndexOutOfBoundsException();
// 取出来的数据的大小小于或等于缓冲区的剩余空间,就不报错
        if (length > remaining())
            throw new BufferOverflowException();
        int end = offset + length;
        for (int i = offset; i < end; i++)
            this.put(src[i]);
        return this;
    }

get(byte[] dst, int offset, int length)方法和put(byte[] src, int offset, int length)方法的逻辑相同。

1.4.6 put(byte[] src)和get(byte[] dst)方法的使用

put(byte[] src)方法的作用:相对批量put方法,此方法将给定的源byte数组的所有内容存储到此缓冲区的当前位置中。与该方法功能完全相同的写法为:dst.put(a, 0, a.length)。

get(byte[] dst)方法的作用:相对批量get方法,此方法将此缓冲区remaining的字节传输到给定的目标数组中。与该方法功能完全相同的写法为:src.get(a, 0, a.length)。使用此方法取得数据的数量取决于byte[] dst目标数组的大小。

put(byte[] src)和get(byte[] dst)方法调用的是3个参数的put和get方法,源代码如下:

public final ByteBuffer put(byte[] src) {
    return put(src, 0, src.length);
}
public ByteBuffer get(byte[] dst) {
    return get(dst, 0, dst.length);
}

在上述源代码中调用的是3个参数的方法put(byte[] src, int offset, int length)和get(byte[] dst, int offset, int length)。

示例代码如下:

public class Test4 {
public static void main(String[] args) {
    byte[] byteArray = new byte[] { 3, 4, 5, 6, 7, 8 };
    ByteBuffer buffer1 = ByteBuffer.allocate(10);
    buffer1.put((byte) 1);
    buffer1.put((byte) 2);
    System.out.println("A=" + buffer1.position());
    buffer1.put(byteArray); // 是相对位置存入操作
    System.out.println("B=" + buffer1.position());
    buffer1.flip();
    buffer1.position(3);
    System.out.println("C=" + buffer1.position());
    byte[] newArray = new byte[buffer1.remaining()];
    buffer1.get(newArray); // 是相对位置读取操作
    for (int i = 0; i < newArray.length; i++) {
        System.out.print(newArray[i] + " ");
    }
}
}

程序运行结果如下:

A=2
B=8
C=3
4 5 6 7 8

在使用put(byte[] src)和get(byte[] dst)方法的过程中,需要注意异常情况的发生。

(1)public final ByteBuffer put(byte[] src)

1)缓冲区的remaining大于或等于数组的length,不出现异常。

2)缓冲区的remaining小于数组的length,出现异常。

(2)public ByteBuffer get(byte[] dst)

1)缓冲区的remaining大于或等于数组的length,不出现异常。

2)缓冲区的remaining小于数组的length,出现异常。

下面对上述两种方法中出现的两种情况分别进行研究。

1)put(byte[] src)方法:缓冲区的remaining大于或等于数组的length,不出现异常。

public class Test4_1 {
public static void main(String[] args) {
    byte[] byteArray = new byte[] { 1, 2, 3, 4, 5 };
    ByteBuffer buffer = ByteBuffer.allocate(10);
    buffer.position(1); // 缓冲区的剩余空间足够了,不出现异常
    buffer.put(byteArray);
    byte[] newByteArray = buffer.array();
    for (int i = 0; i < newByteArray.length; i++) {
        System.out.print(newByteArray[i]);
    }
}
}

程序运行结果如下:

0123450000

2)put(byte[] src)方法:缓冲区的remaining小于数组的length,出现异常。

public class Test4_2 {
public static void main(String[] args) {
    byte[] byteArray = new byte[] { 3, 4, 5, 6, 7, 8 };
    ByteBuffer buffer = ByteBuffer.allocate(10);
    buffer.position(8); // 缓冲区的剩余空间不够了,出现异常
    buffer.put(byteArray);
}
}

程序运行结果如下:

Exception in thread "main" java.nio.BufferOverflowException
    at java.nio.HeapByteBuffer.put(HeapByteBuffer.java:189)
    at java.nio.ByteBuffer.put(ByteBuffer.java:859)
    at ByteBufferAPITest.Test4_2.main(Test4_2.java:10)

3)get(byte[] dst)方法:缓冲区的remaining大于或等于数组的length,不出现异常。

public class Test4_3 {
public static void main(String[] args) {
    byte[] byteArray1 = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    ByteBuffer buffer = ByteBuffer.wrap(byteArray1);
    byte[] byteArrayNew = new byte[5];
    buffer.get(byteArrayNew); // 不出现异常
    for (int i = 0; i < byteArrayNew.length; i++) {
        System.out.print(byteArrayNew[i]);
    }
}
}

程序运行结果如下:12345

4)get(byte[] dst)方法:缓冲区的remaining小于数组的length,出现异常。

public class Test4_4 {
public static void main(String[] args) {
    byte[] byteArray1 = new byte[] { 1, 2, 3, 4, 5 };
    ByteBuffer buffer = ByteBuffer.wrap(byteArray1);
    buffer.position(3);
    byte[] byteArrayNew = new byte[3];
    buffer.get(byteArrayNew);
    // 出现异常,因为缓冲区中的剩余数据不够3个
}
}

程序运行结果如下:

Exception in thread "main" java.nio.BufferUnderflowException
    at java.nio.HeapByteBuffer.get(HeapByteBuffer.java:151)
    at java.nio.ByteBuffer.get(ByteBuffer.java:715)
    at ByteBufferAPITest.Test4_4.main(Test4_4.java:11)

如果在使用public final ByteBuffer put(byte[] src)方法的过程中,出现字节数组的length大于或等于或者小于或等于缓冲区的remaining剩余空间时,就要进行特殊处理,即分批进行处理,示例代码如下:

public class Test4_5 {
public static void main(String[] args) throws NoSuchMethodException, Security
    Exception, IllegalAccessException,
        IllegalArgumentException,  InvocationTargetException,  Interrupted
            Exception {
    byte[] byteArray1 = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    ByteBuffer byteBuffer1 = ByteBuffer.allocate(4);
    int byteArrayCurrentIndex = 0;
    int byteArrayRemaining = 0;
    while (byteArrayCurrentIndex < byteArray1.length) {
        byteArrayRemaining = byteArray1.length - byteArrayCurrentIndex;
        int readLength = Math.min(byteArrayRemaining, byteBuffer1.
            remaining());
        byte[] newByteArray = Arrays.copyOfRange(byteArray1, byteArray
            CurrentIndex,
                byteArrayCurrentIndex + readLength);
        byteBuffer1.put(newByteArray);
        byteBuffer1.flip();
        byte[] getByte = byteBuffer1.array();
        for (int i = 0; i < byteBuffer1.limit(); i++) {
            System.out.print(getByte[i] + " ");
        }
        System.out.println();
        byteArrayCurrentIndex = byteArrayCurrentIndex + readLength;
        byteBuffer1.clear();
    }
}
}

如果在使用get(byte[] dst)方法的过程中,出现字节数组的length大于或等于或者小于或等于缓冲区的remaining时,那么也要进行特殊处理,示例代码如下:

public class Test4_6 {
public static void main(String[] args) {
    byte[] byteArray = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8 };
    ByteBuffer buffer = ByteBuffer.wrap(byteArray);
    int copyDataCount = 3;
    while (buffer.hasRemaining()) {
        byte[] copyByteArray = new byte[Math.min(buffer.remaining(), copy
            DataCount)];
        buffer.get(copyByteArray);
        for (int i = 0; i < copyByteArray.length; i++) {
            System.out.print(copyByteArray[i]);
        }
        System.out.println();
    }
}
}

1.4.7 put(int index, byte b)和get(int index)方法的使用与position不变

put(int index, byte b)方法的作用:绝对put方法,将给定字节写入此缓冲区的给定索引位置。

get(int index)方法的作用:绝对get方法,读取指定位置索引处的字节。

示例代码如下:

public class Test6_1 {
public static void main(String[] args) {
    byte[] byteArrayIn1 = { 1, 2, 3, 4, 5, 6, 7, 8 };
    ByteBuffer bytebuffer = ByteBuffer.allocate(10);
    bytebuffer.put(byteArrayIn1);
    bytebuffer.put(2, (byte) 127); //
    System.out.println(bytebuffer.get(2)); //
    bytebuffer.position(0);
    byte[] byteArrayOut = new byte[bytebuffer.capacity()];
    bytebuffer.get(byteArrayOut, 0, byteArrayOut.length);
    for (int i = 0; i < byteArrayOut.length; i++) {
         System.out.print(byteArrayOut[i] + " ");
    }
}
}

程序运行结果如下:

127
1 2127 4 5 6 7 8 0 0

使用绝对位置操作后,位置(position)并不改变,测试代码如下:

public class Test6_2 {
public static void main(String[] args) {
    ByteBuffer bytebuffer = ByteBuffer.allocate(10);
    bytebuffer.position(9);
    System.out.println(bytebuffer.position());
    bytebuffer.put(2, (byte) 127);
    System.out.println(bytebuffer.position());
    bytebuffer.rewind();
    byte[] byteArrayOut = new byte[bytebuffer.capacity()];
    bytebuffer.get(byteArrayOut, 0, byteArrayOut.length);
    for (int i = 0; i < byteArrayOut.length; i++) {
        System.out.print(byteArrayOut[i] + " ");
    }
}
}

程序运行结果如下:

9
9
0 0127 0 0 0 0 0 0 0

1.4.8 put(ByteBuffer src)方法的使用

put(ByteBuffer src)方法的作用:相对批量put方法,此方法将给定源缓冲区中的剩余字节传输到此缓冲区的当前位置中。如果源缓冲区中的剩余字节多于此缓冲区中的剩余字节,即src.remaining() > remaining(),则不传输字节且抛出BufferOverflowException异常。否则,此方法将给定缓冲区中的n = src.remaining()个字节复制到此缓冲区中,从每个缓冲区的当前位置开始复制。然后,这两个缓冲区的位置都增加n。

示例代码如下:

public class Test7 {
public static void main(String[] args) {
    byte[] byteArrayIn1 = { 1, 2, 3, 4, 5, 6, 7, 8 };
    ByteBuffer bytebuffer1 = ByteBuffer.wrap(byteArrayIn1);
    byte[] byteArrayIn2 = { 55, 66, 77 };
    ByteBuffer bytebuffer2 = ByteBuffer.wrap(byteArrayIn2);
    bytebuffer1.position(4);
    bytebuffer2.position(1);
    bytebuffer1.put(bytebuffer2);
    System.out.println("bytebuffer1被改变:" + bytebuffer1.position());
    System.out.println("bytebuffer2被改变:" + bytebuffer2.position());
    byte[] byteArrayOut = bytebuffer1.array();
    for (int i = 0; i < byteArrayOut.length; i++) {
        System.out.print(byteArrayOut[i] + " ");
    }
}
}

程序运行结果如下:

bytebuffer1被改变:6
bytebuffer2被改变:3
1 2 3 4 66 77 7 8

1.4.9 putType()和getType()方法的使用

putChar(char value)方法的作用:用来写入char值的相对put方法(可选操作)。将两个包含指定char值的字节按照当前的字节顺序写入到此缓冲区的当前位置,然后将该位置增加2。

putChar(int index, char value)方法的作用:用于写入char值的绝对put方法(可选操作)。将两个包含给定char值的字节按照当前的字节顺序写入到此缓冲区的给定索引处。

putDouble(double value)方法的作用:用于写入double值的相对put方法(可选操作)。将8个包含给定double值的字节按照当前的字节顺序写入到此缓冲区的当前位置,然后将该位置增加8。

putDouble(int index, double value)方法的作用:用于写入double值的绝对put方法(可选操作)。将8个包含给定double值的字节按照当前的字节顺序写入到此缓冲区的给定索引处。

putFloat(float value)方法的作用:用于写入float值的相对put方法(可选操作)。将4个包含给定float值的字节按照当前的字节顺序写入到此缓冲区的当前位置,然后将该位置增加4。

putFloat(int index, float value)方法的作用:用于写入float值的绝对put方法(可选操作)。将4个包含给定float值的字节按照当前的字节顺序写入到此缓冲区的给定索引处。

putInt(int value)方法的作用:用于写入int值的相对put方法(可选操作)。将4个包含给定int值的字节按照当前的字节顺序写入到此缓冲区的当前位置,然后将该位置增加4。

putInt(int index, int value)方法的作用:用于写入int值的绝对put方法(可选操作)。将4个包含给定int值的字节按照当前的字节顺序写入到此缓冲区的给定索引处。

putLong(long value)方法的作用:用于写入long值的相对put方法(可选操作)。将8个包含给定long值的字节按照当前的字节顺序写入到此缓冲区的当前位置,然后将该位置增加8。

putLong(int index, long value)方法的作用:用于写入long值的绝对put方法(可选操作)。将8个包含给定long值的字节按照当前的字节顺序写入到此缓冲区的给定索引处。

putShort(short value)方法的作用:用于写入short值的相对put方法(可选操作)。将两个包含指定short值的字节按照当前的字节顺序写入到此缓冲区的当前位置,然后将该位置增加2。

putShort(int index, short value)方法的作用:用于写入short值的绝对put方法(可选操作)。将两个包含给定short值的字节按照当前的字节顺序写入到此缓冲区的给定索引处。

示例代码如下:

public class Test8 {
public static void main(String[] args) {
    ByteBuffer bytebuffer1 = ByteBuffer.allocate(100);
    bytebuffer1.putChar('a'); // 0-1, char占2个字节
    bytebuffer1.putChar(2, 'b'); //2-3
    bytebuffer1.position(4);
    bytebuffer1.putDouble(1.1); // 4-11, double占8个字节
    bytebuffer1.putDouble(12, 1.2); //12-19
    bytebuffer1.position(20);
    bytebuffer1.putFloat(2.1F); // 20-23, float占4个字节
    bytebuffer1.putFloat(24, 2.2F); //24-27
    bytebuffer1.position(28);
    bytebuffer1.putInt(31); // 28-31, int占4个字节
    bytebuffer1.putInt(32, 32); //32-35
    bytebuffer1.position(36);
    bytebuffer1.putLong(41L); // 36-43, long占8个字节
    bytebuffer1.putLong(44, 42L); //44-51
    bytebuffer1.position(52);
    bytebuffer1.putShort((short) 51); //52-53, short占2个字节
    bytebuffer1.putShort(54, (short) 52); //54-55
    bytebuffer1.position(0);
    byte[] byteArrayOut = bytebuffer1.array();
    for (int i = 0; i < byteArrayOut.length; i++) {
        //System.out.print(byteArrayOut[i] + " ");
    }
    System.out.println();
    System.out.println(bytebuffer1.getChar());
    System.out.println(bytebuffer1.getChar(2));
    bytebuffer1.position(4);
    System.out.println(bytebuffer1.getDouble());
    System.out.println(bytebuffer1.getDouble(12));
    bytebuffer1.position(20);
    System.out.println(bytebuffer1.getFloat());
    System.out.println(bytebuffer1.getFloat(24));
    bytebuffer1.position(28);
    System.out.println(bytebuffer1.getInt());
    System.out.println(bytebuffer1.getInt(32));
    bytebuffer1.position(36);
    System.out.println(bytebuffer1.getLong());
    System.out.println(bytebuffer1.getLong(44));
    bytebuffer1.position(52);
    System.out.println(bytebuffer1.getShort());
    System.out.println(bytebuffer1.getShort(54));
}
}

程序运行结果如下:

a
b
1.1
1.2
2.1
2.2
31
32
41
42
51
52

1.4.10 slice()方法的使用与arrayOffSet()为非0的测试

slice()方法的作用:创建新的字节缓冲区,其内容是此缓冲区内容的共享子序列。新缓冲区的内容将从此缓冲区的当前位置开始。此缓冲区内容的更改在新缓冲区中是可见的,反之亦然;这两个缓冲区的位置、限制和标记值是相互独立的。新缓冲区的位置将为0,其容量和限制将为此缓冲区中所剩余的字节数量,其标记是不确定的。当且仅当此缓冲区为直接缓冲区时,新缓冲区才是直接缓冲区。当且仅当此缓冲区为只读时,新缓冲区才是只读的。

示例代码如下:

public class Test9_1 {
public static void main(String[] args) {
    byte[] byteArrayIn1 = { 1, 2, 3, 4, 5, 6, 7, 8 };
    ByteBuffer bytebuffer1 = ByteBuffer.wrap(byteArrayIn1);
    bytebuffer1.position(5);
    ByteBuffer bytebuffer2 = bytebuffer1.slice();
      System.out.println("bytebuffer1.position="  +  bytebuffer1.position()  +
        " bytebuffer1.capacity="
                +  bytebuffer1.capacity()  +  "  bytebuffer1.limit="  +  bytebuffer1.
                  limit());
      System.out.println("bytebuffer2.position="  +  bytebuffer2.position()  +
        " bytebuffer2.capacity="
                +  bytebuffer2.capacity()  +  "  bytebuffer2.limit="  +  bytebuffer2.
                limit());
    bytebuffer2.put(0, (byte) 111);
    byte[] byteArray1 = bytebuffer1.array();
    byte[] byteArray2 = bytebuffer2.array();
    for (int i = 0; i < byteArray1.length; i++) {
        System.out.print(byteArray1[i] + " ");
    }
    System.out.println();
    for (int i = 0; i < byteArray2.length; i++) {
        System.out.print(byteArray2[i] + " ");
    }
}
}

程序运行结果如下:

bytebuffer1.position=5 bytebuffer1.capacity=8 bytebuffer1.limit=8
bytebuffer2.position=0 bytebuffer2.capacity=3 bytebuffer2.limit=3
1 2 3 4 5111 7 8
1 2 3 4 5111 7 8

在使用slice()方法后,再调用arrayOffSet()方法时,会出现返回值为非0的情况,测试代码如下:

public class Test9_2 {
public static void main(String[] args) {
    byte[] byteArrayIn1 = { 1, 2, 3, 4, 5, 6, 7, 8 };
    ByteBuffer bytebuffer1 = ByteBuffer.wrap(byteArrayIn1);
    bytebuffer1.position(5);
    ByteBuffer bytebuffer2 = bytebuffer1.slice();
    System.out.println(bytebuffer2.arrayOffset());
}
}

程序运行结果如下:

5

运行结果说明bytebuffer2的第1个元素的位置是相对于byteArrayIn1数组中索引值为5的偏移。

1.4.11 转换为CharBuffer字符缓冲区及中文的处理

asCharBuffer()方法的作用:创建此字节缓冲区的视图,作为char缓冲区。新缓冲区的内容将从此缓冲区的当前位置开始。此缓冲区内容的更改在新缓冲区中是可见的,反之亦然;这两个缓冲区的位置、限制和标记值是相互独立的。新缓冲区的位置将为0,其容量和限制将为此缓冲区中所剩余的字节数的1/2,其标记是不确定的。当且仅当此缓冲区为直接缓冲区时,新缓冲区才是直接缓冲区。当且仅当此缓冲区为只读时,新缓冲区才是只读的。

示例代码如下:

public class Test10 {
public static void main(String[] args) throws UnsupportedEncodingException {
    byte[] byteArrayIn1 = "我是中国人".getBytes();
    // 运行本代码的*.java文件是UTF-8编码,所以运行环境取得的编码默认是UTF-8
    System.out.println(Charset.defaultCharset().name());
    ByteBuffer bytebuffer = ByteBuffer.wrap(byteArrayIn1);
    System.out.println("bytebuffer=" + bytebuffer.getClass().getName());
    CharBuffer charBuffer = bytebuffer.asCharBuffer();
    System.out.println("charBuffer=" + charBuffer.getClass().getName());
    System.out.println("bytebuffer.position=" + bytebuffer.position() + "
        bytebuffer.capacity="
            + bytebuffer.capacity() + " bytebuffer.limit=" + bytebuffer.
                  limit());
    System.out.println("charBuffer.position=" + charBuffer.position() + "
        charBuffer.capacity="
            + charBuffer.capacity() + " charBuffer.limit=" + charBuffer.
                  limit());
    System.out.println(charBuffer.capacity());
    charBuffer.position(0);
    for (int i = 0; i < charBuffer.capacity(); i++) {
        // get()方法使用的编码为UTF-16BE
        // UTF-8与UTF-16BE并不是同一种编码
        // 所以这时出现了乱码
        System.out.print(charBuffer.get() + " ");
    }
}
}

上述程序运行结果如图1-20所示。

图1-20 运行结果

上面的代码产生了4个步骤。

1)使用代码语句“byte[] byteArrayIn1 = "我是中国人".getBytes(); ”将中文转成字节数组,数组中存储的编码为UTF-8。

2)使用代码语句“ByteBuffer bytebuffer = ByteBuffer.wrap(byteArrayIn1); ”将UTF-8编码的字节数组转换成字节缓冲区(ByteBuffer),缓冲区中存储的编码也为UTF-8。

3)使用代码语句“CharBuffer charBuffer = bytebuffer.asCharBuffer(); ”将编码格式为UTF-8的ByteBuffer中的内容转换成UTF-8编码的CharBuffer。

4)当调用CharBuffer类的子类java.nio.ByteBufferAsCharBufferB中的get()方法时,以UTF-16BE的编码格式获得中文时出现编码不匹配的情况,因此出现了乱码。

从上面的运行结果来看,并没有将正确的中文获取出来,相反还出现了乱码,出现乱码的原因就是编码不对称造成的,解决办法就是使编码对称,也就是将中文转成字节数组时使用UTF-16BE编码,而使用ByteBufferAsCharBufferB的get()方法时再以UTF-16BE编码转回中文即可,这样就不会出现中文乱码问题了。解决乱码问题的程序代码如下:

public class Test11 {
public static void main(String[] args) throws UnsupportedEncodingException {
    // 将中文转成UTF-16BE编码的字节数组
    byte[] byteArrayIn1 = "我是中国人".getBytes("utf-16BE");
    System.out.println(Charset.defaultCharset().name());
    ByteBuffer bytebuffer = ByteBuffer.wrap(byteArrayIn1);
    System.out.println("bytebuffer=" + bytebuffer.getClass().getName());
    CharBuffer charBuffer = bytebuffer.asCharBuffer();
    System.out.println("charBuffer=" + charBuffer.getClass().getName());
    System.out.println("bytebuffer.position=" + bytebuffer.position() + "
        bytebuffer.capacity="
            + bytebuffer.capacity() + " bytebuffer.limit=" + bytebuffer.limit());
    System.out.println("charBuffer.position=" + charBuffer.position() + "
    charBuffer.capacity="
            + charBuffer.capacity() + " charBuffer.limit=" + charBuffer.limit());
    System.out.println(charBuffer.capacity());
    charBuffer.position(0);
    for (int i = 0; i < charBuffer.capacity(); i++) {
        System.out.print(charBuffer.get() + " "); //UTF-16BE
    }
}
}

程序运行结果如下:

UTF-8
bytebuffer=java.nio.HeapByteBuffer
charBuffer=java.nio.ByteBufferAsCharBufferB
bytebuffer.position=0 bytebuffer.capacity=10 bytebuffer.limit=10
charBuffer.position=0 charBuffer.capacity=5 charBuffer.limit=5
5
我 是 中 国 人

从上面输出的结果可以发现,中文乱码的问题解决了。

解决的步骤是什么呢?如下:

1)使用代码语句“byte[] byteArrayIn1 = "我是中国人".getBytes("utf-16BE"); ”将中文转成字节数组,数组中存储的编码为UTF-16BE。

2)使用代码语句“ByteBuffer bytebuffer = ByteBuffer.wrap(byteArrayIn1); ”将UTF-16BE编码的字节数组转换成ByteBuffer, ByteBuffer中存储的编码也为UTF-16BE。

3)使用代码语句“CharBuffer charBuffer = bytebuffer.asCharBuffer(); ”将编码格式为UTF-16BE的ByteBuffer中的内容转换成UTF-16BE编码的CharBuffer。

4)当调用CharBuffer类的子类java.nio.ByteBufferAsCharBufferB中的get()方法时,以UTF-16BE的编码格式获得中文时不再出现乱码,这样乱码问题就解决了。

当然,还可以使用其他的办法来解决乱码问题。

示例代码如下:

public class Test12 {
public static void main(String[] args) throws UnsupportedEncodingException {
    // 字节数组使用的编码为UTF-8
    byte[] byteArrayIn1 = "我是中国人".getBytes("utf-8");
    System.out.println(Charset.defaultCharset().name());
    ByteBuffer bytebuffer = ByteBuffer.wrap(byteArrayIn1);
    // 将bytebuffer中的内容转成UTF-8编码的CharBuffer
    CharBuffer charBuffer = Charset.forName("utf-8").decode(bytebuffer);
    System.out.println("bytebuffer=" + bytebuffer.getClass().getName());
    System.out.println("charBuffer=" + charBuffer.getClass().getName());
    System.out.println("bytebuffer.position=" + bytebuffer.position() + "
        bytebuffer.capacity="
            + bytebuffer.capacity() + " bytebuffer.limit=" + bytebuffer.limit());
    System.out.println("charBuffer.position=" + charBuffer.position() + "
        charBuffer.capacity="
            + charBuffer.capacity() + " charBuffer.limit=" + charBuffer.limit());
    System.out.println(charBuffer.capacity());
    charBuffer.position(0);
    for (int i = 0; i < charBuffer.limit(); i++) {
        System.out.print(charBuffer.get() + " ");
    }
}
}

程序运行结果如下:

GBK
bytebuffer=java.nio.HeapByteBuffer
charBuffer=java.nio.HeapCharBuffer
bytebuffer.position=15 bytebuffer.capacity=15 bytebuffer.limit=15
charBuffer.position=0 charBuffer.capacity=15 charBuffer.limit=5
15
我 是 中 国 人

输出信息的第一行为GBK是因为*.java文件的编码就是GBK,并没有改成UTF-8。

上面的代码也解决了中文乱码问题,解决步骤如下。

1)使用代码语句“byte[] byteArrayIn1 = "我是中国人".getBytes("utf-8"); ”将GBK编码的中文转换成UTF-8编码格式的字节数组。

2)使用代码语句“ByteBuffer bytebuffer = ByteBuffer.wrap(byteArrayIn1); ”将UTF-8编码的字节数组转换成ByteBuffer, ByteBuffer中存储的编码也为UTF-8。

3)使用代码语句“CharBuffer charBuffer = Charset.forName("utf-8").decode(bytebuffer); ”的目的是将编码类型为UTF-8的ByteBuffer转换成编码类型为UTF-8的java.nio.HeapCharBuffer。HeapCharBuffer类中的hb[]数组中存储的内容已经是正确的UTF-8编码的中文了。

4)当调用CharBuffer类的子类java.nio.HeapCharBuffer中的get()方法时,在hb[]数组中直接获得中文,不再出现乱码,乱码问题解决了。

使用asCharBuffer()方法获得CharBuffer后,对ByteBuffer的更改会直接影响CharBuffer中的值,示例代码如下:

public class Test12_sameValue {
public static void main(String[] args) throws UnsupportedEncodingException {
    byte[] byteArray = "我是中国人".getBytes("utf-16BE");
    ByteBuffer byteBuffer1 = ByteBuffer.wrap(byteArray);
    CharBuffer charBuffer = byteBuffer1.asCharBuffer();
    byteBuffer1.put(2, "为".getBytes("utf-16BE")[0]);
    byteBuffer1.put(3, "为".getBytes("utf-16BE")[1]);
    charBuffer.clear();
    for (int i = 0; i < charBuffer.limit(); i++) {
        System.out.print(charBuffer.get() + " ");
    }
}
}

运行结果如下:

我 为 中 国 人

1.4.12 转换为其他类型的缓冲区

asDoubleBuffer()方法的作用:创建此字节缓冲区的视图,作为double缓冲区。新缓冲区的内容将从此缓冲区的当前位置开始。此缓冲区内容的更改在新缓冲区中是可见的,反之亦然;这两个缓冲区的位置、限制和标记值是相互独立的。新缓冲区的位置将为0,其容量和界限将为此缓冲区中所剩余的字节数的1/8,其标记是不确定的。当且仅当此缓冲区为直接缓冲区时,新缓冲区才是直接缓冲区。当且仅当此缓冲区为只读时,新缓冲区才是只读的。

asFloatBuffer()方法的作用:创建此字节缓冲区的视图,作为float缓冲区。新缓冲区的内容将从此缓冲区的当前位置开始。此缓冲区内容的更改在新缓冲区中是可见的,反之亦然;这两个缓冲区的位置、限制和标记值是相互独立的。新缓冲区的位置将为0,其容量和其限制将为此缓冲区中剩余字节数的1/4,其标记是不确定的。当且仅当此缓冲区为直接缓冲区时,新缓冲区才是直接缓冲区。当且仅当此缓冲区为只读时,新缓冲区才是只读的。

asIntBuffer()方法的作用:创建此字节缓冲区的视图,作为int缓冲区。新缓冲区的内容将从此缓冲区的当前位置开始。此缓冲区内容的更改在新缓冲区中是可见的,反之亦然;这两个缓冲区的位置、限制和标记值是相互独立的。新缓冲区的位置将为0,其容量和限制将为此缓冲区中所剩余的字节数的1/4,其标记是不确定的。当且仅当此缓冲区为直接缓冲区时,新缓冲区才是直接缓冲区。当且仅当此缓冲区为只读时,新缓冲区才是只读的。

asLongBuffer()方法的作用:创建此字节缓冲区的视图,作为long缓冲区。新缓冲区的内容将从此缓冲区的当前位置开始。此缓冲区内容的更改在新缓冲区中是可见的,反之亦然;这两个缓冲区的位置、限制和标记值是相互独立的。新缓冲区的位置将为0,其容量和限制将为此缓冲区中所剩余的字节数的1/8,其标记是不确定的。当且仅当此缓冲区为直接缓冲区时,新缓冲区才是直接缓冲区。当且仅当此缓冲区为只读时,新缓冲区才是只读的。

asShortBuffer()方法的作用:创建此字节缓冲区的视图,作为short缓冲区。新缓冲区的内容将从此缓冲区的当前位置开始。此缓冲区内容的更改在新缓冲区中是可见的,反之亦然;这两个缓冲区的位置、限制和标记值是相互独立的。新缓冲区的位置将为0,其容量和限制将为此缓冲区中所剩余的字节数的1/2,其标记是不确定的。当且仅当此缓冲区为直接缓冲区时,新缓冲区才是直接缓冲区。当且仅当此缓冲区为只读时,新缓冲区才是只读的。

示例代码如下:

public class Test13 {
public static void main(String[] args) throws UnsupportedEncodingException {
    ByteBuffer bytebuffer1 = ByteBuffer.allocate(32);
    bytebuffer1.putDouble(1.1D);
    bytebuffer1.putDouble(1.2D);
    bytebuffer1.putDouble(1.3D);
    bytebuffer1.putDouble(1.4D);
    bytebuffer1.flip();
    DoubleBuffer doubleBuffer = bytebuffer1.asDoubleBuffer();
    for (int i = 0; i < doubleBuffer.capacity(); i++) {
        System.out.print(doubleBuffer.get(i) + " ");
    }
    System.out.println();
    ByteBuffer bytebuffer2 = ByteBuffer.allocate(16);
    bytebuffer2.putFloat(2.1F);
    bytebuffer2.putFloat(2.2F);
    bytebuffer2.putFloat(2.3F);
    bytebuffer2.putFloat(2.4F);
    bytebuffer2.flip();
    FloatBuffer floatBuffer = bytebuffer2.asFloatBuffer();
    for (int i = 0; i < floatBuffer.capacity(); i++) {
        System.out.print(floatBuffer.get(i) + " ");
    }
    System.out.println();
    ByteBuffer bytebuffer3 = ByteBuffer.allocate(16);
    bytebuffer3.putInt(31);
    bytebuffer3.putInt(32);
    bytebuffer3.putInt(33);
    bytebuffer3.putInt(34);
    bytebuffer3.flip();
    IntBuffer intBuffer = bytebuffer3.asIntBuffer();
    for (int i = 0; i < intBuffer.capacity(); i++) {
        System.out.print(intBuffer.get(i) + " ");
    }
    System.out.println();
    ByteBuffer bytebuffer4 = ByteBuffer.allocate(32);
    bytebuffer4.putLong(41L);
    bytebuffer4.putLong(42L);
    bytebuffer4.putLong(43L);
    bytebuffer4.putLong(44L);
    bytebuffer4.flip();
    LongBuffer longBuffer = bytebuffer4.asLongBuffer();
    for (int i = 0; i < longBuffer.capacity(); i++) {
        System.out.print(longBuffer.get(i) + " ");
    }
    System.out.println();
    ByteBuffer bytebuffer5 = ByteBuffer.allocate(8);
    bytebuffer5.putShort((short) 51);
    bytebuffer5.putShort((short) 52L);
    bytebuffer5.putShort((short) 53L);
    bytebuffer5.putShort((short) 54L);
    bytebuffer5.flip();
    ShortBuffer shortBuffer = bytebuffer5.asShortBuffer();
    for (int i = 0; i < shortBuffer.capacity(); i++) {
        System.out.print(shortBuffer.get(i) + " ");
    }
}
}

程序运行结果如下:

1.1 1.2 1.3 1.4
2.1 2.2 2.3 2.4
31 32 33 34
41 42 43 44
51 52 53 54

在调用ByteBuffer中的putXXX()方法时,比如如下代码:

ByteBuffer.putInt(value);
ByteBuffer.getInt();

视图缓冲区与之相比有以下三个优势:

1)视图缓冲区不是根据字节进行索引,而是根据其特定于类型的值的大小进行索引;

2)视图缓冲区提供了相对批量get和put方法,这些方法可在缓冲区和数组或相同类型的其他缓冲区之间传输值的连续序列;

3)视图缓冲区可能更高效,这是因为当且仅当其支持的字节缓冲区为直接缓冲区时,它才是直接缓冲区。

先来验证:视图缓冲区不是根据字节进行索引,而是根据其特定于类型的值的大小进行索引。

示例代码如下:

public class Test13_1 {
public static void main(String[] args) throws UnsupportedEncodingException {
    ByteBuffer bytebuffer = ByteBuffer.allocate(10);
    System.out.println("A1=" + bytebuffer.position());
    bytebuffer.putInt(123);
    System.out.println("A2=" + bytebuffer.position());
    bytebuffer.putInt(456);
    System.out.println("A3=" + bytebuffer.position());
    System.out.println();
    IntBuffer intBuffer = IntBuffer.allocate(10);
    System.out.println("B1=" + intBuffer.position());
    intBuffer.put(456);
    System.out.println("B2=" + intBuffer.position());
    intBuffer.put(789);
    System.out.println("B3=" + intBuffer.position());
}
}

程序运行后的结果如下:

A1=0
A2=4
A3=8
B1=0
B2=1
B3=2

从输出的结果来看,ByteBuffer是按字节为单位进行存储,而IntBuffer是按数据类型为单位进行存储。

再来验证:视图缓冲区提供了相对批量get和put方法,这些方法可在缓冲区和数组或相同类型的其他缓冲区之间传输值的连续序列。

示例代码如下:

public class Test13_2 {
public static void main(String[] args) throws UnsupportedEncodingException {
    ByteBuffer bytebuffer = ByteBuffer.allocate(10);
    bytebuffer.putInt(123);
    bytebuffer.putInt(456);
    bytebuffer.flip();
    System.out.println("bytebuffer position=" + bytebuffer.position() + "
        value=" + bytebuffer.getInt());
    System.out.println("bytebuffer position=" + bytebuffer.position() + "
        value=" + bytebuffer.getInt());
    System.out.println("bytebuffer position=" + bytebuffer.position());
    System.out.println();
    IntBuffer intBuffer = IntBuffer.allocate(10);
    intBuffer.put(456);
    intBuffer.put(789);
    intBuffer.flip();
    System.out.println("intBuffer position=" + intBuffer.position() + "
        value=" + intBuffer.get());
    System.out.println("intBuffer position=" + intBuffer.position() + "
        value=" + intBuffer.get());
    System.out.println("intBuffer position=" + intBuffer.position());
}
}

程序运行结果如下:

bytebuffer position=0 value=123
bytebuffer position=4 value=456
bytebuffer position=8
intBuffer position=0 value=456
intBuffer position=1 value=789
intBuffer position=2

最后验证:视图缓冲区可能更高效,这是因为当且仅当其支持的字节缓冲区为直接缓冲区时,它才是直接缓冲区。

示例代码如下:

public class Test13_3 {
public static void main(String[] args) throws UnsupportedEncodingException {
    ByteBuffer bytebuffer = ByteBuffer.allocateDirect(100);
    bytebuffer.putInt(123);
    bytebuffer.putInt(456);
    bytebuffer.flip();
    IntBuffer intBuffer = bytebuffer.asIntBuffer();
    System.out.println(intBuffer.get());
    System.out.println(intBuffer.get());
    System.out.println();
    System.out.println("bytebuffer是直接缓冲区,效率比较快:");
    System.out.println(bytebuffer);
    System.out.println("由于bytebuffer是直接的,所以intBuffer也是直接缓冲区了:");
    System.out.println(intBuffer);
}
}

程序运行结果如下:

123
456
bytebuffer是直接缓冲区,效率比较快:
java.nio.DirectByteBuffer[pos=0 lim=8 cap=100]
由于bytebuffer是直接的,所以intBuffer也是直接缓冲区了:
java.nio.DirectIntBufferS[pos=2 lim=2 cap=2]

1.4.13 设置与获得字节顺序

order()方法与字节数据排列的顺序有关,因为不同的CPU在读取字节时的顺序是不一样的,有的CPU从高位开始读,而有的CPU从低位开始读,当这两种CPU传递数据时就要将字节排列的顺序进行统一,此时order(ByteOrder bo)方法就有用武之地了,它的作用就是设置字节的排列顺序。

什么是高位和低位呢?如果是16位(双字节)的数据,如FF1A,高位是FF,低位是1A。如果是32位的数据,如3F68415B,高位字是3F68,低位字是415B,右边是低位,左边是高位。

ByteOrder order()方法的作用:获取此缓冲区的字节顺序。新创建的字节缓冲区的顺序始终为BIG_ENDIAN。在读写多字节值以及为此字节缓冲区创建视图缓冲区时,使用该字节顺序。

1)public static final ByteOrder BIG_ENDIAN:表示BIG-ENDIAN字节顺序的常量。按照此顺序,多字节值的字节顺序是从最高有效位到最低有效位的。

2)public static final ByteOrder LITTLE_ENDIAN:表示LITTLE-ENDIAN字节顺序的常量。按照此顺序,多字节值的字节顺序是从最低有效位到最高有效位的。

order(ByteOrder bo)方法的作用:修改此缓冲区的字节顺序,在默认的情况下,字节缓冲区的初始顺序始终是BIG_ENDIAN。

示例代码如下:

public class Test14 {
public static void main(String[] args) throws UnsupportedEncodingException {
    int value = 123456789;
    ByteBuffer bytebuffer1 = ByteBuffer.allocate(4);
    System.out.print(bytebuffer1.order() + " ");
    System.out.print(bytebuffer1.order() + " ");
    bytebuffer1.putInt(value);
    byte[] byteArray = bytebuffer1.array();
    for (int i = 0; i < byteArray.length; i++) {
        System.out.print(byteArray[i] + " ");
    }
    System.out.println();
    bytebuffer1 = ByteBuffer.allocate(4);
    System.out.print(bytebuffer1.order() + " ");
    bytebuffer1.order(ByteOrder.BIG_ENDIAN);
    System.out.print(bytebuffer1.order() + " ");
    bytebuffer1.putInt(value);
    byteArray = bytebuffer1.array();
    for (int i = 0; i < byteArray.length; i++) {
        System.out.print(byteArray[i] + " ");
    }
    System.out.println();
    bytebuffer1 = ByteBuffer.allocate(4);
    System.out.print(bytebuffer1.order() + " ");
    bytebuffer1.order(ByteOrder.LITTLE_ENDIAN);
    System.out.print(bytebuffer1.order() + " ");
    bytebuffer1.putInt(value);
    byteArray = bytebuffer1.array();
    for (int i = 0; i < byteArray.length; i++) {
        System.out.print(byteArray[i] + " ");
    }
}
}

程序运行结果如下:

BIG_ENDIAN BIG_ENDIAN 7 91-51 21
BIG_ENDIAN BIG_ENDIAN 7 91-51 21
BIG_ENDIAN LITTLE_ENDIAN 21-51 91 7

如果字节顺序不一致,那么在获取数据时就会出现错误的值,示例代码如下:

public class Test14_1 {
public static void main(String[] args) throws UnsupportedEncodingException {
    ByteBuffer byteBuffer1 = ByteBuffer.allocate(8);
    byteBuffer1.order(ByteOrder.BIG_ENDIAN);
    byteBuffer1.putInt(123);
    byteBuffer1.putInt(567);
    byteBuffer1.flip();
    System.out.println(byteBuffer1.getInt());
    System.out.println(byteBuffer1.getInt());
    ByteBuffer byteBuffer2 = ByteBuffer.wrap(byteBuffer1.array());
    byteBuffer2.order(ByteOrder.LITTLE_ENDIAN);
    System.out.println(byteBuffer2.getInt());
    System.out.println(byteBuffer2.getInt());
}
}

运行结果就是错误的值:

123
567
2063597568
922877952

1.4.14 创建只读缓冲区

asReadOnlyBuffer()方法的作用:创建共享此缓冲区内容的新的只读字节缓冲区。新缓冲区的内容将为此缓冲区的内容。此缓冲区内容的更改在新缓冲区中是可见的,但新缓冲区将是只读的并且不允许修改共享内容。两个缓冲区的位置、限制和标记值是相互独立的。新缓冲区的容量、限制、位置和标记值将与此缓冲区相同。

示例代码如下:

public class Test15 {
public static void main(String[] args) throws UnsupportedEncodingException {
    byte[] byteArrayIn = { 1, 2, 3, 4, 5 };
    ByteBuffer bytebuffer1 = ByteBuffer.wrap(byteArrayIn);
    ByteBuffer bytebuffer2 = bytebuffer1.asReadOnlyBuffer();
    System.out.println("bytebuffer1.isReadOnly()=" + bytebuffer1.isReadOnly());
    System.out.println("bytebuffer2.isReadOnly()=" + bytebuffer2.isReadOnly());
    bytebuffer2.rewind();
    bytebuffer2.put((byte) 123);
}
}

程序运行结果如下:

bytebuffer1.isReadOnly()=false
bytebuffer2.isReadOnly()=true
Exception in thread "main" java.nio.ReadOnlyBufferException
    at java.nio.HeapByteBufferR.put(HeapByteBufferR.java:172)
    at ByteBufferAPITest.Test15.main(Test15.java:14)

1.4.15 压缩缓冲区

compact()方法的作用:压缩此缓冲区(可选操作),将缓冲区的当前位置和限制之间的字节(如果有)复制到缓冲区的开始处,即将索引p = position()处的字节复制到索引0处,将索引p + 1处的字节复制到索引1处,依此类推,直到将索引limit() -1处的字节复制到索引n = limit() -1- p处。然后,将缓冲区的位置设置为n+1,并将其限制设置为其容量。如果已定义了标记,则丢弃它。将缓冲区的位置设置为复制的字节数,而不是0,以便调用此方法后可以紧接着调用另一个相对put方法。

压缩compact执行的过程如图1-21所示。

图1-21 压缩compact执行的过程

将缓冲区中的数据写出之后调用此方法,以防写出不完整。例如,以下循环语句通过buf缓冲区将字节从一个端点复制到另一个端点:

buf.clear();            //还原缓冲区的状态
while (in.read(buf) >= 0 || buf.position ! = 0) {
    buf.flip();
    out.write(buf);
    buf.compact();     //执行压缩操作
}

示例代码如下:

public class Test16 {
public static void main(String[] args) throws UnsupportedEncodingException {
    ByteBuffer byteBuffer1 = ByteBuffer.wrap(new byte[] { 1, 2, 3, 4, 5, 6 });
    System.out.println("A capacity=" + byteBuffer1.capacity() + " position=" +
        byteBuffer1.position() + " limit="
            + byteBuffer1.limit());
    System.out.println("1 getValue=" + byteBuffer1.get());
    System.out.println("B capacity=" + byteBuffer1.capacity() + " position=" +
        byteBuffer1.position() + " limit="
            + byteBuffer1.limit());
    System.out.println("2 getValue=" + byteBuffer1.get());
    System.out.println("C capacity=" + byteBuffer1.capacity() + " position=" +
        byteBuffer1.position() + " limit="
            + byteBuffer1.limit());
    byteBuffer1.compact();
    System.out.println("byteBuffer1.compact()");
    System.out.println("D capacity=" + byteBuffer1.capacity() + " position=" +
        byteBuffer1.position() + " limit="
            + byteBuffer1.limit());
    byte[] getByteArray = byteBuffer1.array();
    for (int i = 0; i < getByteArray.length; i++) {
        System.out.print(getByteArray[i] + " ");
    }
}
}

程序运行结果如下:

A capacity=6 position=0 limit=6
1 getValue=1
B capacity=6 position=1 limit=6
2 getValue=2
C capacity=6 position=2 limit=6
byteBuffer1.compact()
D capacity=6 position=4 limit=6
3 4 5 6 5 6

可以在使用完compact()方法后再使用flip()方法读取压缩后的数据内容。

1.4.16 比较缓冲区的内容

比较缓冲区的内容是否相同有两种方法:equals()和compareTo()。这两种方法还是有使用细节上的区别,先来看一下ByteBuffer类中的equals()方法的源代码:

public boolean equals(Object ob) {
    if (this == ob)
        return true;
    if (! (ob instanceof ByteBuffer))
        return false;
    ByteBuffer that = (ByteBuffer)ob;
    if (this.remaining() ! = that.remaining())
        return false;
    int p = this.position();
    for (int i = this.limit() -1, j = that.limit() -1; i >= p; i--, j--)
        if (! equals(this.get(i), that.get(j)))
            return false;
    return true;
}

从equals()方法的源代码中可以分析出运算的4个主要逻辑。

1)判断是不是自身,如果是自身,则返回为true。

2)判断是不是ByteBuffer类的实例,如果不是,则返回false。

3)判断remaining()值是否一样,如果不一样,则返回false。

4)判断两个缓冲区中的position与limit之间的数据是否完全一样,只要有一个字节不同,就返回false,否则返回true。

通过源代码来看,两个缓冲区的capacity可以不相同,说明equals()方法比较的是position到limit的内容是否完全一样。

1)验证:判断是不是自身,如果是自身,则返回为true。

示例代码如下:

public class Test17_1 {
public static void main(String[] args) throws UnsupportedEncodingException {
    byte[] byteArrayIn1 = { 1, 2, 3, 4, 5 };
    ByteBuffer bytebuffer1 = ByteBuffer.wrap(byteArrayIn1);
    System.out.println("A=" + bytebuffer1.equals(bytebuffer1));
}
}

程序运行结果如下:

A=true

2)验证:判断是不是ByteBuffer类的实例,如果不是,则返回false。

示例代码如下:

public class Test17_2 {
public static void main(String[] args) throws UnsupportedEncodingException {
    byte[] byteArrayIn1 = { 1, 2, 3, 4, 5 };
    int[] intArrayIn2 = { 1, 2, 3, 4, 5 };
    ByteBuffer bytebuffer1 = ByteBuffer.wrap(byteArrayIn1);
    IntBuffer intbuffer2 = IntBuffer.wrap(intArrayIn2);
    System.out.println("A=" + bytebuffer1.equals(intbuffer2));
}
}

程序运行结果如下:

A=false

3)验证:判断remaining()值是否一样,如果不一样,则返回false。

示例代码如下:

public class Test17_3 {
public static void main(String[] args) throws UnsupportedEncodingException {
    byte[] byteArrayIn1 = { 3, 4, 5 };
    byte[] byteArrayIn2 = { 1, 2, 3, 4, 5, 6, 7, 8 };
    ByteBuffer bytebuffer1 = ByteBuffer.wrap(byteArrayIn1);
    ByteBuffer bytebuffer2 = ByteBuffer.wrap(byteArrayIn2);
    bytebuffer1.position(0);
    bytebuffer2.position(3);
    System.out.println("A=" + bytebuffer1.equals(bytebuffer2));
    System.out.println("bytebuffer1.remaining()=" + bytebuffer1.remaining());
    System.out.println("bytebuffer2.remaining()=" + bytebuffer2.remaining());
}
}

程序运行结果如下:

A=false
bytebuffer1.remaining()=3
bytebuffer2.remaining()=5

4)验证:判断两个缓冲区中的position与limit之间的数据是否完全一样,只要有一个字节不同,就返回false,否则返回true。

示例代码如下:

public class Test17_4 {
public static void main(String[] args) throws UnsupportedEncodingException {
    byte[] byteArrayIn1 = { 3, 4, 5 };
    byte[] byteArrayIn2 = { 1, 2, 3, 4, 5, 6, 7, 8 };
    ByteBuffer bytebuffer1 = ByteBuffer.wrap(byteArrayIn1);
    ByteBuffer bytebuffer2 = ByteBuffer.wrap(byteArrayIn2);
    bytebuffer1.position(0);
    bytebuffer1.limit(3);
    bytebuffer2.position(2);
    bytebuffer2.limit(5);
    System.out.println("A=" + bytebuffer1.equals(bytebuffer2));
    System.out.println("AA1 bytebuffer1.remaining()=" + bytebuffer1.remaining());
    System.out.println("AA2 bytebuffer2.remaining()=" + bytebuffer2.remaining());
    bytebuffer2.put(3, (byte) 44);
    System.out.println("B=" + bytebuffer1.equals(bytebuffer2));
    System.out.println("BB1 bytebuffer1.remaining()=" + bytebuffer1.remaining());
    System.out.println("BB2 bytebuffer2.remaining()=" + bytebuffer2.remaining());
}
}

程序运行结果如下:

A=true
AA1 bytebuffer1.remaining()=3
AA2 bytebuffer2.remaining()=3
B=false
BB1 bytebuffer1.remaining()=3
BB2 bytebuffer2.remaining()=3

以上的示例展示了equals(Object object)方法的使用。

compareTo(ByteBuffer that)方法的作用:将此缓冲区与另一个缓冲区进行比较。比较两个字节缓冲区的方法是按字典顺序比较它们的剩余元素序列,而不考虑每个序列在其对应缓冲区中的起始位置。该方法的源代码如下:

public int compareTo(ByteBuffer that) {
    int n = this.position() + Math.min(this.remaining(), that.remaining());
    for (int i = this.position(), j = that.position(); i < n; i++, j++) {
        int cmp = compare(this.get(i), that.get(j));
        if (cmp ! = 0)
            return cmp;
    }
    return this.remaining() - that.remaining();
}

从compareTo(ByteBuffer that)方法的源代码中可以分析出运算的3个主要逻辑。

1)判断两个ByteBuffer的范围是从当前ByteBuffer对象的当前位置开始,以两个ByteBuffer对象最小的remaining()值结束说明判断的范围是remaining的交集。

2)如果在开始与结束的范围之间有一个字节不同,则返回两者的减数,Byte类中的源代码如下:

public static int compare(byte x, byte y) {
    return x - y;
}

3)如果在开始与结束的范围之间每个字节都相同,则返回两者remaining()的减数。

通过源代码来看,两个缓冲区的capacity可以不相同,这个特性和equals()方法一致。

1)验证:如果在开始与结束的范围之间有一个字节不同,则返回两者的减数。

示例代码如下:

public class Test17_5 {
public static void main(String[] args) throws UnsupportedEncodingException {
    byte[] byteArrayIn1 = { 3, 4, 5 };
    byte[] byteArrayIn2 = { 1, 2, 3, 104, 5, 6, 7, 8, 9, 10 };
 
    ByteBuffer bytebuffer1 = ByteBuffer.wrap(byteArrayIn1);
    ByteBuffer bytebuffer2 = ByteBuffer.wrap(byteArrayIn2);
    bytebuffer1.position(0);
    bytebuffer2.position(2);
    System.out.println("A=" + bytebuffer1.compareTo(bytebuffer2));
}
}

程序运行结果如下:

A=-100

2)验证:如果在开始与结束的范围之间每个字节都相同,则返回两者remaining()的减数。

示例代码如下:

public class Test17_6 {
public static void main(String[] args) throws UnsupportedEncodingException {
    byte[] byteArrayIn1 = { 3, 4, 5 };
    byte[] byteArrayIn2 = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
    ByteBuffer bytebuffer1 = ByteBuffer.wrap(byteArrayIn1);
    ByteBuffer bytebuffer2 = ByteBuffer.wrap(byteArrayIn2);
    bytebuffer1.position(0);
    bytebuffer2.position(2);
    System.out.println("A=" + bytebuffer1.compareTo(bytebuffer2));
}
}

程序运行结果如下:

A=-5

通过查看equals(Object obj)和compareTo(ByteBuffer that)方法的源代码可以发现,这两个方法的逻辑就是当前position到limit之间的字符是否逐个相同。

1.4.17 复制缓冲区

ByteBuffer duplicate()方法的作用:创建共享此缓冲区内容的新的字节缓冲区。新缓冲区的内容将为此缓冲区的内容。此缓冲区内容的更改在新缓冲区中是可见的,反之亦然。在创建新的缓冲区时,容量、限制、位置和标记的值将与此缓冲区相同,但是这两个缓冲区的位置、界限和标记值是相互独立的。当且仅当此缓冲区为直接缓冲区时,新缓冲区才是直接缓冲区。当且仅当此缓冲区为只读时,新缓冲区才是只读的。

下面的示例代码演示了duplicate()方法与slice()方法的区别。

public class Test18 {
public static void main(String[] args) throws UnsupportedEncodingException {
    byte[] byteArrayIn1 = { 1, 2, 3, 4, 5 };
    ByteBuffer bytebuffer1 = ByteBuffer.wrap(byteArrayIn1);
    bytebuffer1.position(2);
    System.out.println("bytebuffer1 capacity=" + bytebuffer1.capacity() +
        " limit=" + bytebuffer1.limit()
            + " position=" + bytebuffer1.position());
    ByteBuffer bytebuffer2 = bytebuffer1.slice();
    ByteBuffer bytebuffer3 = bytebuffer1.duplicate();
    // bytebuffer4和bytebuffer1指向的地址是一个
    // 所以在debug中的id是一样的
    ByteBuffer bytebuffer4 = bytebuffer1;
    System.out.println("bytebuffer2 capacity=" + bytebuffer2.capacity() +
        " limit=" + bytebuffer2.limit()
            + " position=" + bytebuffer2.position());
    System.out.println("bytebuffer3 capacity=" + bytebuffer3.capacity() +
        " limit=" + bytebuffer3.limit()
            + " position=" + bytebuffer3.position());
    bytebuffer2.position(0);
    for (int i = bytebuffer2.position(); i < bytebuffer2.limit(); i++) {
        System.out.print(bytebuffer2.get(i) + " ");
    }
    System.out.println();
    bytebuffer3.position(0);
    for (int i = bytebuffer3.position(); i < bytebuffer3.limit(); i++) {
        System.out.print(bytebuffer3.get(i) + " ");
    }
}
}

程序运行结果如下:

bytebuffer1 capacity=5 limit=5 position=2
bytebuffer2 capacity=3 limit=3 position=0
bytebuffer3 capacity=5 limit=5 position=2
3 4 5
1 2 3 4 5

duplicate()方法和slice()方法都会创建新的缓冲区对象,效果如图1-22所示。

图1-22 新创建的缓冲区对象

使用duplicate()方法和slice()方法能创建新的缓冲区,但这些新缓冲区使用的还是原来缓冲区中的byte[]字节数组。

下面验证使用duplicate()方法创建新的缓冲区后,在新缓冲区中添加数据时,被复制的缓冲区中的值也发生改变,说明这两个缓冲区用的是同一个byte[],代码如下:

public class Test19 {
public static void main(String[] args) throws UnsupportedEncodingException {
    byte[] byteArrayIn1 = { 1, 2, 3, 4, 5 };
    ByteBuffer bytebuffer1 = ByteBuffer.wrap(byteArrayIn1);
    ByteBuffer bytebuffer2 = bytebuffer1.duplicate();
    System.out.println("A capacity=" + bytebuffer1.capacity() + " position=" +
        bytebuffer1.position() + " limit="
            + bytebuffer1.limit());
    System.out.println("B capacity=" + bytebuffer2.capacity() + " position=" +
    bytebuffer2.position() + " limit="
            + bytebuffer2.limit());
    bytebuffer2.put(1, (byte) 22);
    bytebuffer2.position(3);
    System.out.println("C capacity=" + bytebuffer1.capacity() + " position=" +
        bytebuffer1.position() + " limit="
            + bytebuffer1.limit());
    System.out.println("D capacity=" + bytebuffer2.capacity() + " position="
        + bytebuffer2.position() + " limit="
            + bytebuffer2.limit() + " bytebuffer2位置是3,而bytebuffer1还是
                  0,说明位置、限制和标记值是独立的");
    bytebuffer1.position(0);
    for (int i = 0; i < bytebuffer1.limit(); i++) {
        System.out.print(bytebuffer1.get(i) + " ");
    }
}
}

程序运行结果如下:

A capacity=5 position=0 limit=5
B capacity=5 position=0 limit=5
C capacity=5 position=0 limit=5
D capacity=5 position=3 limit=5 bytebuffer2位置是3,而bytebuffer1还是0,说明位置、
    限制和标记值是独立的
1 22 3 4 5

1.4.18 对缓冲区进行扩容

一旦创建缓冲区,则容量(capacity)就不能被改变。如果想对缓冲区进行扩展,就得进行相应的处理,示例代码如下:

public class Test20 {
public static ByteBuffer extendsSize(ByteBuffer buffer, int extendsSize) {
    ByteBuffer newBytebuffer = ByteBuffer.allocate(buffer.capacity() + extendsSize);
    newBytebuffer.put(buffer);
    return newBytebuffer;
}
public static void main(String[] args) throws UnsupportedEncodingException {
    byte[] byteArrayIn1 = { 1, 2, 3, 4, 5 };
    ByteBuffer bytebuffer1 = ByteBuffer.wrap(byteArrayIn1);
    ByteBuffer bytebuffer2 = extendsSize(bytebuffer1, 2);
    byte[] newArray = bytebuffer2.array();
    for (int i = 0; i < newArray.length; i++) {
        System.out.print(newArray[i] + " ");
    }
}
}

程序运行结果如下:

1 2 3 4 5 0 0