一、Buffer 读API

  • Buffer rewind() 不改变极限,把位置设置为0,表示从头开始读取。
    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(10);
        buffer.put(new byte[]{'a','b','c','d'});
        buffer.flip();
        System.out.println((char) buffer.get());
        System.out.println((char) buffer.get());
        System.out.println((char) buffer.get());
        System.out.println((char) buffer.get());
        buffer.rewind();
        System.out.println((char) buffer.get());
        System.out.println((char) buffer.get());
    }

结果是:a、b、c、d、a、b
调用rewind之后,从头开始再次读取。

  • Buffer reset() 将此缓冲区的位置重置为先前标记的位置。
  • Buffer mark() 将此缓冲区的标记设置在其位置。
    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(10);
        buffer.put(new byte[]{'a','b','c','d'});
        buffer.flip();
        buffer.get();
        buffer.get();
        ByteBufferUtil.debugAll(buffer);
        //此时position=2。标记此位置
        buffer.mark();
        buffer.get();
        buffer.get();
        //这里读取完毕,此时从标记位置开始读取
        buffer.reset();
        System.out.println((char) buffer.get());
        System.out.println((char) buffer.get());
    }

结果是c、d

  • abstract byte get(int index) 获取制定位置的字节,不改变位置。
    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(10);
        buffer.put(new byte[]{'a','b','c','d'});
        buffer.flip();
        System.out.println((char) buffer.get(2)); //c
        ByteBufferUtil.debugAll(buffer);
    }

在这里插入图片描述

二、字符串和Buffer的转换

    public static void main(String[] args) {
        ByteBuffer buffer1 = ByteBuffer.allocate(16);
        buffer1.put("hello".getBytes());
        debugAll(buffer1);
    }

在这里插入图片描述
此时没有切换到读模式。所以要想读取,还是调用flip()方法;

使用StandardCharsets也可以构造buffer。而且极限根据字节大小设置。并且自动切换到读模式。

    public static void main(String[] args) {
        ByteBuffer buffer2 = StandardCharsets.UTF_8.encode("hello");
        debugAll(buffer2);
    }

在这里插入图片描述

  ByteBuffer buffer3 = ByteBuffer.wrap("hello".getBytes());
  debugAll(buffer3);

在这里插入图片描述
ByteBuffer.wrap的作用和StandardCharsets一样的。也是自动切换读取模式。

StandardCharsets.UTF_8.decode转化buffer为字符串。

        //转化字符串
        String s=StandardCharsets.UTF_8.decode(buffer2).toString();
        System.out.println(s);
        //针对buffer1.因为没有转化读取模式,所以这里需要调用flip
        buffer1.flip();
        String s2=StandardCharsets.UTF_8.decode(buffer1).toString();
        System.out.println(s2);

三、分散读取

假设word1.txt中有数据onetwothree。我们需要一次性读取。

    public static void main(String[] args) {
        //twr Java7快速关闭
        try (FileChannel channel = new RandomAccessFile("word1.txt", "r").getChannel()) {
            ByteBuffer buffer1 = ByteBuffer.allocate(3);
            ByteBuffer buffer2 = ByteBuffer.allocate(3);
            ByteBuffer buffer3 = ByteBuffer.allocate(5);
            //一次性读取
            channel.read(new ByteBuffer[]{buffer1,buffer2,buffer3});
            buffer1.flip();
            buffer2.flip();
            buffer3.flip();
            debugAll(buffer1);
            debugAll(buffer2);
            debugAll(buffer3);
        } catch (IOException e) {
        }
    }

在这里插入图片描述

四、集中写

将one、two、three一次性写入buffer.

    public static void main(String[] args) {
        ByteBuffer buffer1 = StandardCharsets.UTF_8.encode("one");
        ByteBuffer buffer2 = StandardCharsets.UTF_8.encode("two");
        ByteBuffer buffer3 = StandardCharsets.UTF_8.encode("three");
        try (FileChannel channel = new RandomAccessFile("word2.txt", "rw").getChannel()) {
            channel.write(new ByteBuffer[]{buffer1,buffer2,buffer3});
        } catch (IOException e) {
        }
    }

五、黏包和半包

现象

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

Hello,world\n
I’m Nyima\n
How are you?\n
变成了下面的两个 byteBuffer (粘包,半包)

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

出现原因

1 黏包

发送方在发送数据时,并不是一条一条地发送数据,而是将数据整合在一起,当数据达到一定的数量后再一起发送。这就会导致多条信息被放在一个缓冲区中被一起发送出去

2 半包

接收方的缓冲区的大小是有限的,当接收方的缓冲区满了以后,就需要将信息截断,等缓冲区空了以后再继续放入数据。这就会发生一段完整的数据最后被截断的现象

解决办法

通过get(index)方法遍历ByteBuffer,遇到分隔符时进行处理。注意:get(index)不会改变position的值
记录该段数据长度,以便于申请对应大小的缓冲区
将缓冲区的数据通过get()方法写入到target中
调用compact方法切换模式,因为缓冲区中可能还有未读的数据

public class BufferNianBao {

    public static void main(String[] args) {
        //模拟黏包的半包的读取
         ByteBuffer buffer = ByteBuffer.allocate(32);
        // 模拟粘包+半包
        buffer.put("Hello,world\nI'm Gosaint\nHo".getBytes());
        // 调用split函数处理
        split(buffer);
        buffer.put("w are you?\n".getBytes());
        split(buffer);
    }

    private static void split(final ByteBuffer buffer) {
        buffer.flip();
        for(int i=0;i<buffer.limit();i++){
            if (buffer.get(i)=='\n') {
                //遇到\n,表示一个完整的语句。写入的buffer
                int length=i+1-buffer.position();
                ByteBuffer target = ByteBuffer.allocate(length);
                //将数据写入target
                for (int j = 0; j < target.limit(); j++) {
                    // 将buffer中的数据写入target中
                    target.put(buffer.get());
                }
                debugAll(target);
            }
        }
        //读取完毕之后读取剩余的部分,不能使用clear。clear会从头开始的
        buffer.compact();
    }
}
Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐