2023-09-23
原文作者:李林超 原文地址: https://www.lilinchao.com/archives/2089.html

[TOC]

1. 分配内存空间

可以使用allocate() 和 allocateDirect()方法为ByteBuffer分配空间,其他buffer类也有该方法

  • allocate() : 使用的是java的堆内存,堆内字节缓冲区,读写效率低,会受到GC的影响
  • allocateDirect() :使用的是直接内存,直接内存字节缓冲区,读写效率高(零拷贝),不会受GC影响,因为是系统直接内存,所以分配内存要调用操作系统函数,所以分配内存的速度较慢,如果使用不当(资源没得到合理释放),会造成内存泄漏。
    import java.nio.ByteBuffer;
    
    /**
     * Created by lilinchao
     * Date 2022/5/25
     * Description 分配空间
     */
    public class ByteBufferAllocateDemo {
        public static void main(String[] args) {
            System.out.println(ByteBuffer.allocate(16).getClass());
            System.out.println(ByteBuffer.allocateDirect(16).getClass());
        }
    }

输出结果

    class java.nio.HeapByteBuffer
    class java.nio.DirectByteBuffer

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 的内容,它不会移动读指针

读写示例

    import java.nio.ByteBuffer;
    
    import static com.lilinchao.nio.bytebuffer_2.ByteBufferUtil.debugAll;
    
    /**
     * Created by lilinchao
     * Date 2022/5/25
     * Description bytebuffer读写示例
     */
    public class TestByteBufferReadWrite {
        public static void main(String[] args) {
            ByteBuffer buffer = ByteBuffer.allocate(10);
            buffer.put((byte) 0x61);    //a
            debugAll(buffer);
            buffer.put(new byte[]{0x62,0x63,0x64}); //b、c、d
            debugAll(buffer);
            //flip:切换对缓冲区的操作模式 写模式 --> 读模式
            buffer.flip();
            System.out.println(buffer.get());
            //get(i):不会改变索引的位置
            System.out.println(buffer.get(2));
            debugAll(buffer);
            //compact:会把未读完的数据向前压缩,然后切换到写模式
            buffer.compact();
            debugAll(buffer);
            buffer.put(new byte[]{0x65,0x6f});
            debugAll(buffer);
            // rewind 从头开始读
            buffer.flip();
            System.out.println((char)buffer.get());
            System.out.println((char)buffer.get());
            buffer.rewind();
            System.out.println((char)buffer.get());
        }
    }

运行结果

    +--------+-------------------- all ------------------------+----------------+
    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.........      |
    +--------+-------------------------------------------------+----------------+
    +--------+-------------------- all ------------------------+----------------+
    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......      |
    +--------+-------------------------------------------------+----------------+
    97
    99
    +--------+-------------------- all ------------------------+----------------+
    position: [1], 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......      |
    +--------+-------------------------------------------------+----------------+
    +--------+-------------------- all ------------------------+----------------+
    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 ------------------------+----------------+
    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 6f 00 00 00 00 00                   |bcdeo.....      |
    +--------+-------------------------------------------------+----------------+
    b
    c
    b

4. mark 和 reset

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

    import java.nio.ByteBuffer;
    
    /**
     * Created by lilinchao
     * Date 2022/5/25
     * Description mark 和 reset方法
     */
    public class TestByteBufferMarkAndReset {
        public static void main(String[] args) {
            ByteBuffer byteBuffer = ByteBuffer.allocate(10);
            byteBuffer.put(new byte[]{'a', 'b', 'c', 'd'});
            byteBuffer.flip();
    
            System.out.println((char) byteBuffer.get()); // 读取 a
            System.out.println((char) byteBuffer.get()); // 读取 b
            byteBuffer.mark(); // 加标记  索引为2 的位置
            System.out.println((char) byteBuffer.get()); // 读取 c
            System.out.println((char) byteBuffer.get()); // 读取 d
            byteBuffer.reset(); // 将position 重置到索引为2的位置
            System.out.println((char) byteBuffer.get()); // 读取 c
            System.out.println((char) byteBuffer.get()); // 读取 d
        }
    }

运行结果

    a
    b
    c
    d
    c
    d

5. 字符串与 ByteBuffer 互转

    ByteBuffer buffer1 = StandardCharsets.UTF_8.encode("你好");
    ByteBuffer buffer2 = Charset.forName("utf-8").encode("你好");
    
    debug(buffer1);
    debug(buffer2);
    
    CharBuffer buffer3 = StandardCharsets.UTF_8.decode(buffer1);
    System.out.println(buffer3.getClass());
    System.out.println(buffer3.toString());

输出结果

             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| e4 bd a0 e5 a5 bd                               |......          |
    +--------+-------------------------------------------------+----------------+
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| e4 bd a0 e5 a5 bd                               |......          |
    +--------+-------------------------------------------------+----------------+
    class java.nio.HeapCharBuffer
    你好

示例

    import java.nio.ByteBuffer;
    import java.nio.CharBuffer;
    import java.nio.charset.StandardCharsets;
    
    import static com.lilinchao.nio.bytebuffer_2.ByteBufferUtil.debugAll;
    
    /**
     * Created by lilinchao
     * Date 2022/5/25
     * Description 字符串与 ByteBuffer 互转 Demo
     */
    public class TestByteBufferString {
        public static void main(String[] args) {
            //1、字符串转为 ByteBuffer,还是写模式
            ByteBuffer buffer = ByteBuffer.allocate(16);
            buffer.put("hello".getBytes());
            debugAll(buffer);
    
            // 2、Charset 字符集类,自动切换到读模式
            ByteBuffer helloBuffer = StandardCharsets.UTF_8.encode("hello");
            debugAll(helloBuffer);
    
            // 3、wrap,自动切换到读模式
            ByteBuffer buffer3 = ByteBuffer.wrap("hello".getBytes());
            debugAll(buffer3);
            // 4、转换为字符串
            CharBuffer charBuffer = StandardCharsets.UTF_8.decode(helloBuffer);
            System.out.println(charBuffer.toString());
    
            // 不起作用
            buffer.flip(); // 切换读模式,起作用
            CharBuffer charBuffer1 = StandardCharsets.UTF_8.decode(buffer);
            System.out.println(charBuffer1.toString());
        }
    }

运行结果

    +--------+-------------------- all ------------------------+----------------+
    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...........|
    +--------+-------------------------------------------------+----------------+
    +--------+-------------------- all ------------------------+----------------+
    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           |
    +--------+-------------------------------------------------+----------------+
    +--------+-------------------- all ------------------------+----------------+
    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           |
    +--------+-------------------------------------------------+----------------+
    hello
    hello

6. Scattering Reads(分散读)

分散读取集中写的方法不重要,重要的是思想,可以减少在ByteBuffer之间的拷贝,减少数据的复制次数,提高效率。

示例

  • 创建一个文本文件words.txt
    onetwothree
  • 分散读取示例代码
    import java.io.IOException;
    import java.io.RandomAccessFile;
    import java.nio.ByteBuffer;
    import java.nio.channels.FileChannel;
    
    import static com.lilinchao.nio.bytebuffer_2.ByteBufferUtil.debugAll;
    
    /**
     * Created by lilinchao
     * Date 2022/5/25
     * Description 分散读 Demo
     */
    public class ScatteringReadsDemo {
        public static void main(String[] args) {
    
            try(FileChannel channel = new RandomAccessFile("datas/words.txt", "r").getChannel()) {
                ByteBuffer b1 = ByteBuffer.allocate(3); //  one
                ByteBuffer b2 = ByteBuffer.allocate(3); //  two
                ByteBuffer b3 = ByteBuffer.allocate(5); //  three
                channel.read(new ByteBuffer[]{b1, b2, b3});
                b1.flip();
                b2.flip();
                b3.flip();
                debugAll(b1);
                debugAll(b2);
                debugAll(b3);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

运行结果

    +--------+-------------------- all ------------------------+----------------+
    position: [0], limit: [3]
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 6f 6e 65                                        |one             |
    +--------+-------------------------------------------------+----------------+
    +--------+-------------------- all ------------------------+----------------+
    position: [0], limit: [3]
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 74 77 6f                                        |two             |
    +--------+-------------------------------------------------+----------------+
    +--------+-------------------- all ------------------------+----------------+
    position: [0], limit: [5]
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 74 68 72 65 65                                  |three           |
    +--------+-------------------------------------------------+----------------+

7. Gathering Writes(集中写)

使用如下方式写入,可以将多个 buffer 的数据填充至 channel

    import java.io.IOException;
    import java.io.RandomAccessFile;
    import java.nio.ByteBuffer;
    import java.nio.channels.FileChannel;
    
    import static com.lilinchao.nio.bytebuffer_2.ByteBufferUtil.debugAll;
    
    /**
     * Created by lilinchao
     * Date 2022/5/25
     * Description 集中写Demo
     */
    public class GatheringWritesDemo {
        public static void main(String[] args) {
            try (RandomAccessFile file = new RandomAccessFile("datas/words.txt", "rw")) {
                FileChannel channel = file.getChannel();
                ByteBuffer d = ByteBuffer.allocate(4);
                ByteBuffer e = ByteBuffer.allocate(4);
                channel.position(11);
    
                d.put(new byte[]{'f', 'o', 'u', 'r'});
                e.put(new byte[]{'f', 'i', 'v', 'e'});
                d.flip();
                e.flip();
                debugAll(d);
                debugAll(e);
                channel.write(new ByteBuffer[]{d, e});
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

运行结果

    +--------+-------------------- all ------------------------+----------------+
    position: [0], limit: [4]
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 66 6f 75 72                                     |four            |
    +--------+-------------------------------------------------+----------------+
    +--------+-------------------- all ------------------------+----------------+
    position: [0], limit: [4]
             +-------------------------------------------------+
             |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
    +--------+-------------------------------------------------+----------------+
    |00000000| 66 69 76 65                                     |five            |
    +--------+-------------------------------------------------+----------------+

最后:调试工具类

    import io.netty.util.internal.StringUtil;
    
    import java.nio.ByteBuffer;
    
    import static io.netty.util.internal.MathUtil.isOutOfBounds;
    import static io.netty.util.internal.StringUtil.NEWLINE;
    
    /**
     * @Author: lilinchao
     * @Date: 2022/5/25
     * @Description:调试工具类
     */
    public class ByteBufferUtil {
        private static final char[] BYTE2CHAR = new char[256];
        private static final char[] HEXDUMP_TABLE = new char[256 * 4];
        private static final String[] HEXPADDING = new String[16];
        private static final String[] HEXDUMP_ROWPREFIXES = new String[65536 >>> 4];
        private static final String[] BYTE2HEX = new String[256];
        private static final String[] BYTEPADDING = new String[16];
    
        static {
            final char[] DIGITS = "0123456789abcdef".toCharArray();
            for (int i = 0; i < 256; i++) {
                HEXDUMP_TABLE[i << 1] = DIGITS[i >>> 4 & 0x0F];
                HEXDUMP_TABLE[(i << 1) + 1] = DIGITS[i & 0x0F];
            }
    
            int i;
    
            // Generate the lookup table for hex dump paddings
            for (i = 0; i < HEXPADDING.length; i++) {
                int padding = HEXPADDING.length - i;
                StringBuilder buf = new StringBuilder(padding * 3);
                for (int j = 0; j < padding; j++) {
                    buf.append("   ");
                }
                HEXPADDING[i] = buf.toString();
            }
    
            // Generate the lookup table for the start-offset header in each row (up to 64KiB).
            for (i = 0; i < HEXDUMP_ROWPREFIXES.length; i++) {
                StringBuilder buf = new StringBuilder(12);
                buf.append(NEWLINE);
                buf.append(Long.toHexString(i << 4 & 0xFFFFFFFFL | 0x100000000L));
                buf.setCharAt(buf.length() - 9, '|');
                buf.append('|');
                HEXDUMP_ROWPREFIXES[i] = buf.toString();
            }
    
            // Generate the lookup table for byte-to-hex-dump conversion
            for (i = 0; i < BYTE2HEX.length; i++) {
                BYTE2HEX[i] = ' ' + StringUtil.byteToHexStringPadded(i);
            }
    
            // Generate the lookup table for byte dump paddings
            for (i = 0; i < BYTEPADDING.length; i++) {
                int padding = BYTEPADDING.length - i;
                StringBuilder buf = new StringBuilder(padding);
                for (int j = 0; j < padding; j++) {
                    buf.append(' ');
                }
                BYTEPADDING[i] = buf.toString();
            }
    
            // Generate the lookup table for byte-to-char conversion
            for (i = 0; i < BYTE2CHAR.length; i++) {
                if (i <= 0x1f || i >= 0x7f) {
                    BYTE2CHAR[i] = '.';
                } else {
                    BYTE2CHAR[i] = (char) i;
                }
            }
        }
    
        /**
         * 打印所有内容
         * @param buffer
         */
        public static void debugAll(ByteBuffer buffer) {
            int oldlimit = buffer.limit();
            buffer.limit(buffer.capacity());
            StringBuilder origin = new StringBuilder(256);
            appendPrettyHexDump(origin, buffer, 0, buffer.capacity());
            System.out.println("+--------+-------------------- all ------------------------+----------------+");
            System.out.printf("position: [%d], limit: [%d]\n", buffer.position(), oldlimit);
            System.out.println(origin);
            buffer.limit(oldlimit);
        }
    
        /**
         * 打印可读取内容
         * @param buffer
         */
        public static void debugRead(ByteBuffer buffer) {
            StringBuilder builder = new StringBuilder(256);
            appendPrettyHexDump(builder, buffer, buffer.position(), buffer.limit() - buffer.position());
            System.out.println("+--------+-------------------- read -----------------------+----------------+");
            System.out.printf("position: [%d], limit: [%d]\n", buffer.position(), buffer.limit());
            System.out.println(builder);
        }
    
        private static void appendPrettyHexDump(StringBuilder dump, ByteBuffer buf, int offset, int length) {
            if (isOutOfBounds(offset, length, buf.capacity())) {
                throw new IndexOutOfBoundsException(
                        "expected: " + "0 <= offset(" + offset + ") <= offset + length(" + length
                                + ") <= " + "buf.capacity(" + buf.capacity() + ')');
            }
            if (length == 0) {
                return;
            }
            dump.append(
                    "         +-------------------------------------------------+" +
                            NEWLINE + "         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |" +
                            NEWLINE + "+--------+-------------------------------------------------+----------------+");
    
            final int startIndex = offset;
            final int fullRows = length >>> 4;
            final int remainder = length & 0xF;
    
            // Dump the rows which have 16 bytes.
            for (int row = 0; row < fullRows; row++) {
                int rowStartIndex = (row << 4) + startIndex;
    
                // Per-row prefix.
                appendHexDumpRowPrefix(dump, row, rowStartIndex);
    
                // Hex dump
                int rowEndIndex = rowStartIndex + 16;
                for (int j = rowStartIndex; j < rowEndIndex; j++) {
                    dump.append(BYTE2HEX[getUnsignedByte(buf, j)]);
                }
                dump.append(" |");
    
                // ASCII dump
                for (int j = rowStartIndex; j < rowEndIndex; j++) {
                    dump.append(BYTE2CHAR[getUnsignedByte(buf, j)]);
                }
                dump.append('|');
            }
    
            // Dump the last row which has less than 16 bytes.
            if (remainder != 0) {
                int rowStartIndex = (fullRows << 4) + startIndex;
                appendHexDumpRowPrefix(dump, fullRows, rowStartIndex);
    
                // Hex dump
                int rowEndIndex = rowStartIndex + remainder;
                for (int j = rowStartIndex; j < rowEndIndex; j++) {
                    dump.append(BYTE2HEX[getUnsignedByte(buf, j)]);
                }
                dump.append(HEXPADDING[remainder]);
                dump.append(" |");
    
                // Ascii dump
                for (int j = rowStartIndex; j < rowEndIndex; j++) {
                    dump.append(BYTE2CHAR[getUnsignedByte(buf, j)]);
                }
                dump.append(BYTEPADDING[remainder]);
                dump.append('|');
            }
    
            dump.append(NEWLINE +
                    "+--------+-------------------------------------------------+----------------+");
        }
    
        private static void appendHexDumpRowPrefix(StringBuilder dump, int row, int rowStartIndex) {
            if (row < HEXDUMP_ROWPREFIXES.length) {
                dump.append(HEXDUMP_ROWPREFIXES[row]);
            } else {
                dump.append(NEWLINE);
                dump.append(Long.toHexString(rowStartIndex & 0xFFFFFFFFL | 0x100000000L));
                dump.setCharAt(dump.length() - 9, '|');
                dump.append('|');
            }
        }
    
        public static short getUnsignedByte(ByteBuffer buffer, int index) {
            return (short) (buffer.get(index) & 0xFF);
        }
    }
阅读全文