一、Buffer 基本介绍

二、Buffer 类及其子类

(1)在 NIO 中,Buffer 是一个顶层父类,它是一个抽象类。

(2)常用Buffer子类一览(除boolean之外的7个基本类型对应的buffer)

Netty(二)- NIO三大组件之Buffer-LMLPHP

(3)Buffer 的四个属性:

public abstract class Buffer {
    // Invariants: mark <= position <= limit <= capacity
    private int mark = -1;
    private int position = 0;
    private int limit;
    private int capacity;
}

Netty(二)- NIO三大组件之Buffer-LMLPHP
(4)Buffer类相关方法

public abstract class Buffer {
    //JDK1.4时,引入的api
    public final int capacity( )//返回此缓冲区的容量
    public final int position( )//返回此缓冲区的位置
    public final Buffer position (int newPositio)//设置此缓冲区的位置
    public final int limit( )//返回此缓冲区的限制
    public final Buffer limit (int newLimit)//设置此缓冲区的限制
    public final Buffer mark( )//在此缓冲区的位置设置标记
    public final Buffer reset( )//将此缓冲区的位置重置为以前标记的位置
    public final Buffer clear( )//清除此缓冲区, 即将各个标记恢复到初始状态,但是数据并没有真正擦除, 后面操作会覆盖
    public final Buffer flip( )//反转此缓冲区
    public final Buffer rewind( )//重绕此缓冲区
    public final int remaining( )//返回当前位置与限制之间的元素个数
    public final boolean hasRemaining( )//告知在当前位置和限制之间是否有元素
    public abstract boolean isReadOnly( );//告知此缓冲区是否为只读缓冲区
 
    //JDK1.6时引入的api
    public abstract boolean hasArray();//告知此缓冲区是否具有可访问的底层实现数组
    public abstract Object array();//返回此缓冲区的底层实现数组
    public abstract int arrayOffset();//返回此缓冲区的底层实现数组中第一个缓冲区元素的偏移量
    public abstract boolean isDirect();//告知此缓冲区是否为直接缓冲区
}

(5)ByteBuffer

public abstract class ByteBuffer {
    //缓冲区创建相关api
    public static ByteBuffer allocateDirect(int capacity)//创建直接缓冲区
    public static ByteBuffer allocate(int capacity)//设置缓冲区的初始容量
    public static ByteBuffer wrap(byte[] array)//把一个数组放到缓冲区中使用
    //构造初始化位置offset和上界length的缓冲区
    public static ByteBuffer wrap(byte[] array,int offset, int length)
     //缓存区存取相关API
    public abstract byte get( );//从当前位置position上get,get之后,position会自动+1
    public abstract byte get (int index);//从绝对位置get
    public abstract ByteBuffer put (byte b);//从当前位置上添加,put之后,position会自动+1
    public abstract ByteBuffer put (int index, byte b);//从绝对位置上put
 }

三、Buffer 的使用

public class BasicBuffer {

    public static void main(String[] args) {
        // 创建一个Buffer, 大小为 3, 即可以存放3个int
        IntBuffer intBuffer = IntBuffer.allocate(3);

        // 向buffer 存放数据
        for(int i = 0; i < intBuffer.capacity(); i++) {
            intBuffer.put( i * 2);
        }

        // 将buffer转换,之前是写,现在转换为读
        intBuffer.flip();

        while (intBuffer.hasRemaining()) {
            System.out.println(intBuffer.get());
        }
    }
    
}

0
2
4

Netty(二)- NIO三大组件之Buffer-LMLPHP

Netty(二)- NIO三大组件之Buffer-LMLPHP

flip()方法源码:

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

Netty(二)- NIO三大组件之Buffer-LMLPHP

四、关于Buffer 的注意事项和细节

1. put和get的数据类型应该相同

public class NIOByteBufferPutGet {
    public static void main(String[] args) {
        // 创建一个Buffer
        ByteBuffer buffer = ByteBuffer.allocate(64);

        // 类型化方式放入数据
        buffer.putInt(100);
        buffer.putLong(9);
        buffer.putChar('你');
        buffer.putShort((short) 4);

        // 取出
        buffer.flip();

        System.out.println(buffer.getInt());
        System.out.println(buffer.getLong());
        System.out.println(buffer.getChar());
        System.out.println(buffer.getShort());
    }
}

输出:

2. 可以将一个普通 Buffer 转成只读 Buffer

public class ReadOnlyBuffer {
    public static void main(String[] args) {

        // 创建一个buffer
        ByteBuffer buffer = ByteBuffer.allocate(3);

        for(int i = 0; i < 3; i++) {
            buffer.put((byte)i);
        }

        // 转换成读模式
        buffer.flip();

        // 得到一个只读的Buffer
        ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();
        System.out.println(readOnlyBuffer.getClass());

        // 读取
        while (readOnlyBuffer.hasRemaining()) {
            System.out.println(readOnlyBuffer.get());
        }

        // 抛出ReadOnlyBufferException异常
        readOnlyBuffer.put((byte)100);
    }
}

输出:

class java.nio.HeapByteBufferR
0
1
2
Exception in thread "main" java.nio.ReadOnlyBufferException
	at java.nio.HeapByteBufferR.put(HeapByteBufferR.java:172)
	at com.lwk.nettydemo.nio.ReadOnlyBuffer.main(ReadOnlyBuffer.java:28)

3. 可以使用MappedByteBuffer让文件直接在内存中修改

/**
 * MappedByteBuffer 可让文件直接在内存(堆外内存)修改,操作系统不需要再拷贝一次
 */
public class MappedByteBufferTest {
    public static void main(String[] args) throws Exception {

        RandomAccessFile randomAccessFile = new RandomAccessFile("1.txt", "rw");
        // 获取对应的通道
        FileChannel channel = randomAccessFile.getChannel();

        /**
         * 参数1:FileChannel.MapMode.READ_WRITE 使用的读写模式
         * 参数2:0:可以直接修改的起始位置
         * 参数3:5:是映射到内存的大小(不是索引位置),即 将1.txt的5个字节映射到内存
         * 可以直接修改的范围就是 0-5
         * 实际类型 DirectByteBuffer
         */
        MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);

        mappedByteBuffer.put(0, (byte) 'H');
        mappedByteBuffer.put(3, (byte) '9');
        //IndexOutOfBoundsException异常,因为索引0-4已经占了5个字节,所以再修改索引5时,对应的字节超过了范围,报错
        //mappedByteBuffer.put(5, (byte) 'Y');

        randomAccessFile.close();
        System.out.println("修改成功~~");

    }
}

4. 可以通过 Buffer 数组完成读写操作(Scattering 和 Gathering)

/**
 * Scattering:将数据写入到buffer时,可以采用buffer数组,依次写入  【分散】
 * Gathering: 从buffer读取数据时,可以采用buffer数组,依次读 【聚集】
 */
public class ScatteringAndGatheringTest {
    public static void main(String[] args) throws Exception {

        // 使用 ServerSocketChannel 和 SocketChannel 网络
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);

        // 绑定端口到socket ,并启动
        serverSocketChannel.socket().bind(inetSocketAddress);

        // 创建buffer数组
        ByteBuffer[] byteBuffers = new ByteBuffer[2];
        byteBuffers[0] = ByteBuffer.allocate(5);
        byteBuffers[1] = ByteBuffer.allocate(3);

        // 等客户端连接(telnet)
        SocketChannel socketChannel = serverSocketChannel.accept();
        // 假定从客户端接收8个字节
        int messageLength = 8;
        // 循环读取
        while (true) {

            int byteRead = 0;

            while (byteRead < messageLength) {
                long l = socketChannel.read(byteBuffers);
                // 累计读取的字节数
                byteRead += l;
                System.out.println("byteRead=" + byteRead);
                // 使用流打印,看看当前的这个buffer的 position 和 limit
                Arrays.stream(byteBuffers).map(buffer -> "postion=" + buffer.position() + ", limit=" + buffer.limit()).forEach(System.out::println);
            }

            // 将所有的buffer进行flip
            Arrays.asList(byteBuffers).forEach(ByteBuffer::flip);

            // 将数据读出显示到客户端
            long byteWirte = 0;
            while (byteWirte < messageLength) {
                long l = socketChannel.write(byteBuffers); //
                byteWirte += l;
            }

            // 将所有的buffer进行clear
            Arrays.asList(byteBuffers).forEach(ByteBuffer::clear);

            System.out.println("byteRead:=" + byteRead + " byteWrite=" + byteWirte + ", messagelength" + messageLength);
        }

    }
}

Netty(二)- NIO三大组件之Buffer-LMLPHP

Netty(二)- NIO三大组件之Buffer-LMLPHP

12-06 03:50