Java-第十七部分-NIO和Netty-Buffer

226 阅读3分钟

NIO和Netty全文

Buffer

  • 缓冲区,数据从通道读入缓冲区,从缓冲区写入到通道中,可以理解为是一个容器对象
  • 本质上一块可以写入数据和读取数据的内存 image.png
  • flip()模式转换,写转换为读,将position设为0,limit为原先的position image.png
  • rewind()仅仅将postion设置为0,可以重新读
  • compact()只清除读过的数据,将未读的数据拷贝到起始处,将position设置到最后一个未读元素的后面
  • mark()标记positionreset()恢复position
  • 存放数据的数组 image.png
  • 类型化put时,放入的类型和取出的类型要相等,否则抛出异常BufferUnderflowException
ByteBuffer bb = ByteBuffer.allocate(1024);
bb.putInt(10);
bb.putLong(9);
bb.putChar('a');
bb.putShort((short) 1);
bb.flip();
System.out.println(bb.getInt());
System.out.println(bb.getLong());
System.out.println(bb.getChar());

RandomAccessFile raf = new RandomAccessFile("/Users/mzx/Desktop/java/nio/test1.txt", "rw");
FileChannel fc = raf.getChannel();
ByteBuffer bb = ByteBuffer.allocate(1);
//读,向buffer中写
int len = fc.read(bb);
while (len != -1) {
    System.out.println("开始读");
    //模式转换
    bb.flip();
    while (bb.hasRemaining()) {
        System.out.println((char)bb.get());
    }
    bb.clear();
    //循环读
    len = fc.read(bb);
}
fc.close();
raf.close();

IntBuffer

IntBuffer ib = IntBuffer.allocate(1024);
//往buffer中放数据
for (int i = 0; i < ib.capacity(); i++) {
    int j = 2 * (i + 1);
    //类似加入数组中的效果
    ib.put(j);
}
//模式转换,几个指针切换
ib.flip();
//获取
while (ib.hasRemaining()) {
    //每次get,`position`指针后移动
    int value = ib.get();
    System.out.println(value);
}
  • get方法 image.png image.png

capacity、position和limit

  • capacity 容量大小,可容纳的最大数据量
  • position
  1. 写数据时,表示数据写入的当前位置,初始值为0;写入后,移动到下一个可插入的单元
  2. 读数据时,读取数据的位置;读取后,移动到下一个可读取的单元
  • limit
  1. 写数据时,表示可对buffer写入多少个数据,就是capacity,写入的结束为止
  2. 读数据时,表示有多少可读的数据,对应写模式下的position image.png
  • mark,标记,用于记录当前position的位置

mark和reset

int temp = 0;
boolean flag = false;
while (ib.hasRemaining()) {
    temp ++;
    int value = ib.get();
    System.out.println(value);
    if (temp == 100) {
        ib.mark();
        System.out.println("ib.mark()" + ib.mark());
    }
    //只恢复一次
    if (temp == 200 && !flag) {
        ib.reset();
        flag = !flag;
        System.out.println("ib.reset()" + ib.reset());
    }
}
  • mark image.png
  • reset image.png

缓冲区

  • 缓冲区分片

在现有缓冲区上,创建一个子缓冲区,底层数据共享

ByteBuffer bb = ByteBuffer.allocate(10);
for (int i = 0; i < bb.capacity(); i++) {
    bb.put((byte) i);
}
//创建子缓冲区 [3,7)
bb.position(3);
bb.limit(7);
ByteBuffer slice = bb.slice();
System.out.println(slice.capacity());
//改变子缓冲区内容
for (int i = 0; i < slice.capacity(); i++) {
    byte b = slice.get(i);
    b *= 10;
    slice.put(i, b);
}
//将position设置为0
bb.rewind();
//将limit设置为容量
bb.limit(bb.capacity());
while (bb.remaining() > 0) {
    System.out.println(bb.get());
}
  • 只读缓冲区,HeapByteBufferR

获得一个与原缓冲区相同的缓冲区,数据共享,但是只能读取

ByteBuffer bb = ByteBuffer.allocate(10);
for (int i = 0; i < bb.capacity(); i++) {
    bb.put((byte) i);
}
//创建只读缓存区
ByteBuffer readOnly = bb.asReadOnlyBuffer();
for (int i = 0; i < bb.capacity(); i++) {
    byte b = bb.get(i);
    b *= 10;
    bb.put(i, b);
}
readOnly.flip();
while (readOnly.remaining() > 0) {
    System.out.println(readOnly.get());
}
  • 直接缓冲区,加快io速度,减少中间操作
String infile = "/Users/mzx/Desktop/java/训练营笔记.pdf";
FileInputStream fis = new FileInputStream(infile);
FileChannel fic = fis.getChannel();

String tofile = "/Users/mzx/Desktop/java/nio/训练营笔记.pdf";
FileOutputStream fos = new FileOutputStream(tofile);
FileChannel foc = fos.getChannel();

ByteBuffer bb = ByteBuffer.allocateDirect(1024);
while(true) {
    //底层也是移动position和limit
    //position到0,limit到capacity
    bb.clear();
    int len = fic.read(bb);
    if (len == -1) {
        break;
    }
    bb.flip();
    foc.write(bb);
}
  • 内存映射文件I/O,读写文件的一种方式,只将文件中实际读取或者写入的部分才会映射到内存(队外内存),操作系统不需要拷贝一次

刷新后,才会改变

int start = 5;
int size = 1024;
RandomAccessFile raf = new RandomAccessFile("/Users/mzx/Desktop/java/nio/test3.txt", "rw");
FileChannel fc = raf.getChannel();
//截取文件start开始,size长度的内容进内存
MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, start, size);
//改变内存中,索引位置的内容
mbb.put(0, (byte)97);
mbb.put(4,(byte) 122);
fc.close();
raf.close();

实际类型 image.png

分散和聚合

  • Scattering,分散,将数据写入到buffer,可以采用buffer数组,依次写入
  • Gathering,聚合,从buffer读取数据时,可以采用buffer数组,依次读
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.socket().bind(new InetSocketAddress(7777));
//创建buffer数组
ByteBuffer[] bbs = new ByteBuffer[2];
bbs[0] = ByteBuffer.allocate(5);
bbs[1] = ByteBuffer.allocate(3);
//等待客户端
SocketChannel sc = ssc.accept();
//循环读取
int messageLength = 8; //假定接收8个
while (true) {
    int byteRead = 0;
    while (byteRead < messageLength) {
        //读到bbs中
        long l = sc.read(bbs);
        byteRead += l;
        System.out.println("byteRead: " + byteRead);
        Arrays.asList(bbs).stream().map(bb -> "position: " + bb.position() + " limit: " + bb.limit()).forEach(System.out::println);
    }
    //回显到客户端
    Arrays.asList(bbs).forEach(bb -> bb.flip());
    long byteWrite = 0;
    while (byteWrite < messageLength) {
        //写到bbs中
        long l = sc.write(bbs);
        byteWrite += l;
        System.out.println("byteWrite: " + byteWrite);
    }
    Arrays.asList(bbs).forEach(ByteBuffer::clear);
    System.out.println("byteRead: " + byteRead + " byteWrite: " + byteWrite);
}