• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

NIO的BufferByteBuffer

武飞扬头像
咚咚?
帮助1

1 NIO中的Buffer

1.1 ByteBuffer详解(最常用的)

1.1.1 ByteBuffer 是抽象类,他的主要实现为 HeapByteBuffer 和 MappedByteBuffer
1. HeapByteBuffer    堆ByteBuffer        JVM内的堆内存  --->  读写操作 效率低 会收到GC影响 
2. MappedByteBuffer(DirectByteBuffer)    OS内存        --->   读写操作 效率高 不会收到GC影响 。 不主动析构,会造成内存的泄露  

3. 什么是内存溢出,什么是内存泄漏 ?
内存溢出:你真正操作的数据量集已经超出了我实际的内存 oom   100m的内存,实际操作的过程中 真实内存为120m 一次性的读取到内存 
内存泄露:100m的内存,但实际处理的数据为80m,内存就不够了  20m的内存就给泄露了
内存泄露的原因:1、没有主动析构,主动释放内存 2、内存碎片
1.1.2 获取方式
 1. ByteBuffer.allocate(10);//一旦分配空间,不可以动态调整
 2. encode()
1.1.3 核心结构
ByteBuffer是一个类似数组的结构,整个结构中包含三个主要的状态
1. Capacity 
   buffer的容量,类似于数组的size
2. Position
   buffer当前缓存的下标,在读取操作时记录读到了那个位置,在写操作时记录写到了那个位置。从0开始,每读取一次,下标 1
3. Limit
   读写限制,在读操作时,设置了你能读多少字节的数据,在写操作时,设置了你还能写多少字节的数据
   
   
   
所谓的读写模式,本质上就是这几个状态的变化。主要有Position和Limit联合决定了Buffer的读写数据区域。

学新通

1.1.4 代码演示
public class TestNio {

    @Test
    public void testStatus() {
        ByteBuffer allocate = ByteBuffer.allocate(10);
        System.out.println("allocate.capacity() = "   allocate.capacity());  // 10
        System.out.println("allocate.position() = "   allocate.position());  // 0
        System.out.println("allocate.limit() = "   allocate.limit()); //  10
    }

    @Test
    public void testStatus1() throws IOException {
        // 写模式
        ByteBuffer buffer = ByteBuffer.allocate(10);
        byte[] bytes = {'a', 'b', 'c', 'd'};
        buffer.put(bytes);
        System.out.println("buffer.capacity() = "   buffer.capacity());  // 10
        System.out.println("buffer.position() = "   buffer.position()); // 4
        System.out.println("buffer.limit() = "   buffer.limit()); // 10
    }

    @Test
    public void testStatus3() throws IOException {
        // 写模式
        ByteBuffer buffer = ByteBuffer.allocate(10);
        byte[] bytes = {'a', 'b', 'c', 'd'};
        buffer.put(bytes);
        // 转换成读模式
        buffer.flip();
        System.out.println("buffer.capacity() = "   buffer.capacity());  // 存储的容量  10
        System.out.println("buffer.position() = "   buffer.position()); // 0
        System.out.println("buffer.limit() = "   buffer.limit()); //  4
    }
    
    // flip把buffer转换成读模式为啥position为0呢   flip的源码一看就知道了
      public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }

    @Test
    public void testStatus4() throws IOException {
        // 写模式
        ByteBuffer buffer = ByteBuffer.allocate(10);
        byte[] bytes = {'a', 'b', 'c', 'd'};
        buffer.put(bytes);
        // 写模式
        buffer.clear();
//        buffer.compact(); 写模式 把之前没读完的数据往移动,继续写入后面的数据
        System.out.println("buffer.capacity() = "   buffer.capacity());  // 10
        System.out.println("buffer.position() = "   buffer.position()); // 0
        System.out.println("buffer.limit() = "   buffer.limit()); // 10
    }
    //clear转成写模式为啥position值为0,为啥我把50行注释掉 position为4呢  clear源码
     public final Buffer clear() {
        position = 0;
        limit = capacity;
        mark = -1;
        return this;
    }
    // 看完就是知道 调用clear方法会把position置为0也就是从头开始写入,但是buffer里面的数据不会消失,还是可以读取到达
    // 如果写入的话会把之前的数据给覆盖掉而且。
    

    @Test
    public void testStatus5() {
        // 创建一个buffer默认是写模式
        ByteBuffer buffer = ByteBuffer.allocate(10);
        // 往里面存储数据
        buffer.put(new byte[]{'a', 'b', 'c', 'd'});
        // 读模式
        buffer.flip();
        System.out.println("buffer.get() = "   (char) buffer.get());
        System.out.println("buffer.get() = "   (char) buffer.get());


        System.out.println("buffer.capacity() = "   buffer.capacity());  // 10
        System.out.println("buffer.position() = "   buffer.position()); // 2
        System.out.println("buffer.limit() = "   buffer.limit()); // 4


        System.out.println("==============================");
        // 变写模式了 写模式 把之前没读完的数据往移动,继续写入后面的数据
        buffer.compact();
        System.out.println("buffer.capacity() = "   buffer.capacity());  // 10
        System.out.println("buffer.position() = "   buffer.position()); // 2
        System.out.println("buffer.limit() = "   buffer.limit()); // 4
    }
}
学新通
1.1.5 总结
最后总结一下
写入Buffer数据之前要设置写模式
1. 写模式
   1. 新创建的Buffer自动是写模式
   2. 调用了clear,compact方法
   
读取Buffer数据之前要设置读模式
2. 读模式
   1. 调用flip方法

1.2 核心API

  • buffer中写入数据[写模式 创建一个bytebuffer ,clear(),compact()]

    1. channel的read方法
       channel.read(buffer)
    2. buffer的put方法
       buffer.put(byte)    buffer.put((byte)'a')..
       buffer.put(byte[])
    
  • 从buffer中读出数据

    1. channel的write方法
    
    2. buffer的get方法 //每调用一次get方法会影响,position的位置。
    
    3. rewind方法(手风琴),可以将postion重置成0 ,用于复读数据。
    
    4. mark&reset方法,通过mark方法进行标记(position),通过reset方法跳回标记,从新执行.
    
    5. get(i) 方法,获取特定position上的数据,但是不会对position的位置产生影响。
    
1.2.1 代码演示 rewind()方法
public static void main(String[] args) {
    ByteBuffer buffer = ByteBuffer.allocate(10);
    buffer.put(new byte[]{'a', 'b', 'c', 'd'});
    // 读模式
    buffer.flip();
    while (buffer.hasRemaining()) {
        System.out.println("buffer.get() = "   (char) buffer.get());
    }
    System.out.println("================");
    buffer.rewind(); // 把position的值设置为0
    while (buffer.hasRemaining()) {
        System.out.println("buffer.get() = "   (char) buffer.get());
    }
}
1.2.2 代码演示 mark&reset方法
public static void main(String[] args) {
    ByteBuffer buffer = ByteBuffer.allocate(10);
    buffer.put(new byte[]{'a', 'b', 'c', 'd'});
    buffer.flip();
    System.out.println("buffer.get() = "   (char) buffer.get());// a
    System.out.println("buffer.get() = "   (char) buffer.get());// b
    buffer.mark(); // 打一个标记
    System.out.println("buffer.get() = "   (char) buffer.get());// c
    System.out.println("buffer.get() = "   (char) buffer.get());// d
    buffer.reset(); // 回到打标记position位置
    System.out.println("buffer.get() = "   (char) buffer.get());// c
    System.out.println("buffer.get() = "   (char) buffer.get());// d
}
// 把position的值赋值给了 mark
public final Buffer mark() {
    mark = position;
    return this;
}

// reset源码 你在那里调用的mark方法,他就回到哪里
public final Buffer reset() {
    int m = mark;
    if (m < 0)
        throw new InvalidMarkException();
    position = m;
    return this;
}
学新通

1.3 字符串的操作

1.3.1 字符串存储到Buffer中
  • 代码演示

    // 默认是写模式
    ByteBuffer buffer = ByteBuffer.allocate(10);
    buffer.put("dongdong".getBytes());
    // 转换成读模式
    buffer.flip();
    while (buffer.hasRemaining()) {
      System.out.println("buffer.get() = "   (char)buffer.get());
    }
    // 又转换成写模式
    buffer.clear();
    
    
    // ByteBuffer buffer = Charset.forName("UTF-8").encode("dongdong");
    
    1、encode方法自动 把字符串按照字符集编码后,存储在ByteBuffer.
    2、自动把ByteBuffer设置成读模式,且不能手工调用flip方法。
    
    ByteBuffer buffer = StandardCharsets.UTF_8.encode("dongdong");
    
    while (buffer.hasRemaining()) {
      System.out.println("buffer.get() = "   (char) buffer.get());
    }
    buffer.clear();
    1、encode方法自动 把字符串按照字符集编码后,存储在ByteBuffer.
    2、自动把ByteBuffer设置成读模式,且不能手工调用flip方法。
      
    ByteBuffer buffer = ByteBuffer.wrap("dongdong".getBytes());
    while (buffer.hasRemaining()) {
      System.out.println("buffer.get() = "   (char) buffer.get());
    }
    buffer.clear();
    
    学新通
1.3.2 问题 为什么 ByteBuffer buffer = StandardCharsets.UTF_8.encode(“dongdong”) 时候 在调用 buffer.flip()方法读不到
ByteBuffer buffer = StandardCharsets.UTF_8.encode("dongdong");
System.out.println("buffer.capacity() = "   buffer.capacity());  // 8
System.out.println("buffer.position() = "   buffer.position()); // 0
System.out.println("buffer.limit() = "   buffer.limit()); // 8

buffer.flip();
System.out.println("buffer.capacity() = "   buffer.capacity());  // 8
System.out.println("buffer.position() = "   buffer.position()); // 0
System.out.println("buffer.limit() = "   buffer.limit()); // 0
while (buffer.hasRemaining()) {
    System.out.println("buffer.get() = "   (char) buffer.get());
}
buffer.clear();

// flip()方法的源码看就知道了
public final Buffer flip() {
    limit = position;
    position = 0;
    mark = -1;
    return this;
}

// 在我没有调用的时候我此时的 capacity 唯我传入的值的长度为8,而我的position 是我写入或者读取的下标 此时我还没有写入 所以为0 而我的limit的值是我此时能读或者能写入多少,此时我及没读又没写所以和我的传入值得长度一样为8
// 在我调用buffer.flip()方法得时候 通过源码可以看出 上来就把position赋值给了limit都等于0 肯定是读取不到数据的。
// 所以在使用ByteBuffer buffer = StandardCharsets.UTF_8.encode("dongdong") 或者 ByteBuffer buffer = Charset.forName("UTF-8").encode("dongdong") 这些api的时候 一定记得此时已经是读模式了,不需要再转成读模式了切记~~
学新通
1.3.3 Buffer中的数据转换成字符串
  • 代码演示

    ByteBuffer buffer = ByteBuffer.allocate(10);
    buffer.put("咚".getBytes());
    
    buffer.flip();
    CharBuffer result = StandardCharsets.UTF_8.decode(buffer);
    System.out.println("result.toString() = "   result.toString());
    

    1.4 粘包与半包

1.4.1 概念
  • 粘包:在传输数据的过程中,在一条消息中读取到了另一条的部分数据。
  • 半包:在传输数据的过程中,只接收到了部分数据,而非完整的数据。
1.4.2 代码
  • \n 作为分割符,进行行的区分。
  • compact方法进行处理,把第一次没有读取完的数据,向前移动和后面的内容进行整合。
public static void main(String[] args) {
    ByteBuffer buffer = ByteBuffer.allocate(50);
    buffer.put("Hi DongDong\nl love y".getBytes());
    doLineSplit(buffer);
    // buffer.put("ou\nDo you like me?\n".getBytes());
}

// byteBuffer接收的数据 \n
private static void doLineSplit(ByteBuffer buffer) {
    buffer.flip();
    for (int i = 0; i < buffer.limit(); i  ) {
        if (buffer.get(i) == '\n') {
            // 这里为什么要这样定义长度呢?
            // 是为了让buffer的空间不那么大造成资源的浪费
            int len = i   1 - buffer.position();
            ByteBuffer target = ByteBuffer.allocate(len);
            for (int j = 0; j < len; j  ) {
                // 这里不get的话上面的取的得position的只就为0
                target.put(buffer.get());
            }
            // 读模式
            target.flip();
            System.out.println("StandardCharsets.UTF_8.decode(target).toString() = "   StandardCharsets.UTF_8.decode(target).toString());
        }
    }
    // 写模式,把之前没读完的数据往移动,继续写入后面的数据
    buffer.compact();
}
学新通

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhgjefek
系列文章
更多 icon
同类精品
更多 icon
继续加载