ByteBuffer

作者: 我可能是个假开发 | 来源:发表于2023-12-25 14:01 被阅读0次

    一、.基本使用

    使用ByteBuffer读取文件中的内容:

    public class TestByteBuffer {
        public static void main(String[] args) {
            // 获得FileChannel
            try (FileChannel channel = new FileInputStream("stu.txt").getChannel()) {
                // 获得缓冲区
                ByteBuffer buffer = ByteBuffer.allocate(10);
                int hasNext = 0;
                StringBuilder builder = new StringBuilder();
                while((hasNext = channel.read(buffer)) > 0) {
                    // 切换模式 limit=position, position=0
                    buffer.flip();
                    // 当buffer中还有数据时,获取其中的数据
                    while(buffer.hasRemaining()) {
                        builder.append((char)buffer.get());
                    }
                    // 切换模式 position=0, limit=capacity
                    buffer.clear();
                }
                System.out.println(builder.toString());
            } catch (IOException e) {
            }
        }
    }
    
    • 向 buffer 写入数据,调用 channel.read(buffer):从channel读取数据写入buffer
    • 调用 flip() 切换至读模式
      • flip会使得buffer中的limit变为position,position变为0
    • 从 buffer 读取数据,调用 buffer.get()
    • 调用 clear() 或者compact()切换至写模式
      • 调用clear()方法时position=0,limit变为capacity
      • 调用compact()方法时,会将缓冲区中的未读数据压缩到缓冲区前面
    • 重复以上步骤

    二、结构

    bytebuffer.png
    public class TestByteBufferReadWrite {
        public static void main(String[] args) {
            ByteBuffer buffer = ByteBuffer.allocate(10);
            buffer.put((byte) 0x61); // 'a'
            debugAll(buffer);
            /**
             * position: [1], limit: [10]
             *          +-------------------------------------------------+
             *          |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
             * +--------+-------------------------------------------------+----------------+
             * |00000000| 61 00 00 00 00 00 00 00 00 00                   |a.........      |
             * +--------+-------------------------------------------------+----------------+
             */
    
            buffer.put(new byte[]{0x62, 0x63, 0x64}); // b  c  d
            debugAll(buffer);
            /**
             * position: [4], limit: [10]
             *          +-------------------------------------------------+
             *          |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
             * +--------+-------------------------------------------------+----------------+
             * |00000000| 61 62 63 64 00 00 00 00 00 00                   |abcd......      |
             * +--------+-------------------------------------------------+----------------+
             */
    
            //0 不切换成读模式,读不到数据
    //        System.out.println(buffer.get());
            buffer.flip();
            //97
            System.out.println(buffer.get());
            debugAll(buffer);
            /**
             * position: [1], limit: [5]
             *          +-------------------------------------------------+
             *          |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
             * +--------+-------------------------------------------------+----------------+
             * |00000000| 61 62 63 64 00 00 00 00 00 00                   |abcd......      |
             * +--------+-------------------------------------------------+----------------+
             */
    
            buffer.compact();
            debugAll(buffer);
            /**
             *  position: [3], limit: [10]
             *          +-------------------------------------------------+
             *          |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
             * +--------+-------------------------------------------------+----------------+
             * |00000000| 62 63 64 64 00 00 00 00 00 00                   |bcdd......      |
             * +--------+-------------------------------------------------+----------------+
             * +--------+-------------------- all ------------------------+----------------+
             */
    
    
            buffer.put(new byte[]{0x65,0x66});
            debugAll(buffer);
            /**
             *  position: [5], limit: [10]
             *          +-------------------------------------------------+
             *          |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
             * +--------+-------------------------------------------------+----------------+
             * |00000000| 62 63 64 65 66 00 00 00 00 00                   |bcdef.....      |
             * +--------+-------------------------------------------------+----------------+
             */
        }
    }
    

    三、常见方法

    1.分配空间

    可以使用 allocate 方法为 ByteBuffer 分配空间,其它 buffer 类也有该方法

    import java.nio.ByteBuffer;
    
    public class TestByteBufferAllocate {
        public static void main(String[] args) {
            System.out.println(ByteBuffer.allocate(16).getClass()); // class java.nio.HeapByteBuffer
            System.out.println(ByteBuffer.allocateDirect(16).getClass()); // class java.nio.DirectByteBuffer
        }
    }
    
    • class java.nio.HeapByteBuffer - java 堆内存,读写效率较低,受到 GC 的影响
    • class java.nio.DirectByteBuffer - 直接内存,读写效率高(少一次拷贝),不会受 GC 影响,分配的效率低

    2.向 buffer 写入数据

    • 调用 channel 的 read 方法:int readBytes = channel.read(buf);
    • 调用 buffer 自己的 put 方法:buf.put((byte)127);

    3.从 buffer 读取数据

    • 调用 channel 的 write 方法:int writeBytes = channel.write(buf);
    • 调用 buffer 自己的 get 方法:byte b = buf.get();

    get 方法会让 position 读指针向后走,如果想重复读取数据

    • 可以调用 rewind 方法将 position 重新置为 0
    • 或者调用 get(int i) 方法获取索引 i 的内容,它不会移动读指针

    4.mark 和 reset

    mark 是在读取时,做一个标记,即使 position 改变,只要调用 reset 就能回到 mark 的位置

    public class TestByteBufferRead {
    
        public static void main(String[] args) {
            ByteBuffer buffer = ByteBuffer.allocate(10);
            buffer.put(new byte[]{'a', 'b', 'c', 'd'});
            buffer.flip();
    
            // rewind 从头开始读
            /*
            buffer.get(new byte[4]);
            debugAll(buffer);
            buffer.rewind();
            System.out.println((char)buffer.get());
            */
            /**
             * position: [4], limit: [4]
             *          +-------------------------------------------------+
             *          |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
             * +--------+-------------------------------------------------+----------------+
             * |00000000| 61 62 63 64 00 00 00 00 00 00                   |abcd......      |
             * a
             */
    
            // mark & reset
            // mark 做一个标记,记录 position 位置, reset 是将 position 重置到 mark 的位置
            /*
            System.out.println((char) buffer.get()); //a
            System.out.println((char) buffer.get()); //b
            buffer.mark(); // 加标记,索引 2 的位置
            System.out.println((char) buffer.get()); //c
            System.out.println((char) buffer.get()); //d
            buffer.reset(); // 将 position 重置到做标记的地方,即索引 2
            System.out.println((char) buffer.get()); //c
            System.out.println((char) buffer.get()); //d
            */
    
            // get(i) 不会改变读索引的位置
            System.out.println((char) buffer.get(3)); //d
            debugAll(buffer);
            /**
             * position: [0], limit: [4]
             *          +-------------------------------------------------+
             *          |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
             * +--------+-------------------------------------------------+----------------+
             * |00000000| 61 62 63 64 00 00 00 00 00 00                   |abcd......      |
             */
        }
    }
    

    rewind 和 flip 都会清除 mark 位置

    四、字符串与 ByteBuffer 互转

    public class TestByteBufferString {
        public static void main(String[] args) {
            // 1. 字符串转为 ByteBuffer
            ByteBuffer buffer1 = ByteBuffer.allocate(16);
            buffer1.put("hello".getBytes());
            debugAll(buffer1);
            /**
             * position: [5], limit: [16]
             *          +-------------------------------------------------+
             *          |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
             * +--------+-------------------------------------------------+----------------+
             * |00000000| 68 65 6c 6c 6f 00 00 00 00 00 00 00 00 00 00 00 |hello...........|
             */
    
            // 2. Charset 会自动切换到读模式
            ByteBuffer buffer2 = StandardCharsets.UTF_8.encode("hello");
            debugAll(buffer2);
            /**
             * position: [0], limit: [5]
             *          +-------------------------------------------------+
             *          |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
             * +--------+-------------------------------------------------+----------------+
             * |00000000| 68 65 6c 6c 6f                                  |hello           |
             */
    
            // 3. wrap 会自动切换到读模式
            ByteBuffer buffer3 = ByteBuffer.wrap("hello".getBytes());
            debugAll(buffer3);
            /**
             * position: [0], limit: [5]
             *          +-------------------------------------------------+
             *          |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
             * +--------+-------------------------------------------------+----------------+
             * |00000000| 68 65 6c 6c 6f                                  |hello           |
             */
    
            // 4. 转为字符串 读模式下可以直接转 写模式下找不到数据
            String str1 = StandardCharsets.UTF_8.decode(buffer2).toString();
            System.out.println(str1);//hello
    
            // 写模式下需要切换为读模式
            buffer1.flip();
            String str2 = StandardCharsets.UTF_8.decode(buffer1).toString();
            System.out.println(str2);//hello
    
        }
    }
    

    五、Scattering Reads

    分散读取,文本文件 3parts.txt
    onetwothree

    public class TestScatteringReads {
        public static void main(String[] args) {
            try (FileChannel channel = new RandomAccessFile("words2.txt", "r").getChannel()) {
                ByteBuffer b1 = ByteBuffer.allocate(3);
                ByteBuffer b2 = ByteBuffer.allocate(3);
                ByteBuffer b3 = ByteBuffer.allocate(5);
                channel.read(new ByteBuffer[]{b1, b2, b3});
                b1.flip();
                b2.flip();
                b3.flip();
                debugAll(b1);
                debugAll(b2);
                debugAll(b3);
            } catch (IOException e) {
            }
        }
    }
    
    position: [0], limit: [3]
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 68 65 6c                                        |hel             |
    +--------+-------------------------------------------------+----------------+
    +--------+-------------------- all ------------------------+----------------+
    position: [0], limit: [3]
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 6c 6f 77                                        |low             |
    +--------+-------------------------------------------------+----------------+
    +--------+-------------------- all ------------------------+----------------+
    position: [0], limit: [5]
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 6f 72 6c 64 e4                                  |orld.           |
    

    六、Gathering Writes

    将多个 buffer 的数据填充至 channel

    public class TestGatheringWrites {
        public static void main(String[] args) {
            ByteBuffer b1 = StandardCharsets.UTF_8.encode("hello");
            ByteBuffer b2 = StandardCharsets.UTF_8.encode("world");
            ByteBuffer b3 = StandardCharsets.UTF_8.encode("你好");
    
            try (FileChannel channel = new RandomAccessFile("words.txt", "rw").getChannel()) {
                channel.write(new ByteBuffer[]{b1, b2, b3});
            } catch (IOException e) {
            }
        }
    }
    

    减少数据拷贝,提高效率

    七、网络数据传输粘包和半包

    粘包:一次性发送多条数据
    半包:接收方缓冲区大小限制

    网络上有多条数据发送给服务端,数据之间使用 \n 进行分隔
    但由于某种原因这些数据在接收时,被进行了重新组合。
    将错乱的数据恢复成原始的按 \n 分隔的数据

    public class TestByteBufferExam {
        public static void main(String[] args) {
            /*
            例如原始数据有3条为
                 Hello,world\n
                 I'm zhangsan\n
                 How are you?\n
                 
            变成了下面的两个 byteBuffer (黏包,半包)
                 Hello,world\nI'm zhangsan\nHo
                 w are you?\n
            
            将错乱的数据恢复成原始的按 \n 分隔的数据
             */
            ByteBuffer source = ByteBuffer.allocate(32);
            source.put("Hello,world\nI'm zhangsan\nHo".getBytes());
            split(source);
            source.put("w are you?\n".getBytes());
            split(source);
        }
    
        private static void split(ByteBuffer source) {
            source.flip();
            for (int i = 0; i < source.limit(); i++) {
                // 找到一条完整消息
                if (source.get(i) == '\n') {
                    int length = i + 1 - source.position();
                    // 把这条完整消息存入新的 ByteBuffer
                    ByteBuffer target = ByteBuffer.allocate(length);
                    // 从 source 读,向 target 写
                    for (int j = 0; j < length; j++) {
                        target.put(source.get());
                    }
                    debugAll(target);
                }
            }
            source.compact();
        }
    }
    

    相关文章

      网友评论

        本文标题:ByteBuffer

        本文链接:https://www.haomeiwen.com/subject/wuzimdtx.html