ByteBuffer详解---学习孙哥&不良人课程,整理学习笔记文章

76 阅读4分钟

4. ByteBuffer详解

4.1 ByteBuffer是抽象类,他的主要实现类为

抽象类:

接口 : 两者很相似,面向对象设计的过程中 ,什么情况需要 设计为抽象类?什么情况设计为接口

抽象的概念(名词)设计成抽象类 比如动物,形状 ,汽车

抽象的动作 功能 (动词) 设计成接口 比如DAO 是一个功能 ,设计成接口

比如Service 是一个动作,设计为接口

ByteBuffer 是缓存,是一个抽象的名词,所以设计为抽象类

1. HeapByteBuffer    堆ByteBuffer        JVM内的堆内存  --->  读写操作 效率低 会收到GC影响 
2. MappedByteBuffer(DirectByteBuffer)    OS内存        --->   读写操作 效率高 不会收到GC影响 。
3. 不主动析构,会造成内存的泄露

4. 内存泄漏:	开辟的内存没有析构和内存碎片  会导致原本100MB  实际处理80mb,但内存不够了
5. 内存溢出: 读取的内存超过实际的内存,叫做内存溢出  比如100mb内存,处理120mb数据

内存碎片是指开辟了两块内存 20MB 10MB,实际可以用的内存会小于70MB,这是因为

开辟的两块内存之间存在缝隙,天然的,一定会有,这个缝隙就叫做内存碎片

4.2 获取方式
 1. ByteBuffer.allocate(10);//一旦分配空间,不可以动态调整
 2. encode()
4.3 核心结构

ByteBuffer是一个类似数组的结构,整个结构中包含三个主要的状态

  1. Capacity

buffer的容量,类似于数组的size

  1. Position

buffer当前缓存的下标,在读取操作时记录读到了那个位置,在写操作时记录写到了那个位置。从0开始,每读取一次,下标+1

  1. Limit

读写限制,在读操作时,设置了你能读多少字节的数据,在写操作时,设置了你还能写多少字节的数据

所谓的读写模式,本质上就是这几个状态的变化。主要有Position和Limit联合决定了Buffer的读写数据区域。

代码演示

public class TestNIO4 {

   @Test
   public void test1(){

      ByteBuffer buffer=ByteBuffer.allocate(10);

      System.out.println("buffer.capacity()="+buffer.capacity());//10
      System.out.println("buffer.position()="+buffer.position());//0
      System.out.println("buffer.limit()="+buffer.limit());//10

   }
   @Test
   public void test2(){

      ByteBuffer buffer=ByteBuffer.allocate(10);
      buffer.get(new byte[]{1,2,3,4});

      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 test3(){

      ByteBuffer buffer=ByteBuffer.allocate(10);
      buffer.get(new byte[]{1,2,3,4});
      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

   }
   @Test
   public void test4(){

      ByteBuffer buffer=ByteBuffer.allocate(10);
      buffer.get(new byte[]{1,2,3,4});
      buffer.flip();
      while (buffer.hasRemaining()){
         byte b = buffer.get();
         System.out.println((char) b);
      }

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

   }
   @Test
   public void test5(){

      ByteBuffer buffer=ByteBuffer.allocate(10);
      buffer.get(new byte[]{1,2,3,4});
      buffer.flip();
      while (buffer.hasRemaining()){
         byte b = buffer.get();
         System.out.println((char) b);
      }
      buffer.clear();

      System.out.println("buffer.capacity()="+buffer.capacity());//10
      System.out.println("buffer.position()="+buffer.position());//0
      System.out.println("buffer.limit()="+buffer.limit());//10

   }
   @Test
   public void test6(){

      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


      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());//10
//      buffer.flip();
      System.out.println((char) buffer.get(0));//c
      System.out.println((char) buffer.get(1));//d


   }
}
最后总结一下
写入Buffer数据之前要设置写模式
1. 写模式
   1. 新创建的Buffer自动是写模式
   2. 调用了clear,compact方法
   
读取Buffer数据之前要设置读模式
2. 读模式
   1. 调用flip方法
4.4 核心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的位置产生影响。

rewind

public class TestNIO5 {
   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("-----------------------------------");
      //让position指向0,重新获取一遍数据
      buffer.rewind();
      while (buffer.hasRemaining()){
         System.out.println("buffer.get()="+ (char)buffer.get());
      }

   }
}

mark() 和 reset()

public class TestNIO6 {
   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();
      System.out.println("buffer.get()="+ (char)buffer.get());//c
      System.out.println("buffer.get()="+ (char)buffer.get());//d

   }
}

get(i) 获取特定position上的数据,但是不会对position的位置产生影响。

public class TestNIO7 {
   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.position()="+ buffer.position());//1
      System.out.println("buffer.get(0)="+ (char)buffer.get(0));//a
      System.out.println("buffer.position()="+ buffer.position());//1

   }
}
4.5 字符串操作

字符串存储到Buffer中

ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put("sunshuai".getBytes());

buffer.flip();
while (buffer.hasRemaining()) {
  System.out.println("buffer.get() = " + (char)buffer.get());
}
buffer.clear();


ByteBuffer buffer = Charset.forName("UTF-8").encode("sunshuai");

1、encode方法自动 把字符串按照字符集编码后,存储在ByteBuffer.
2、自动把ByteBuffer设置成读模式,且不能手工调用flip方法。

ByteBuffer buffer = StandardCharsets.UTF_8.encode("sunshuai");

while (buffer.hasRemaining()) {
  System.out.println("buffer.get() = " + (char) buffer.get());
}
buffer.clear();
1、encode方法自动 把字符串按照字符集编码后,存储在ByteBuffer.
2、自动把ByteBuffer设置成读模式,且不能手工调用flip方法。
  
ByteBuffer buffer = ByteBuffer.wrap("sunshuai".getBytes());
while (buffer.hasRemaining()) {
  System.out.println("buffer.get() = " + (char) buffer.get());
}
buffer.clear();
  • 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());
4.6 粘包与半包
1. \n 作为分割符,进行行的区分。
2. compact进行处理,把第一次没有读取完的数据,向前移动和后面的内容进行整合。

//1. 半包 粘包
public class TestNIO10 {
    public static void main(String[] args) {
        ByteBuffer buffer = ByteBuffer.allocate(50);
        buffer.put("Hi sunshuai\nl love y".getBytes());
        doLineSplit(buffer);
        buffer.put("ou\nDo you like me?\n".getBytes());
        doLineSplit(buffer);
    }

    // ByteBuffer接受的数据 \n
    private static void doLineSplit(ByteBuffer buffer) {
        buffer.flip();
        for (int i = 0; i < buffer.limit(); i++) {
            if (buffer.get(i) == '\n') {
                int length = i + 1 - buffer.position();
                ByteBuffer target = ByteBuffer.allocate(length);
                for (int j = 0; j < length; j++) {
                    target.put(buffer.get());
                }

                //截取工作完成
                target.flip();
                System.out.println("StandardCharsets.UTF_8.decode(target).toString() = " + StandardCharsets.UTF_8.decode(target).toString());
            }
        }
        buffer.compact();
    }
}