java nio Buffer 学习笔记

4 篇文章 0 订阅

Buffer 是一个对象, 它包含一些要写入或者刚读出的数据,即数据的缓冲区。在 NIO 中加入 Buffer 对象,体现了新库与原 I/O 的一个重要区别,在面向流的 I/O 中,您将数据直接写入或者将数据直接读到 Stream 对象中。buffer + channel 类似原io包中的BufferedInputStream/BufferedOutputStream,对数据流做了缓冲,减少磁盘或者网络io次数,提高性能等。缓冲区实质上是一个数组。通常它是一个字节数组,但是也可以使用其他种类的数组。但是一个缓冲区不 仅仅 是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。
下面是Buffer的类结构图:
 

 
首先从最顶层的Buffer开始
四个私有属性用于处理缓冲区的数据。buffer类中定义如下:

 

private int mark = -1;  
private int position = 0;  
private int limit;  
private int capacity; 

   Capacity(容量):
        缓冲区能装载的最大数据量,缓冲区被创建时被设置,并不会被更改。
   Position(位置):
        被读取或被写入的下一个元素的索引,调用get( ) 和 put( )方法时会关联的更新position,换句话说,写入buffer时,Position代表buffer第一个未被写入元素的索引【即put下个元素存放的位置】,读取时,Position代表buffer代表第一个未被读取的索引【即get取时第一个元素位置】。
   Limit(限制):
        缓冲区中不能被读取或被写入的第一元素。换句话说,在缓冲区中可以使用的元素个数,写入buffer时,Limit为Capacity即能写入buffer最多元素个数,读取buffer是Limit为buffer中元素的个数即写入状态是Position的值。
   Mark(标记):
         被记录的位置,调用mark( )方法时设置mark=position。调用reset( )设置position = mark,mark属性未被设置时,默认为-1;


     四个属性的关系如下:
mark <= position <= limit <= capacity  


     创建buffer
     使用的是实现类中的allocate方法。创建一个容量大小为7的ByteBuffer示例如下:
 

ByteBuffer  readBuffer = ByteBuffer.allocate(7);

 

 


创建完毕buffer后可以被写入【即写模式】,position代表未被写入第一元素为0,被写入时limit 等于capacity为7代表缓冲区能写入的byte数,mark为-1(后面有原代码说明)。capacity为固定值7,其他三个属性在buffer被使用的能被修改。
     mark属性的看下原代码,在buffer的allocate方法如下

 

public static ByteBuffer allocate(int capacity) {  
(capacity < 0)  
 throw new IllegalArgumentException();  
urn new HeapByteBuffer(capacity, capacity);  
}  
    HeapByteBuffer的构造方法如下

 

class HeapByteBuffer  
    extends ByteBuffer  
{  
    // For speed these fields are actually declared in X-Buffer;  
    // these declarations are here as documentation  
    /*
    protected final byte[] hb;
    protected final int offset;
    */  
    HeapByteBuffer(int cap, int lim) {      // package-private  
    super(-1, 0, lim, cap, new byte[cap], 0);  
    /*
    hb = new byte[cap];
    offset = 0;
    */  
    }  
    super为 ByteBuffer,super的各个值属性如下,

 

ByteBuffer(int mark, int pos, int lim, int cap, // package-private  
     byte[] hb, int offset)  
   {  
super(mark, pos, lim, cap);  
this.hb = hb;  
this.offset = offset;  
   }  
    从这里我们可以发现mark的被设置为-1。
    上面的源码我们发现buffer实际是数组的包装,实际数据存储对象为一个数组,byteBuffer的构造函数中   new byte[cap],创建的就是一个byte的数组。
   Accessing(访问,读写buffer)
   API如下:

 

public abstract class ByteBuffer  
extends Buffer implements Comparable  
{  
// This is a partial API listing  
public abstract byte get( );  
public abstract byte get (int index);  
public abstract ByteBuffer put (byte b);  
public abstract ByteBuffer put (int index, byte b);  
}  
    填充:
    ByteBuffer在子类HeapByteBuffer有put方法的实现如下

 

public ByteBuffer put(byte x) {  
    hb[ix(nextPutIndex())] = x;  
    return this;  
 
    }  
//获取写入索引位置  
final int nextPutIndex() {  // package-private  
    //写入范围校验,必须为position和limit之间  
    if (position >= limit)  
        throw new BufferOverflowException();  
    //先将position返回,position再自加1  
    return position++;  
    } 
    从代码中我们可以看出put就是数据放置到数组对应的position位置,然后position=position+1指向下个空闲位置。从这里我们也看可以看出put值放入的范围为position位置到limit之间。
示例:

 

buffer.put((byte)'H').put((byte)'e').put((byte)'l').put((byte)'l') .put((byte)'o');  

    各属性值变化如下
 

       往里进行put时,只有position值发生变化,例如上面的填充了5个字节的大小,position值为5,指上buffer里面的第6个元素。
       put (int index, byte b)可以让我们操作buffer中已经操作过位置的值。

buffer.put((byte)'w');  

      变化后的图示如下
     
 
      使用 put (int index, byte b)并不会影响到buffer的相关属性值的变化
    Flipping(将读状态转换到写状态)
    buffer被填充后,我们怎样将数据读出来呢,flip(),能将buffer由写模式切换至读模式。

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

     其实flip主要是调整position位置为0,limit设置为position  ,mark 设置为-1。
     从这里我们可以看出如果执行了flip方法,直接往buffer里面写入值的话,整个buffer从0开始被重新,但是最多只能写入到limit,而如果执行读的话,我们从索引为0的开始读,最多读取limit个数据。
       上个buffer执行flip后各属性状态变成为
 
          从图可以看出buffer的position位置变为0,代表可以从0索引开始读取数据,limit设置为原来的position位置6,代表我能从索引位置0读取到5。
       Draining(从buffer读取)
      get方法是从buffer里面读取数据,在子类HeapByteBuffer有put方法的实现如下

public byte get() {  
   return hb[ix(nextGetIndex())];  
   }  
   //注意这里offset值为0  
   protected int ix(int i) {  
return i + offset;  
   }  
   
   //获取position值  
  final int nextGetIndex() {               // package-private  
   if (position >= limit)  
       throw new BufferUnderflowException();  
   return position++;  
   }  

    可以看出get方法获取的是position与limit之间的数据,每获取后position位置+1,即下个get索引所在位置,所以无论我们调用什么方法操作buffer后,最后使用get方法获取的都是position与limit之间的数据。
   调用示例如下

buffer.flip();  
System.out.println( (char)buffer.get() );  

buffer.mark();
System.out.println( (char)buffer.get() );  

buffer.reset();
System.out.println( (char)buffer.get() );

   上面的buffer我们flip后调用get整个存储变成
 

position指向下个索引位置即为1,其他属性不变化
mark后存储变成:


 get后:


 reset后:


 clear(清空buffer)
  

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

 清空比较明确,即调整position调整为0,limit 调整为capacity,mark调整为-1。我们的上面的示例buffer执行clear后各个属性情况如下
 

 从这里我们可以看出,clear()并未实际的清空数据,而只是调整相关属性。

 

构造方法:

ByteBuffer没有public的构造方法,而是通过一下三种方式来创建:

 

public static ByteBuffer allocate(int capacity)

public static ByteBuffer allocateDirect(int capacity)

public static ByteBuffer wrap(byte[] array, int offset, int length) 

 allocateallocateDirect分别调用了其子类HeapByteBufferDirectByteBuffer前者可以看出分配的buffer是在heap区域的,而后者是通过unsafe.allocateMemory(cap + ps);Java虚拟机外的内存中分配了一块,通过unsafe直接分配操作系统内存来使用。这块directMemory可以通过-XX:MaxDirectMemorySize来配置默认大小,通过fullGC回收。

直接使用系统内存的好处是可以减少操作系统内存到虚拟机内存的拷贝,而提高性能。



 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值