1、Buffer简介

Buffer本质是一个内存块,既可以写入数据,也可以读出数据。

java.nio中的Buffer类是一个非线程安全的类

Buffer类型说明:
**
Buffer是一个抽象类,对应于java的主要数据类型,nio中有8种缓冲区,分别如下

  • ByteBuffer : 使用最多的Buffer
  • CharBuffer
  • ShortBuffer
  • IntBuffer
  • DoubleBufffer
  • LongBuffer
  • FloatBuffer
  • MappedByteBuffer :专门用于内存映射

Buffer属性说明
capacity 容量,即可以容纳的最大数据量;在缓冲区创建时设置并且不能改变
limit 上限,缓冲区中当前的数据量
position 读写指针,缓冲区中下一个要被读或写的元素的索引
mark 标记,调用 mark()方法来设置 mark=position,再调用 reset()可以让 position 恢复到 mark 标记的位置即 position=mark

ByteBuffer 有以下重要属性

  • capacity
  • position
  • limit

一开始
0021.png
写模式下,position 是写入位置,limit 等于容量,下图表示写入了 4 个字节后的状态
0018.png
flip 动作发生后,position 切换为读取位置,limit 切换为读取限制
0019.png
读取 4 个字节后,状态
0020.png

clear 动作发生后,状态
0021.png

compact 方法,是把未读完的部分向前压缩,然后切换至写模式

0022.png

Buffer使用步骤:

(1)使用创建子类实例对象的 allocate()方法,创建一个 Buffer 类的实例对象。
(2)调用 put 方法,将数据写入到缓冲区中。
(3)写入完成后,在开始读取数据前,调用 Buffer.flip()方法,将缓冲区转换为读模式。
(4)调用 get 方法,从缓冲区中读取数据。
(5)读取完成后,调用 Buffer.clear() 或 Buffer.compact()方法,将缓冲区转换为写入模式。

2、Buffer实战

2.1、Buffer调试工具类

  1. package com.jx.netty.niobase.util;
  2. import io.netty.util.internal.StringUtil;
  3. import java.nio.ByteBuffer;
  4. import static io.netty.util.internal.StringUtil.NEWLINE;
  5. import static io.netty.util.internal.MathUtil.isOutOfBounds;
  6. /**
  7. * @Description:
  8. * @Author: zhangjx
  9. * @Date: 2021-05-07
  10. **/
  11. public class ByteBufferUtil {
  12. private static final char[] BYTE2CHAR = new char[256];
  13. private static final char[] HEXDUMP_TABLE = new char[256 * 4];
  14. private static final String[] HEXPADDING = new String[16];
  15. private static final String[] HEXDUMP_ROWPREFIXES = new String[65536 >>> 4];
  16. private static final String[] BYTE2HEX = new String[256];
  17. private static final String[] BYTEPADDING = new String[16];
  18. static {
  19. final char[] DIGITS = "0123456789abcdef".toCharArray();
  20. for (int i = 0; i < 256; i++) {
  21. HEXDUMP_TABLE[i << 1] = DIGITS[i >>> 4 & 0x0F];
  22. HEXDUMP_TABLE[(i << 1) + 1] = DIGITS[i & 0x0F];
  23. }
  24. int i;
  25. // Generate the lookup table for hex dump paddings
  26. for (i = 0; i < HEXPADDING.length; i++) {
  27. int padding = HEXPADDING.length - i;
  28. StringBuilder buf = new StringBuilder(padding * 3);
  29. for (int j = 0; j < padding; j++) {
  30. buf.append(" ");
  31. }
  32. HEXPADDING[i] = buf.toString();
  33. }
  34. // Generate the lookup table for the start-offset header in each row (up to 64KiB).
  35. for (i = 0; i < HEXDUMP_ROWPREFIXES.length; i++) {
  36. StringBuilder buf = new StringBuilder(12);
  37. buf.append(NEWLINE);
  38. buf.append(Long.toHexString(i << 4 & 0xFFFFFFFFL | 0x100000000L));
  39. buf.setCharAt(buf.length() - 9, '|');
  40. buf.append('|');
  41. HEXDUMP_ROWPREFIXES[i] = buf.toString();
  42. }
  43. // Generate the lookup table for byte-to-hex-dump conversion
  44. for (i = 0; i < BYTE2HEX.length; i++) {
  45. BYTE2HEX[i] = ' ' + StringUtil.byteToHexStringPadded(i);
  46. }
  47. // Generate the lookup table for byte dump paddings
  48. for (i = 0; i < BYTEPADDING.length; i++) {
  49. int padding = BYTEPADDING.length - i;
  50. StringBuilder buf = new StringBuilder(padding);
  51. for (int j = 0; j < padding; j++) {
  52. buf.append(' ');
  53. }
  54. BYTEPADDING[i] = buf.toString();
  55. }
  56. // Generate the lookup table for byte-to-char conversion
  57. for (i = 0; i < BYTE2CHAR.length; i++) {
  58. if (i <= 0x1f || i >= 0x7f) {
  59. BYTE2CHAR[i] = '.';
  60. } else {
  61. BYTE2CHAR[i] = (char) i;
  62. }
  63. }
  64. }
  65. /**
  66. * 打印所有内容
  67. * @param buffer
  68. */
  69. public static void debugAll(ByteBuffer buffer) {
  70. int oldlimit = buffer.limit();
  71. buffer.limit(buffer.capacity());
  72. StringBuilder origin = new StringBuilder(256);
  73. appendPrettyHexDump(origin, buffer, 0, buffer.capacity());
  74. System.out.println("+--------+-------------------- all ------------------------+----------------+");
  75. System.out.printf("position: [%d], limit: [%d]\n", buffer.position(), oldlimit);
  76. System.out.println(origin);
  77. buffer.limit(oldlimit);
  78. }
  79. /**
  80. * 打印可读取内容
  81. * @param buffer
  82. */
  83. public static void debugRead(ByteBuffer buffer) {
  84. StringBuilder builder = new StringBuilder(256);
  85. appendPrettyHexDump(builder, buffer, buffer.position(), buffer.limit() - buffer.position());
  86. System.out.println("+--------+-------------------- read -----------------------+----------------+");
  87. System.out.printf("position: [%d], limit: [%d]\n", buffer.position(), buffer.limit());
  88. System.out.println(builder);
  89. }
  90. private static void appendPrettyHexDump(StringBuilder dump, ByteBuffer buf, int offset, int length) {
  91. if (isOutOfBounds(offset, length, buf.capacity())) {
  92. throw new IndexOutOfBoundsException(
  93. "expected: " + "0 <= offset(" + offset + ") <= offset + length(" + length
  94. + ") <= " + "buf.capacity(" + buf.capacity() + ')');
  95. }
  96. if (length == 0) {
  97. return;
  98. }
  99. dump.append(
  100. " +-------------------------------------------------+" +
  101. NEWLINE + " | 0 1 2 3 4 5 6 7 8 9 a b c d e f |" +
  102. NEWLINE + "+--------+-------------------------------------------------+----------------+");
  103. final int startIndex = offset;
  104. final int fullRows = length >>> 4;
  105. final int remainder = length & 0xF;
  106. // Dump the rows which have 16 bytes.
  107. for (int row = 0; row < fullRows; row++) {
  108. int rowStartIndex = (row << 4) + startIndex;
  109. // Per-row prefix.
  110. appendHexDumpRowPrefix(dump, row, rowStartIndex);
  111. // Hex dump
  112. int rowEndIndex = rowStartIndex + 16;
  113. for (int j = rowStartIndex; j < rowEndIndex; j++) {
  114. dump.append(BYTE2HEX[getUnsignedByte(buf, j)]);
  115. }
  116. dump.append(" |");
  117. // ASCII dump
  118. for (int j = rowStartIndex; j < rowEndIndex; j++) {
  119. dump.append(BYTE2CHAR[getUnsignedByte(buf, j)]);
  120. }
  121. dump.append('|');
  122. }
  123. // Dump the last row which has less than 16 bytes.
  124. if (remainder != 0) {
  125. int rowStartIndex = (fullRows << 4) + startIndex;
  126. appendHexDumpRowPrefix(dump, fullRows, rowStartIndex);
  127. // Hex dump
  128. int rowEndIndex = rowStartIndex + remainder;
  129. for (int j = rowStartIndex; j < rowEndIndex; j++) {
  130. dump.append(BYTE2HEX[getUnsignedByte(buf, j)]);
  131. }
  132. dump.append(HEXPADDING[remainder]);
  133. dump.append(" |");
  134. // Ascii dump
  135. for (int j = rowStartIndex; j < rowEndIndex; j++) {
  136. dump.append(BYTE2CHAR[getUnsignedByte(buf, j)]);
  137. }
  138. dump.append(BYTEPADDING[remainder]);
  139. dump.append('|');
  140. }
  141. dump.append(NEWLINE +
  142. "+--------+-------------------------------------------------+----------------+");
  143. }
  144. private static void appendHexDumpRowPrefix(StringBuilder dump, int row, int rowStartIndex) {
  145. if (row < HEXDUMP_ROWPREFIXES.length) {
  146. dump.append(HEXDUMP_ROWPREFIXES[row]);
  147. } else {
  148. dump.append(NEWLINE);
  149. dump.append(Long.toHexString(rowStartIndex & 0xFFFFFFFFL | 0x100000000L));
  150. dump.setCharAt(dump.length() - 9, '|');
  151. dump.append('|');
  152. }
  153. }
  154. public static short getUnsignedByte(ByteBuffer buffer, int index) {
  155. return (short) (buffer.get(index) & 0xFF);
  156. }
  157. }

2.2、Buffer使用示例

  1. public static void main(String[] args) {
  2. IntBuffer intBuffer = IntBuffer.allocate(10);
  3. for (int i = 0; i < 5; i++) {
  4. intBuffer.put(i);
  5. System.out.println("写模式: position: " + intBuffer.position() + " limit: " + intBuffer.limit() + " capacity: " + intBuffer.capacity());
  6. }
  7. //将缓冲区切换为读模式
  8. intBuffer.flip();
  9. for (int i = 0; i < 5; i++) {
  10. System.out.println("读取: " + intBuffer.get() + " 读模式: position: " + intBuffer.position() + " limit: " + intBuffer.limit() + " capacity: " + intBuffer.capacity());
  11. }
  12. //rewind 倒带已经读完的数据,再读一遍
  13. intBuffer.rewind();
  14. for (int i = 0; i < 5; i++) {
  15. System.out.println("读取: " + intBuffer.get() + " 读模式: position: " + intBuffer.position() + " limit: " + intBuffer.limit() + " capacity: " + intBuffer.capacity());
  16. }
  17. //mark 和 reset
  18. intBuffer.rewind();
  19. for (int i = 0; i < 5; i++) {
  20. if(i == 2){
  21. intBuffer.mark();
  22. }
  23. System.out.println("读取: " + intBuffer.get() + " 读模式: position: " + intBuffer.position() + " limit: " + intBuffer.limit() + " capacity: " + intBuffer.capacity());
  24. }
  25. intBuffer.reset();
  26. System.out.println("读取: " + intBuffer.get() + " 读模式: position: " + intBuffer.position() + " limit: " + intBuffer.limit() + " capacity: " + intBuffer.capacity());
  27. //clear 清空缓冲区
  28. intBuffer.clear();
  29. intBuffer.put(1);
  30. System.out.println("读取: " + intBuffer.get() + " 读模式: position: " + intBuffer.position() + " limit: " + intBuffer.limit() + " capacity: " + intBuffer.capacity());
  31. }

2.3、字符串和ByteBuffer互相转化

        //字符串转为ByteBuffer

        //1、转为字节数组,写入ByteBuffer,仍处于写模式
        ByteBuffer strBuffer1 = ByteBuffer.allocate(16);
        strBuffer1.put( "hello".getBytes());
        ByteBufferUtil.debugAll(strBuffer1);

        //2、Charset 自动切换为读模式
        ByteBuffer strBuffer2 = StandardCharsets.UTF_8.encode("hello");
        ByteBufferUtil.debugAll(strBuffer2);

        //3、wrap 自动切换为读模式
        ByteBuffer strBuffer3 = ByteBuffer.wrap("hello".getBytes());
        ByteBufferUtil.debugAll(strBuffer3);


        //ByteBuffer转为字符串


        //strBuffer1仍然处于写模式,无法转为字符串
        String s1 = StandardCharsets.UTF_8.decode(strBuffer1).toString();
        System.out.println("s1: " + s1);

        String s2 = StandardCharsets.UTF_8.decode(strBuffer2).toString();
        System.out.println("s2: " +s2);

2.4、ByteBuffer 黏包,半包问题

网络上有多条数据发送给服务端,数据之间使用 \n 进行分隔但由于某种原因这些数据在接收时,被进行了重新组合,例如原始数据有3条为

  • Hello,world\n
  • I’m zhangsan\n
  • How are you?\n

变成了下面的两个 byteBuffer (黏包,半包)

  • Hello,world\nI’m zhangsan\nHo
  • w are you?\n

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

public class ByteBufferExam {

    public static void main(String[] args) {
        ByteBuffer source = ByteBuffer.allocate(32);
        //                     11            24
        source.put("Hello,world\nI'm zhangsan\nHo".getBytes());
        split(source);

        source.put("w are you?\nhaha!\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 len = i + 1 - source.position();
                ByteBuffer buffer = ByteBuffer.allocate(len);


                for (int j = 0; j < len; j++) {
                    byte b = source.get();
                    buffer.put(b);
                }
                ByteBufferUtil.debugAll(buffer);
            }
        }
        source.compact();
    }
}