NIO与网络编程——缓冲区的使用

PunkLu 2020年01月06日 163次浏览
缓冲区的使用

缓冲区简介

缓冲区(Buffer)在NIO的使用中占据了很高的地位,因为数据就是存放到缓冲区中,并对数据进行处理的。在CRUD操作时,都是对缓冲区进行处理的。每种缓冲区都有自己独有的API。

在使用传统的I/O流API时,如InputStream和OutputStream,以及Reader和Writer联合使用时,常常把字节流中的数据放入byte[]字节数组或char[]字符数组中,也可以从byte[]或char[]数组中获取数据来实现功能上的需求,但由于在Java语言中对array数组自身进行操作的API非常少,常用的操作仅仅是length属性和下标[x]了,在JDK中也没有提供更加方便操作数组中数据的API,如果对数组中的数据进行高级处理,就得自己写代码实现,这个问题可以使用NIO技术中的缓冲区Buffer类来解决,它提供了很多工具方法。

Buffer类是一个抽象类,它具有7个直接子类,分别是ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer,也就是缓冲区中存储的数据类型并不像普通I/O流只能存储byte或char数据类型,Buffer类能存储的数据类型是多样的。

类java.lang.StringBuffer是在lang包下的,而在nio包下并没有提供java.nio.StringBuffer缓冲区,在NIO中存储字符的缓冲区可以使用CharBuffer类。NIO中的Buffer是一个用于存储基本数据类型值的容器,它以类似于数组有序的方式来存储和组织数据。每个基本数据类型(除去boolean)都有一个子类与之对应。

Buffer类的使用

Buffer类是抽象类,并不能直接实例化,而其子类:ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer和ShortBuffer也是抽象类。这七个子类也是抽象类,也不能不能被直接new实例化。创建这些类的对象时,需要将上面七种数据类型的数组包装(wrap)进缓冲区中,此时就需要借助静态方法wrap()进行实现。wrap()方法的作用是将数组放入缓冲区中,来构建存储不同数据类型的缓冲区。

包装数据与获得容量

在NIO技术的缓冲区中,存在4个核心技术点,分别是:

  1. capacity 容量
  2. limit 限制
  3. position 位置
  4. mark 标记

capacity代表包含元素的数量。缓冲区的capacity不能为负数,并且不能更改。

int capacity()方法的作用:返回此缓冲区的数量

代码示例:

public class TestCapacity {

    public static void main(String[] args) {
        byte[] byteArray = new byte[]{1,2,3};
        short[] shortArray = new short[]{1,2,3,4};
        int[] intArray = new int[]{1,2,3,4,5};
        long[] longArray = new long[]{1,2,3,4,5,6};
        float[] floatArray = new float[]{1,2,3,4,5,6,7};
        double[] doubleArray = new double[]{1,2,3,4,5,6,7,8};
        char[] charArray = new char[]{'a','b','c','d'};

        ByteBuffer byteBuffer = ByteBuffer.wrap(byteArray);
        ShortBuffer shortBuffer = ShortBuffer.wrap(shortArray);
        IntBuffer intBuffer = IntBuffer.wrap(intArray);
        LongBuffer longBuffer = LongBuffer.wrap(longArray);
        FloatBuffer floatBuffer = FloatBuffer.wrap(floatArray);
        DoubleBuffer doubleBuffer = DoubleBuffer.wrap(doubleArray);
        CharBuffer charBuffer = CharBuffer.wrap(charArray);

        System.out.println("bytebuffer=" + byteBuffer.getClass().getName());
        System.out.println("shortbuffer=" + shortBuffer.getClass().getName());
        System.out.println("intbuffer=" + intBuffer.getClass().getName());
        System.out.println("longbuffer=" + longBuffer.getClass().getName());
        System.out.println("floatbuffer=" + floatBuffer.getClass().getName());
        System.out.println("doublebuffer=" + doubleBuffer.getClass().getName());
        System.out.println("charbuffer=" + charBuffer.getClass().getName());

        System.out.println();

        System.out.println("bytebuffer.capacity=" + byteBuffer.capacity());
        System.out.println("shortbuffer.capacity=" + shortBuffer.capacity());
        System.out.println("intbuffer.capacity=" + intBuffer.capacity());
        System.out.println("longbuffer.capacity=" + longBuffer.capacity());
        System.out.println("floatbuffer.capacity=" + floatBuffer.capacity());
        System.out.println("doublebuffer.capacity=" + doubleBuffer.capacity());
        System.out.println("charbuffer.capacity=" + charBuffer.capacity());
    }
}

缓冲区的限制

方法int limit()的作用:返回此缓冲区的限制。

方法Buffer limit(int newLimit)的作用:设置此缓冲区的限制

缓冲区中的限制代表第一个不应该读取或写入元素的index(索引)。缓冲区的限制(limit)不能为负,并且limit不能大于其capacity。如果position大于新的limit。则将position设置为新的limit。如果mark已定义且大于新的limit,则丢弃该mark。

当limit为4时,只有0、1、2、3这四个位置可存储数据,后面的位置不可以存放数据。limit使用的场景就是当反复地向缓冲区中存取数据时使用。比如,通过设置limit来实现只读取limit前面的内容。

代码示例:

public class TestLimit {


    public static void main(String[] args) {
        char[] charArray = new char[]{'a','b','c','d','e'};
        CharBuffer buffer = CharBuffer.wrap(charArray);
        System.out.println("A capacity()=" + buffer.capacity() + " limit()=" + buffer.limit());
        buffer.limit(3);
        System.out.println();
        System.out.println("B capacity()=" + buffer.capacity() + " limit()=" + buffer.limit());
        buffer.put(0,'o');
        buffer.put(1,'p');
        buffer.put(2,'q');
        buffer.put(3,'r');  // 此位置是第一个不可读不可写的索引
        buffer.put(4,'s');
        buffer.put(5,'t');
        buffer.put(6,'u');
    }
}

此程序运行后在第21行出现IndexOutOfBoundsException异常,因为limit之后不能放置元素。

位置获取与设置

方法int position()的作用:返回此缓冲区的位置。

方法Buffer position(int newPosition)的作用:设置此缓冲区新的位置。

位置代表“下一个”要读取或写入元素的index(索引),缓冲区的position(位置)不能为负,并且position不能大于其limit。如果mark已定义且大于新的position,则丢弃该mark。

代码示例:

public class TestPosition {

    public static void main(String[] args) {
        char[] charArray = new char[]{'a','b','c','d'};
        CharBuffer charBuffer = CharBuffer.wrap(charArray);
        System.out.println("A capacity()=" + charBuffer.capacity() + " limit()=" + charBuffer.limit()
                                            + " position()=" + charBuffer.position());
        charBuffer.position(2);
        System.out.println("B capacity()=" + charBuffer.capacity() + " limit()=" + charBuffer.limit()
                                            + " position()=" + charBuffer.position());
        charBuffer.put("z");
        for (int i = 0; i < charArray.length; i++) {
            System.out.print(charArray[i] + " ");
        }
    }
}

执行结果:

A capacity()=4 limit()=4 position()=0
B capacity()=4 limit()=4 position()=2
a b z d 

从执行结果可以看出,position是下一个读取或写入的index。

剩余空间大小获取

方法 int remaining()的作用:返回”当前位置“(即position)与limit之间的元素数。

方法remaining()的示例代码:

public class TestRemaining {

    public static void main(String[] args) {
        char[] charArray = new char[]{'a','b','c','d','e'};
        CharBuffer charBuffer = CharBuffer.wrap(charArray);
        System.out.println("A capacity()=" + charBuffer.capacity() + " limit()=" +charBuffer.limit()
                                            + " position()=" + charBuffer.position());
        charBuffer.position(2);
        System.out.println("B capacity()=" + charBuffer.capacity() + " limit()=" +charBuffer.limit()
                + " position()=" + charBuffer.position());
        System.out.println("C remaining()=" + charBuffer.remaining());
    }
}

使用Buffer mark()方法进行标记

方法Buffer mark()的作用:在此缓冲区的位置设置标记。

缓冲区的标记是一个索引,在调用reset()方法时,会将缓冲区的position位置重置为该索引。标记(mark)并不是必须的。mark不能将其定义为负数,并且不能让它大于position。如果定义了mark,则在将position或limit调整为小于该mark的值时,该mark被丢弃,丢弃后mark的值是-1.如果未定义mark,那么调用reset()方法将导致抛出InvalidMarkException异常。

代码示例:

public class TestMark {

    public static void main(String[] args) {
        byte[] byteArray = new byte[]{1,2,3};
        ByteBuffer byteBuffer = ByteBuffer.wrap(byteArray);
        System.out.println("bytebuffer.capacity=" + byteBuffer.capacity());
        System.out.println();

        byteBuffer.position(1);
        byteBuffer.mark();  // 在当前位置1设置mark

        System.out.println("bytebuffer.position=" + byteBuffer.position());

        byteBuffer.position(2); // 改变位置
        byteBuffer.reset(); // 位置重置
        System.out.println();

        // 回到位置为1处

        System.out.println("bytebuffer.position=" + byteBuffer.position());
    }


}

运行结果:

bytebuffer.capacity=3

bytebuffer.position=1

bytebuffer.position=1

从运行结果可以看出,在位置1处设置完mark后,将position设置为2,然后进行reset后,position又回到了1。

判断只读

boolean isReadOnly()方法的作用:判断此缓冲区是否为直接缓冲区。

非直接缓冲区保存数据的时候,通过ByteBuffer向硬盘保存数据时需要将数据暂存在JVM的中间缓冲区,如果有频繁操作数据的情况发生,则在每次操作时都会将数据暂存在JVM的中间缓冲区,再交给ByteBuffer处理,这样就降低了吞吐量,提高了内存占用率,所以非直接缓冲区的这个弊端就由直接缓冲区解决了。

如果使用直接缓冲区来实现两端数据交互,则直接在内核空间中就进行了处理,无需JVM创建新的缓冲区,这样就减少了在JVM中创建中间缓冲区的步骤,增加了程序运行效率。

示例代码:

public class TestDirect {

    public static void main(String[] args) {
        ByteBuffer byteBuffer = ByteBuffer.allocateDirect(100);
        System.out.println(byteBuffer.isDirect());
    }
}

执行结果:

true

说明ByteBuffer.allocateDirect()方法创建出了直接缓冲区。

还原缓冲区的状态

final Buffer clear()方法的作用:还原缓冲区到初始的状态,包含将位置设置为0,将限制设置为容量,并求其标记,即”一切为默认“。

clear()方法的主要使用场景是在对缓冲区存储数据之前调用此方法,例如:

buf.clear();   // 准备开始向缓冲区中写数据了,缓冲区的状态要通过clear()进行还原
in.read(buf);  // 从in开始读数据,将数据写入buf中

需要注意的是,clear并不能真正清除数据,它只能将mark、limit、position设置为初始值,真正的清除旧数据是在随后的写入新数据时。

示例代码:

public class TestClear {

    public static void main(String[] args) {
        byte[] byteArray = new byte[]{1,2,3};

        ByteBuffer byteBuffer = ByteBuffer.wrap(byteArray);

        byteBuffer.position(2);
        byteBuffer.limit(3);
        byteBuffer.mark();

        byteBuffer.clear();

        System.out.println("bytebuffer.position=" + byteBuffer.position());
        System.out.println();

        System.out.println("bytebuffer.limit=" + byteBuffer.limit());
        System.out.println();

        try{
            byteBuffer.reset();
        }catch (InvalidMarkException e){
            System.out.println("bytebuffer mark 丢失");
        }
    }

}

运行结果:

bytebuffer.position=0

bytebuffer.limit=3

bytebuffer mark 丢失

对缓冲区进行反转

final Buffer flip()方法的作用:反转此缓冲区,首先将限制limit设置为当前位置,然后将位置设置为0。如果已经定义了标记,则丢弃该标记。

flip()方法的内部源代码如下:

public final Buffer flip(){
        limit = position;
        position = 0;
        mark = -1;
        return this;
}

当向缓冲区中存储数据,然后再从缓冲区中读取这些数据时,就是使用flip()方法的最佳时机。

判断是否有底层实现的数组

final boolean hasArray()方法的作用:判断此缓冲区是否具有可访问的底层实现数组。该方法内部源代码为:

public final boolean hasArray(){
        return (hb != null) && !isReadOnly;
}

示例代码为:

public class TestHasArray {

    public static void main(String[] args) {
        ByteBuffer byteBuffer = ByteBuffer.allocate(100);
        byteBuffer.put((byte)1);
        byteBuffer.put((byte)2);
        System.out.println(byteBuffer.hasArray());
    }
}

执行结果:

true

判断当前位置与限制之间是否有剩余元素

final boolean hasRemaining()方法的作用:判断在当前位置position和限制limit之间是否有元素。该方法的内部源码如下:

public final boolean hasRemaining(){
        return position < limit;
}

final int remaining()方法的作用:返回“当前位置”position与限制limit之间的元素个数。该方法的内部源代码如下:

public final int remaining(){
        return limit - position;
}

示例代码为:

public class TestHasRemaining {

    public static void main(String[] args) {
        byte[] byteArray = new byte[]{1,2,3};
        ByteBuffer byteBuffer = ByteBuffer.wrap(byteArray);

        byteBuffer.limit(3);

        byteBuffer.position(2);

        System.out.println("bytebuffer.hasRemaining=" + byteBuffer.hasRemaining()
                            + " bytebuffer.remaining=" + byteBuffer.remaining() );
    }
}

程序运行结果:

bytebuffer.hasRemaining=true bytebuffer.remaining=1

这两个方法可以在读写缓冲区中的数据时使用,示例代码:

public class Test13_1 {

    public static void main(String[] args) {
        byte[] byteArray = {1,2,3,4,5,6,7,8,9};
        ByteBuffer byteBuffer = ByteBuffer.wrap(byteArray);
        int remaining = byteBuffer.remaining();
        for (int i = 0; i < remaining; i++) {
            System.out.print(byteBuffer.get() + "  ");
        }

        System.out.println();

        byteBuffer.clear();
        while (byteBuffer.hasRemaining()){
            System.out.print(byteBuffer.get() + "  ");
        }
        System.out.println();

        byteBuffer.clear();
        for (;byteBuffer.hasRemaining() == true;){
            System.out.print(byteBuffer.get() + "  ");
        }
    }
}

程序运行结果:

1  2  3  4  5  6  7  8  9
1  2  3  4  5  6  7  8  9
1  2  3  4  5  6  7  8  9

重绕缓冲区

final Buffer rewind()方法的作用:重绕此缓冲区,将位置设置为0并丢弃标记mark。该方法的内部源码为:

public final Buffer rewind(){
        position = 0;
        mark = -1;
        return this;
}

在一系列通道“重新写入或获取”的操作之前调用此方法(假设已经设置了限制limit)。例如: out.write(buf); // 将buf的remaining剩余空间的数据输出到out中 buf.rewind(); // rewind重绕缓冲区 buf.get(array); // 从缓冲区获取数据保存到array中

rewind()方法的通俗解释是“标记清除,position值归0,limit值不变”。 rewind()方法常在重新读取缓冲区中数据时使用。

rewind()、clear()、和flip()方法在官方文档中的解释如下:

rewind():使缓冲区为“重新读取”已包含的数据做准备,它使限制保持不变,将位置设置为0.
clear():使缓冲区为一系列新的通道读取或相对put(value)操作做好准备,即它将限制设置为容量大小,将位置设置为0。
flip():使缓冲区为一系列新的通道写入或相对get(value)操作做好准备,即它将限制设置为当前位置,然后将位置设置为0。

获得偏移量

final int arrayOffset()方法的作用:返回此缓冲区的底层实现数组中第一个缓冲区元素的偏移量

示例代码:

public class TestArrayOffset {

    public static void main(String[] args) {
        byte[] byteArray = new byte[]{1,2,3};
        ByteBuffer byteBuffer = ByteBuffer.wrap(byteArray);
        System.out.println("bytebuffer.arrayOffset=" + byteBuffer.arrayOffset());
    }
}

运行结果:

bytebuffer.arrayOffset=0

使用List.toArray(T[])转成数组类型

如果想在List中存储ByteBuffer数据类型,可以使用List中的toArray()方法转成ByteBuffer[]数组类型。

示例代码:

public class TestToArray {

    public static void main(String[] args) {
        ByteBuffer buffer1 = ByteBuffer.wrap(new byte[]{'a','b','c'});
        ByteBuffer buffer2 = ByteBuffer.wrap(new byte[]{'x','y','z'});
        ByteBuffer buffer3 = ByteBuffer.wrap(new byte[]{'1','2','3'});

        List<ByteBuffer> list = new ArrayList<>();
        list.add(buffer1);
        list.add(buffer2);
        list.add(buffer3);

        ByteBuffer[] byteBufferArray = new ByteBuffer[list.size()];
        list.toArray(byteBufferArray);

        System.out.println(byteBufferArray.length);

        for (int i = 0; i < byteBufferArray.length; i++) {
            ByteBuffer eachByteBuffer = byteBufferArray[i];
            while (eachByteBuffer.hasRemaining()){
                System.out.print((char)eachByteBuffer.get());
            }
            System.out.println();
        }
    }
}

ByteBuffer类的使用

ByteBuffer类是Buffer类的子类,可以在缓冲区中以字节为单位对数据进行存取。

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

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

如果字节缓冲区为直接字节缓冲区,则JVM会尽量在直接字节缓冲区上执行本机I/O操作,也就是直接对内核空间进行访问,以提高运行效率。

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

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

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

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

示例代码:

public class TestAllocate {

    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=0limit=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()方法创建的直接缓冲区释放内存有两种方法:

1、手动释放空间,示例代码TestAllocateDirectGC
2、交给JVM进行处理,示例代码TestAllocateDirectJVMAutoGC

包装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)值
3、length:将新缓冲区的界限设置为offset+length

示例代码:

public class TestWrap {

    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());

    }
}

从运行结果可以看出,offset参数只是设置缓冲区的position值,而length+position则等于limit的值。

put(byte b)和get()方法的使用与position自增特性

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

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

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

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

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

put(byte[] src,int offset,int length)方法的作用:相对批量put方法,此方法将把给定数据源src中从offset位置开始的length个byte值放到调用此方法的缓冲区的当前postion位置后的length个元素,如果缓冲区原本有值则将被覆盖掉。

put(byte[] src):把src数组的全部内容存储到此缓冲区的当前位置中。

同理,get(byte[] dst,int offset,int length)则是将当前缓冲区中当前position位置开始的length个元素复制到dst数组中的offset位置。

get(byte[] dst):将缓冲区remaining的字节传输到给定的目标数组中。

示例代码:

public class TestPutAndGet {

    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放入缓冲区的第三位
        // 值变成1,2,66,77,88,6,7,8
        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()];

        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(int index,byte b)和get(int index)方法的使用与position不变

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

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

使用绝对位置操作后,位置position并不改变。

put(ByteBuffer src)方法的使用

put(ByteBuffer src)方法的作用:将给定缓冲区中的剩余字节传输到此缓冲区的当前位置中。需要注意的是剩余字节而不是全部。且使用时要注意容量是否足够。

需要注意的是,操作完后,给定缓冲区和当前缓冲区的位置position都增加传输的字节数。

示例代码:

public class TestPut {

    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

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

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

putDouble(double value):将8个包含给定double值的字节按照当前的字节顺序写入到此缓冲区的当前位置,然后将该位置增加8。

putFloat(float value):将4个包含给定float值的字节按照当前的字节顺序写入到此缓冲区的当前位置,然后将该位置增加4.

putInt(int value):4个字节

putLong(long value):8个字节

putShort(short value):2个字节

示例代码:

public class TestPutTypeAndGet {

    public static void main(String[] args) {
        ByteBuffer byteBuffer1 = ByteBuffer.allocate(100);
        byteBuffer1.putChar('a'); // 0-1,char占两个字节
        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);

        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

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

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

示例代码:

public class TestSlice {

    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=5bytebuffer1.capacity=8bytebuffer1.limit=8
bytebuffer2.position=0bytebuffer2.capacity=3bytebuffer2.limit=3
1 2 3 4 5 111 7 8 
1 2 3 4 5 111 7 8 

从运行结果可以看出,对新缓冲区内容的变更对旧缓冲区可见,反之亦然。

在使用slice()方法后,再调用arrayOffset方法时,会出现返回值为非0的情况。示例代码为:

public class TestArrayOffsetAfterSlice {

    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的第一个元素的位置是相对于byteArrayIn1数组中索引值为5的偏移。

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

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

示例代码为:

public class TestAsCharBuffer {

    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() + " ");
        }
    }
}

运行结果:

UTF-8
bytebuffer=java.nio.HeapByteBuffer
charBuffer=java.nio.ByteBufferAsCharBufferB
byteBuffer.position=0 byteBuffer.capacity=15 byteBuffer.limit= 15
charBuffer.position=0 charBuffer.capacity=7 charBuffer.limit= 7
7
 釦 颯  귥 鮽 

从运行结果的乱码来看,并没有将正确的中文获取出来,相反还出现了乱码,出现乱码的原因就是编码不对称导致的,解决办法就是使编码对称,也就是将中文转成字节数组时使用UTF-16BE编码,而使用CharBuffer的子类ByteBufferAsCharBuffer中的get方法时,再以UTF-16BE编码转回中文即可,这样就不会出现中文乱码问题了。

解决乱码问题的方法有以下两种:

public class TestAsCharBuffer1 {

    public static void main(String[] args) throws UnsupportedEncodingException {
        byte[] byteArrayIn1=  "我是中国人".getBytes("utf-16BE");
        // 运行本代码的*.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() + " ");
        }
    }
}
public class TestAsCharBuffer2 {

    public static void main(String[] args) throws UnsupportedEncodingException {
        byte[] byteArrayIn1=  "我是中国人".getBytes("UTF-8");
        // 运行本代码的*.java文件是UTF-8编码,所以运行环境取得的默认编码是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++) {
            // get()方法使用的编码为UTF-16BE
            // UTF-8与UTF-16BE并不是同一种编码
            // 所以这时出现了乱码
            System.out.print(charBuffer.get() + " ");
        }
    }
}

TestAsCharBuffer1是通过获取字节数组时指定编码格式为与get()方法一致的UTF-16BE编码实现正常显示,TestAsCharBuffer2则是通过Charset.forName("utf-8").decode(byteBuffer)时指定CharBuffer编码格式为UTF-8来实现正常显示。

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

public class TestAsCharBufferRef {

    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() + " ");
        }
    }
}

运行结果:

我 为 中 国 人

转换为其它类型的缓冲区

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

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

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

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

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

设置与获得字节顺序

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

右边是低位,左边是高位。

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

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

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

示例代码为:

public class TestOrder {

    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 TestOrderWrong {

    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

创建只读缓冲区

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

示例代码为:

public class TestAsReadOnlyBuffer {

    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
Exception in thread "main" java.nio.ReadOnlyBufferException
bytebuffer2.isReadOnly()=true
	at java.nio.HeapByteBufferR.put(HeapByteBufferR.java:172)
	at tech.punklu.niosocket.chapter1.TestAsReadOnlyBuffer.main(TestAsReadOnlyBuffer.java:16)

比较缓冲区的内容

比较缓冲区的内容是否相同有两种方法:equals(Object obj)和compareTo(ByteBuffer that)。这两个方法的逻辑就是当前postion到limit之间的字符是否逐个相同。

复制缓冲区

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

示例代码:

public class TestDuplicate {

    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()方法能创建新的缓冲区,且两个缓冲区的位置、限制、标记是相互独立的。但这些新缓冲区使用的还是原来缓冲区中的byte[]字节数组。示例代码:

public class TestDumplicateSlice {

    public static void main(String[] args) {
        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 

对缓冲区进行扩容

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

public class TestExtendBufferSize {

    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 

CharBuffer类的API使用

重载append(char)/append(CharSequence)/append(CharSequence,start,end)方法的使用

public CharBuffer append(char c)方法的作用:将指定字符添加到此缓冲区(可选操作)。该调用与以下调用完全相同:dst.put(c)。

public CharBuffer append(CharSequence csq)方法的作用:将指定的字符序列添加到此缓冲区(可选操作)。该调用与以下调用完全相同:dst.put(csq.toString()),又可能没有添加整个序列,这取决于csq的toString实现。

public CharBuffer append(CharSequence scq,int start,int end)方法的作用:将指定字符序列的子序列添加到此缓冲区。该调用与以下调用完全相同:dst.put(csq.subSequence(start,end).toString())。

示例代码:

public class TestAppend {

    public static void main(String[] args) {
        CharBuffer charBuffer = CharBuffer.allocate(15);
        System.out.println("A " + charBuffer.position());
        charBuffer.append("a");
        System.out.println("B " + charBuffer.position());

        charBuffer.append("bcdefg");
        System.out.println("C " + charBuffer.position());
        charBuffer.append("abchijklmn",3,8);
        System.out.println("D "+ charBuffer.position());
        char[] newArray = charBuffer.array();
        for (int i = 0; i < newArray.length; i++) {
            System.out.print(newArray[i] + " ");
        }
        System.out.println();
        System.out.println("charbuffer capacity= "+ charBuffer.capacity());
    }
}

运行结果:

A 0
B 1
C 7
D 12
a b c d e f g h i j k l       
charbuffer capacity= 15

读取相对于当前位置的给定索引处的字符

public final char charAt(int index)方法的作用:读取相对于当前位置的给定索引处的字符。

示例代码:

public class TestCharAt {

    public static void main(String[] args) {
        CharBuffer charBuffer = CharBuffer.allocate(10);
        charBuffer.append("abcdefg");
        charBuffer.position(2);
        System.out.println(charBuffer.charAt(0));
        System.out.println(charBuffer.charAt(1));
        System.out.println(charBuffer.charAt(2));
    }
}

运行结果:

c
d
e

从运行结果可以看出,index的值是从position开始的第index值,而不是从0开始的第index值

put(String src)、int read(CharBuffer target)和subSequence(int start,int end)方法的使用

put(String src)方法的作用:相对批量put方法。此方法将给定源字符串中的所有内容传输到此缓冲区的当前位置。该方法的功能与以下调用完全相同:dst.put(s,0,s.length())

int read(CharBuffer target)方法的作用:试图将当前字符缓冲区中的字符写入指定的字符缓冲区。缓冲区可照原样用作字符的存储库:所做的唯一更改是put操作的结果。不对缓冲区执行翻转或重绕操作。

subSequence(int start,int end)方法的作用:创建表示此缓冲区的指定序列、相对于当前位置的新字符缓冲区。新缓冲区将共享此缓冲区中的内容。新缓冲区的容量将为此缓冲区的容量,其位置将为position()+start,其限制将为position()+end。当且仅当此缓冲区为直接缓存区时,新缓冲区才是直接缓冲区,只读同理。两个参数的意义:

1、start
        子序列中第一个字符相对于前面位置的索引,必须为非负,且不大于remaining()
2、end
        子序列中最后一个字符后面的字符相对于当前位置的索引;必须不小于start且不大于remaining

示例代码:

public class TestSubSequence {

    public static void main(String[] args) throws IOException {
        CharBuffer buffer1 = CharBuffer.allocate(8);
        buffer1.append("ab123456");
        buffer1.position(2);
        buffer1.put("cde");
        buffer1.rewind();
        for (int i = 0; i < buffer1.limit(); i++) {
            System.out.print(buffer1.get());
        }
        System.out.println();

        buffer1.position(1);
        CharBuffer buffer2 = CharBuffer.allocate(4);
        System.out.println("A Buffer2 position=" + buffer2.position());
        buffer1.read(buffer2); // read相当于position是1进行导出
        System.out.println("B buffer2 position=" + buffer2.position());
        buffer2.rewind();
        for (int i = 0; i < buffer2.limit(); i++) {
            System.out.print(buffer2.get());
        }
        System.out.println();

        buffer1.position(2);
        CharBuffer buffer3 = buffer1.subSequence(0,2);
        System.out.println("C buffer3 position=" + buffer3.position() + " capacity=" + buffer3.capacity()
                            + " limit=" + buffer3.limit());

        for (int i = buffer3.position(); i < buffer3.limit(); i++) {
            System.out.print(buffer3.get());
        }
    }
}

运行结果:

abcde456
A Buffer2 position=0
B buffer2 position=4
bcde
C buffer3 position=2 capacity=8 limit=4
cd

static CharBuffer wrap(CharSequence csq,int start,int end)方法的使用

public static CharBuffer wrap(CharSequence csq,int start,int end):将字符序列包装到缓冲区中。新的缓冲区是只读缓冲区,且只读缓冲区的内容将为给定字符序列的内容。缓冲区的容量将为csq.length(),其位置将为start,其限制将为end,其标记是未定义的。

示例代码为:

public class TestCharBufferWrap {

    public static void main(String[] args)throws IOException {
        CharBuffer charBuffer1 = CharBuffer.wrap("abcdefg",3,5);
        System.out.println("capacity=" + charBuffer1.capacity() + " limit=" + charBuffer1.limit()
                            + " position=" + charBuffer1.position());

        for (int i = 0; i < charBuffer1.limit(); i++) {
            System.out.print(charBuffer1.get(i) + " ");
        }
        charBuffer1.append("我是只读的,不能添加数据,会出现异常!");
    }
}

运行结果:

capacity=7 limit=5 position=3
a b c d e Exception in thread "main" java.nio.ReadOnlyBufferException
	at java.nio.CharBuffer.put(CharBuffer.java:920)
	at java.nio.CharBuffer.put(CharBuffer.java:950)
	at java.nio.CharBuffer.append(CharBuffer.java:1351)
	at tech.punklu.niosocket.chapter1.TestCharBufferWrap.main(TestCharBufferWrap.java:25)

获得字符缓冲区的长度

public final int length()方法的作用:返回此字符缓冲区的长度。当将字符缓冲区视为字符序列时,长度只是该位置(包括)和限制(不包括)之间的字符数,即长度等于remaining().length()方法内部就是调用的remaining()方法。