Buffer

  1. Java NIO Buffer
    当我们需要与NIO Channel进行交互时,我们就需要使用到NIO Buffer,即数据从Buffer读取到Channel中,并且从Channel中写入到Buffer中。
    实际上,一个Buffer其实就是一块内存区域,我们可以在这个内存区域中进行数据的读写。NIO Buffer其实是这样的内存块的一个封装,并提供了一些操作方法让我们能够方便地进行数据的读写。
  2. Buffer类型有:
  • ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer
  1. NIO Buffer的基本使用
    使用NIO Buffer的步骤如下:
  • 将数据写入Buffer中

  • 调用Buffer.flip()方法,将NIO Buffer转换为读模式

  • 从Buffer中读取数据

  • 调用Buffer.clear()或Buffer.compact()方法,将Buffer转换为写模式。
    当我们将数据写入到Buffer中时,Buffer会记录我们已经写了多少的数据,当我们需要从Buffer中读取数据时,必须调用Buffer.flip()将Buffer切换为度模式。
    一旦读取了所有的Buffer数据,那么我们必须清理Buffer,让其从新可写,清理Buffer可以调用Buffer.clear()或Buffer.compact()

    public class Test {
        public static void main(String[] args) {
        IntBuffer intBuffer = IntBuffer.allocate(2);
        intBuffer.put(124342);
        intBuffer.put(3);
       intBuffer.flip();
      System.out.println(intBuffer.get());
      System.out.println(intBuffer.get());
      }
    }
    

分配了两个单位大小的IntBuffer,因此他可以写入两个int值
我们使用put方法将int值写入,然后使用flip方法将buffer转换为读模式,然后连续使用get方法从buffer中获取这两个int值。
没当调用一次get方法读取数据时,buffer的读指针就会向前移动一个单位长度(在这里是一个int长度)。

  1. Buffer属性
    一个Buffer由三个属性:
  • capacity
  • position
  • limit
    其中position和limit的含义和Buffer处于读模式或写模式有关,而capacity的含义与Buffer所处的模式无关
  1. capacity
    一个内存块会有一个固定的大小,即容量(capacity),我们最多写入capacity个单位的数据到Buffer中,例如一个DoubleBuffer,其Capacity是100,那么我们做多可以写入100个double数据。

  2. position
    当从一个Buffer中写入数据时,我们是从Buffer的一个确定的位置(position)开始写入。在最初的状态时,position的值是0。每当我们写入了一个单位的数据后,position就会递增一。
    当我们从Buffer中读取数据时,我们也是从某个特定的位置开始读取。当我们调用了flip()方法将Buffer从写模式转换到读模式时,position的值会自动被设置为0,每当我们读取一个单位的数据,position的值递增1。
    position表示了读写操作的位置指针。

  3. limit
    limit-position表示此时还可以写入/读取多少单位的数据

  4. 分配Buffer
    为了获取一个Buffer对象,我们首先需要分配内存空间。每个类型的Buffer都有一个allocate()方法,我们可以通过这个方法分配Buffer

    ByteBuffer buf = ByteBuffer.allocate(48);
    

这里我们分配了48*sizeof(Byte)字节的内存空间

  1. 关于Direct Buffer和Non-Direct Buffer的区别
  • Direct Buffer:

    • 所分配的内存不在JVM堆上,不受GC的管理(但是Direct Buffer的java对象是由GC管理的,因此当发生GC,对象被回收时,Direct Buffer也会被释放)
    • 因为Direct Buffer不在JVM堆上分配,因此Direct Buffer对应用程序的内存占用的影响就不那么明显(实际上还是镇用了那么多内存,但是JVM不好统计到非JVM管理的内存)
    • 申请和释放Direct Buffer的开销比较大,因此正确的使用Direct Buffer的方式是在初始化时申请一个Buffer,然后不断复用此Buffer,在程序结束后才释放此Buffer
    • 使用Direct Buffer时,当进行一些底层的系统IO操作时,效率会比较高,因为此时JVM不需要拷贝buffer中的内存到中间临时缓冲区。
  • Non-Direct buffer

    • 直接在JVM堆上进行内存的分配,本质上市byte[]数组的封装
    • 因为Non-Direct Buffer在JVM堆中,因此当进行操作系统底层IO操作时,会将次Buffer的内存复制到中间临时缓冲区,因此Non-Direct buffer的效率就较低
  1. 写数据到Buffer

    int bytesRead = inChannel.read(buf); //read into buffer.
    buf.put(127);
    
  2. 从Buffer中读取数据

        //read from buffer into channel.
    int bytesWritten = inChannel.write(buf);
    byte aByte = buf.get();
    
  3. 重置position
    Buffer.rewind()方法可以重置position的值为0,因此我们可以重新读取/写入Buffer了,如果是读模式,则重置的是读模式的position,如果是写模式,则重置的是写模式的position

  4. mark()和reset()
    我么可以通过调用Buffer.mark()将当前的position的值保存起来,随后可以通过调用Buffer.reset()方法将position的值恢复回来

    public class Test {
    
             public static void main(String[] args) {
             IntBuffer intBuffer = IntBuffer.allocate(2);
             intBuffer.put(1);
             intBuffer.put(2);
    
            intBuffer.flip();
            System.out.println(intBuffer.get());
            intBuffer.mark();
            System.out.println(intBuffer.get());
    
           System.out.println(intBuffer.position());
           intBuffer.reset();
          System.out.println(intBuffer.position());
          System.out.println(intBuffer.get());
        }
    }
    

这里我们写入两个int值,然后首先读取了一个值,此时读position的值为1
接着我们调用mark()方法将当前的position保存起来(在读模式,因此保存的是position),然后再次读取,此时position就是2了,接着使用reset()恢复原来的读position,因此读position就为1,可以再次读取数据。

  1. flip,rewind和clear的区别
  • flip
    源码

    public final Buffer flip() {
    limit = position;
    position = 0;
    mark = -1;
    return this;
    }
    

Buffer的读/写模式公用一个position和limit变量
当从写模式变为读模式时,原来的写position就变成了读模式的limit

  • rewind
    源码

     public final Buffer rewind() {
     position = 0;
      mark = -1;
      return this;
    }
    

rewind,即倒带,这个方法仅仅是将position置为0

  • clear
    源码

    public final Buffer clear() {
    position = 0;
    limit = capacity;
    mark = -1;
    return this;
    }  
    

根据源码我们知道,clear将position设置为0,将limit设置为capacity
clear方法使用场景
1 在一个已经写满数据的Buffer中,调用clear,可以从头读取buffer的数据。
2 为了将一个Buffer填充慢数据,可以调用clear,然后一直写入,直到达到limit

你可能感兴趣的:(Buffer)