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

相关文章

  • java nio

    ByteBuffer 写文件 ByteBuffer读中文文件 ByteBuffer读取普通文件

  • 11.15

    Java 中怎么创建 ByteBuffer?1.1 使用allocate()静态方法ByteBuffer buff...

  • Netty内存分配原理

    1 java NIO的ByteBuffer Bytebuffer分为两种:HeapByteBuffer(堆内内存)...

  • ByteBuffer

    缓冲区(Buffer)缓冲区(Buffer)就是在内存中预留指定大小的存储空间用来对输入/输出(I/O)的数据作临...

  • ByteBuffer

    测试代码

  • Netty(二) ByteBuf

    Netty ByteBuf 是NIO中ByteBuffer的封装,相比JDK ByteBuffer更加易用; 为读...

  • NIO DirectByteBuffer 内存泄露的测试

    写NIO程序经常使用ByteBuffer来读取或者写入数据,那么使用ByteBuffer.allocate(...

  • 关于java的ByteBuffer与Netty ByteBuf的

    JDK的ByteBuffer的缺点:1.final byte hb;这是JDKde ByteBuffer对象中用于...

  • Tomcat之Http11Processor源码分析

    Http11Processor继承关系 java.nio.ByteBuffer ByteBuffer有三个属性值:...

  • netty之ByteBuf

    ByteBuf是netty用于替代nio的ByteBuffer,存储字节的数据容器,相比于ByteBuffer,B...

网友评论

    本文标题:ByteBuffer

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