Netty基础01-NIO-ByteBuffer

2、ByteBuffer

简单案例

  • pom
  <dependencies>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.39.Final</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.18</version>
        </dependency>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.8.5</version>
        </dependency>
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>19.0</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
    </dependencies>
  • code
// 读取data.txt文件中的内容
@Slf4j
public class ChannelAndBuffer {

    public static void main(String[] args) {
        // 1、FileChannel
        // 输入输出流  RadomAccessFile
        try (FileChannel channel = new FileInputStream("data.txt").getChannel()) {

            //2、准备缓冲区 10个字节
            ByteBuffer buffer = ByteBuffer.allocate(10);

            while (true) {
                // 3、从channel 读取数据 也就是想buffer写入数据
                int len = channel.read(buffer);

                // len == -1表示没有数据
                if (len == -1) {
                    break;
                }
                log.debug("--->{}", "读取的字节数:" + len);

                // 4、切换buffer模式为度模式
                buffer.flip();

                // 5、检查buffer中是否还有剩余数据
                while (buffer.hasRemaining()) {
                    byte b = buffer.get();
                    log.debug("--->{}", "读出的字节:" + (char) b);
                }
                
                // 6、切换为写模式
                buffer.clear();
            }


        } catch (IOException e) {
        }
    }
}
  • 日志
2021-04-10 14:18:12.648 [main] DEBUG com.riest.netty.ChannelAndBuffer:  --->读取的字节数:10
2021-04-10 14:18:12.652 [main] DEBUG com.riest.netty.ChannelAndBuffer:  --->读出的字节:1
2021-04-10 14:18:12.652 [main] DEBUG com.riest.netty.ChannelAndBuffer:  --->读出的字节:2
2021-04-10 14:18:12.652 [main] DEBUG com.riest.netty.ChannelAndBuffer:  --->读出的字节:3
2021-04-10 14:18:12.652 [main] DEBUG com.riest.netty.ChannelAndBuffer:  --->读出的字节:4
2021-04-10 14:18:12.652 [main] DEBUG com.riest.netty.ChannelAndBuffer:  --->读出的字节:5
2021-04-10 14:18:12.653 [main] DEBUG com.riest.netty.ChannelAndBuffer:  --->读出的字节:6
2021-04-10 14:18:12.653 [main] DEBUG com.riest.netty.ChannelAndBuffer:  --->读出的字节:7
2021-04-10 14:18:12.653 [main] DEBUG com.riest.netty.ChannelAndBuffer:  --->读出的字节:8
2021-04-10 14:18:12.653 [main] DEBUG com.riest.netty.ChannelAndBuffer:  --->读出的字节:9
2021-04-10 14:18:12.653 [main] DEBUG com.riest.netty.ChannelAndBuffer:  --->读出的字节:0
2021-04-10 14:18:12.653 [main] DEBUG com.riest.netty.ChannelAndBuffer:  --->读取的字节数:3
2021-04-10 14:18:12.653 [main] DEBUG com.riest.netty.ChannelAndBuffer:  --->读出的字节:a
2021-04-10 14:18:12.653 [main] DEBUG com.riest.netty.ChannelAndBuffer:  --->读出的字节:b
2021-04-10 14:18:12.653 [main] DEBUG com.riest.netty.ChannelAndBuffer:  --->读出的字节:c

正确使用姿势

  • 向buffer写入数据。例如调用channel.read(buffer)
  • 调用flip() 切换至读模式
  • 从buffer读取数据,例如调用buffer.get()
  • 调用clear()或者compacy()切换至写模式
  • 重复上述步骤

结构

  • limit:写入限制
  • Capacity:容量
  • Position:位置(指针)

一开始:

写模式下:position是写入位置,limit等于容量,下图表示写入了4个字节后的状态

flip()动作发生后:position切换为读取位置,limit切换为读取限制

读取四个字节后,状态

clear()发生后,状态:

comapct()方法,将未读完的部分向前压缩,然后切换至写模式

常用方法

  • 分配内存方法

     // 1、分配内存的方法
     ByteBuffer allocate = ByteBuffer.allocate(16);
     ByteBuffer byteBuffer = ByteBuffer.allocateDirect(16);
     // class java.nio.HeapByteBuffer  java 堆内存,读写效率低,收到GC影响
     System.out.println(allocate.getClass());
     // class java.nio.DirectByteBuffer 系统内存 ,读写效率高(少一次拷贝),不受GC影响,分配效率低
     System.out.println(byteBuffer.getClass());
    
  • 写入方法

       // 2、写入方法
        try (FileChannel channel = new FileInputStream("").getChannel()) {
             ByteBuffer buffer = ByteBuffer.allocate(10);
             // channel
              channel.read(buffer);
              // buffer
              buffer.put((byte)'a');
              //......
         } catch (IOException e) {
         }
    
  • 读取方法

     //3、读取方法
      ByteBuffer allocate = ByteBuffer.allocate(10);
      // 写
      allocate.put(new byte[]{'a', 'b', 'c', 'd'});
      // 切换至读模式
      allocate.flip();
      allocate.get(new byte[4]);
      debugAll(allocate);
      // 从头开始读
      allocate.rewind();
      debugAll(allocate);
    // get 方法会让position的指针往后移动,如果向重复读取数据
    // 可以将rewind方法将position重新置为0
    // 或者调用get(int i)方法获取索引的 i 的内容,它不会移动指针
    

  • mark & reset

    mark 做一个标记,记录position的位置

    reset将position重置到position的位置

            ByteBuffer allocate = ByteBuffer.allocate(10);
            allocate.put(new byte[]{'a', 'b', 'c', 'd'});
            allocate.flip();
            System.out.println("---->"+allocate.get());
            System.out.println("---->"+allocate.get());
            // 标记position的位置
            allocate.mark();
            System.out.println("---->"+allocate.get());
            System.out.println("---->"+allocate.get());
            // 将position 重置到 postion 索引为2 的位置
            allocate.reset();
            System.out.println("---->"+allocate.get());
    

  • String 和 ByteBuffer相互转换

            // 1、字符串转ByteBuffer
            ByteBuffer buffer1 = ByteBuffer.allocate(16);
            buffer1.put("hello".getBytes());
            debugAll(buffer1);
    
            // 该方法会自动切换至读模式
            ByteBuffer buffer2 = StandardCharsets.UTF_8.encode("hello");
            debugAll(buffer2);
            // 该方法会自动切换至读模式
            ByteBuffer buffer3 = ByteBuffer.wrap("hello".getBytes());
            debugAll(buffer3);
    
            // 2、ByteBuffer 转字符串
            String str1 = StandardCharsets.UTF_8.decode(buffer2).toString();
            System.out.println("---->"+str1);
    
            // buffer1切换读模式
            buffer1.flip();
            String str2 = StandardCharsets.UTF_8.decode(buffer1).toString();
            System.out.println("---->"+str2);
    

分散读取Scattered Read

    /**
     * 分散读取
     */
    public  static  void scatteringRead(){
        try (FileChannel channel = new RandomAccessFile("word1.txt","r").getChannel()) {
            ByteBuffer buffer1 = ByteBuffer.allocate(4);
            ByteBuffer buffer2 = ByteBuffer.allocate(3);
            ByteBuffer buffer3 = ByteBuffer.allocate(6);
            channel.read(new ByteBuffer[]{buffer1,buffer2,buffer3});
            buffer1.flip();
            buffer2.flip();
            buffer3.flip();
            debugAll(buffer1);
            debugAll(buffer2);
            debugAll(buffer3);
        } catch (IOException e) {
        }
    }

集中写 Gather Write

    /**
     * 集中写
     */
    public static void gatherWrite(){
        ByteBuffer b1 = StandardCharsets.UTF_8.encode("NIO ");
        ByteBuffer b2 = StandardCharsets.UTF_8.encode("IS  ");
        ByteBuffer b3 = StandardCharsets.UTF_8.encode("SOEASY");

        try (FileChannel channel = new RandomAccessFile("word2.txt", "rw").getChannel()) {
            channel.write(new ByteBuffer[]{b1,b2,b3});
        } catch (IOException e) {
        }
    }

练习

网络上有很多条数据发送给服务器,数据之间使用 \n 进行分隔 ,但由于某种原有导致这些数据在接收是,被进行了重新组合,例如原始数据有三条,分别为
Hello,world\n
I`m zhangsan\n
How are you?\n

变成了下面的两个ByeBuffer(黏包、半包)
Hello,world\nI`m zhangsan\nHo
w are you?\n

现在要求你编写code,将错乱的数据恢复成原始的按照 \n分隔的数据

code 如下:

    public static void main(String[] args) {
        String str1 = "hello,world\nI`m zhangsan\nHo";
        String str2 = "w are you?\n";
        ByteBuffer b1 = ByteBuffer.allocate(30);
        b1.put(str1.getBytes());
        dispose(b1);
        b1.put(str2.getBytes());
        dispose(b1);
    }

    public static void dispose(ByteBuffer buffer) {
        // 切换成读模式
        buffer.flip();
        for (int i = 0; i < buffer.limit(); i++) {
            if (buffer.get(i) == '\n') {
                // \n 的位置
                int len = i + 1 - buffer.position();
                ByteBuffer allocate = ByteBuffer.allocate(len);
                for (int j = 0; j < len; j++) {
                    allocate.put(buffer.get());
                }
                debugAll(allocate);
            }
        }
        //将未读完的部分向前压缩,然后切换至写模式
        buffer.compact();
    }

posted @ 2021-04-13 09:21  一个努力的人QAQ  阅读(95)  评论(0编辑  收藏  举报