ByteBuffer用法小结



在NIO中,数据的读写操作始终是与缓冲区相关联的.读取时信道(SocketChannel)将数据读入缓冲区,写入时首先要将发送的数据按顺序填入缓冲区.缓冲区是定长的,基本上它只是一个列表,它的所有元素都是基本数据类型.ByteBuffer是最常用的缓冲区,它提供了读写其他数据类型的方法,且信道的读写方法只接收ByteBuffer.因此ByteBuffer的用法是有必要牢固掌握的.

1.创建ByteBuffer
1.1 使用allocate()静态方法
    ByteBuffer buffer=ByteBuffer.allocate(256);
    以上方法将创建一个容量为256字节的ByteBuffer,如果发现创建的缓冲区容量太小,唯一的选择就是重新创建一个大小合适的缓冲区.

1.2 通过包装一个已有的数组来创建
    如下,通过包装的方法创建的缓冲区保留了被包装数组内保存的数据.
    ByteBuffer buffer=ByteBuffer.wrap(byteArray);

    如果要将一个字符串存入ByteBuffer,可以如下操作:
    String sendString="你好,服务器. ";
    ByteBuffer sendBuffer=ByteBuffer.wrap(sendString.getBytes("UTF-16"));

2.回绕缓冲区
  buffer.flip();
  这个方法用来将缓冲区准备为数据传出状态,执行以上方法后,输出通道会从数据的开头而不是末尾开始.回绕保持缓冲区中的数据不变,只是准备写入而不是读取.

3.清除缓冲区
  buffer.clear();
  这个方法实际上也不会改变缓冲区的数据,而只是简单的重置了缓冲区的主要索引值.不必为了每次读写都创建新的缓冲区,那样做会降低性能.相反,要重用现在的缓冲区,在再次读取之前要清除缓冲区.

4.从套接字通道(信道)读取数据
  int bytesReaded=socketChannel.read(buffer);
  执行以上方法后,通道会从socket读取的数据填充此缓冲区,它返回成功读取并存储在缓冲区的字节数.在默认情况下,这至少会读取一个字节,或者返回-1指示数据结束.

5.向套接字通道(信道)写入数据
  socketChannel.write(buffer);
  此方法以一个ByteBuffer为参数,试图将该缓冲区中剩余的字节写入信道.

 

 

-----------------------

ByteBuffer俗称缓冲器, 是将数据移进移出通道的唯一方式,并且我们只能创建一个独立的基本类型缓冲器,或者使用“as”方法从 ByteBuffer 中获得。ByteBuffer 中存放的是字节,如果要将它们转换成字符串则需要使用 Charset , Charset 是字符编码,它提供了把字节流转换成字符串 ( 解码 ) 和将字符串转换成字节流 ( 编码) 的方法。

private byte[] getBytes (char[] chars) {//将字符转为字节(编码)
   Charset cs = Charset.forName ("UTF-8");
   CharBuffer cb = CharBuffer.allocate (chars.length);
   cb.put (chars);
   cb.flip ();
   ByteBuffer bb = cs.encode (cb)
   return bb.array();
         }

private char[] getChars (byte[] bytes) {//将字节转为字符(解码)
      Charset cs = Charset.forName ("UTF-8");
      ByteBuffer bb = ByteBuffer.allocate (bytes.length);
      bb.put (bytes);
      bb.flip ();
       CharBuffer cb = cs.decode (bb);
  
   return cb.array();
}

通道也就是FileChannel,可以由FileInputStream,FileOutputStream,RandomAccessFile三个类来产生,例如:FileChannel fc = new FileInputStream().getChannel();与通道交互的一般方式就是使用缓冲器,可以把通道比如为煤矿(数据区),而把缓冲器比如为运煤车,想要得到煤一般都通过运煤车来获取,而不是直接和煤矿取煤。用户想得到数据需要经过几个步骤:

一、用户与ByteBuffer的交互

    向ByteBuffer中输入数据,有两种方式但都必须先为ByteBuffer指定容量

         ByteBuffer buff = ByteBuffer.allocate(BSIZE);

     a)  buff  =  ByteBuffer.wrap("askjfasjkf".getBytes())注意:wrap方法是静态函数且只能接收byte类型的数据,任何其他类型的数据想通过这种方式传递,需要进行类型的转换。

     b)  buff.put();可以根据数据类型做相应调整,如buff.putChar(chars),buff.putDouble(double)等

二、FileChannel 与 ByteBuffer的交互:

    缓冲器向通道输入数据

     FileChannel fc = new FileInputStream().getChannel();  

     fc.write(buff);

     fc.close();

三、 用户与ByteBuffer交互

    通道向缓冲器送入数据

    FileChannel fc =  new FileOutputStream().getChannel();

    fc.read( buff);

    fc.flip();

四、呈现给用户(三种方式)

  1)String encoding = System.getProperty("file.encoding");

   System.out.println("Decoded using " + encoding + ": "  + Charset.forName(encoding).decode(buff));   

  2)System.out.println(buff.asCharBuffer());//这种输出时,需要在输入时就进行编码getBytes("UTF-8")

  3) System.out.println(buff.asCharBuffer());//通过CharBuffer向ByteBuffer输入 buff.asCharBuffer().put。

 

fc.rewind();



---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

类ByteBuffer是Java nio程序经常会用到的类,也是重要类 ,我们通过源码分析该类的实现原理。


一.ByteBuffer类的继承结构

public abstract class ByteBuffer
extends Buffer
implements Comparable<ByteBuffer>

 

ByteBuffer的核心特性来自Buffer


二. ByteBuffer和Buffer的核心特性
A container for data of a specific primitive type. 用于特定基本类型数据的容器。
子类ByteBuffer支持除boolean类型以外的全部基本数据类型。


补充,回顾Java的基本数据类型

Java语言提供了八种基本类型,六种数字类型(四个整数型,两个浮点型),一种字符类型,一种布尔型。

1、整数:包括int,short,byte,long
2、浮点型:float,double
3、字符:char
4、布尔:boolean

 

类型    大小 最小值   最大值
byte    8-bit -128   +127
short  16-bit -2^15   +2^15-1
int       32-bit -2^31   +2^31-1
long    64-bit -2^63   +2^63-1
float    32-bit IEEE754   IEEE754
double 64-bit IEEE754   IEEE754
char    16-bit Unicode 0 Unicode 2^16-1
boolean ----- -----   ------     


本质上,Buffer也就是由装有特定基本类型数据的一块内存缓冲区和操作数据的4个指针变量(mark标记,position位置, limit界限,capacity容量)组成。不多说,上源码:

Java代码   收藏代码
  1. public abstract class Buffer {  
  2.   
  3.     // Invariants: mark <= position <= limit <= capacity  
  4.     private int mark = -1;  
  5.     private int position = 0;  
  6.     private int limit;  
  7.     private int capacity;  
  8.     ......  
  9. }  
  10.   
  11. public abstract class ByteBuffer  
  12.     extends Buffer  
  13.     implements Comparable<ByteBuffer>  
  14. {  
  15.   
  16.     // These fields are declared here rather than in Heap-X-Buffer in order to  
  17.     // reduce the number of virtual method invocations needed to access these  
  18.     // values, which is especially costly when coding small buffers.  
  19.     //  
  20.     final byte[] hb;   // Non-null only for heap buffers  
  21.     final int offset;  
  22.     boolean isReadOnly;   // Valid only for heap buffers  
  23.     ......  
  24. }  

 其中,字节数组final byte[] hb就是所指的那块内存缓冲区。


Buffer缓冲区的主要功能特性有:
a.Transferring data  数据传输,主要指可通过get()方法和put()方法向缓冲区存取数据,ByteBuffer提供存取除boolean以为的全部基本类型数据的方法。


b.Marking and resetting  做标记和重置,指mark()方法和reset()方法;而标记,无非是保存操作中某个时刻的索引位置。


c.Invariants 各种指针变量


d.Clearing, flipping, and rewinding 清除数据,位置(position)置0(界限limit为当前位置),位置(position)置0(界限limit不变),指clear()方法, flip()方法和rewind()方法。

 

e.Read-only buffers 只读缓冲区,指可将缓冲区设为只读。

 

f.Thread safety 关于线程安全,指该缓冲区不是线程安全的,若多线程操作该缓冲区,则应通过同步来控制对该缓冲区的访问。


g.Invocation chaining 调用链, 指该类的方法返回调用它们的缓冲区,因此,可将方法调用组成一个链;例如:
 b.flip();
 b.position(23);
 b.limit(42);
等同于
 b.flip().position(23).limit(42);

 


三.ByteBuffer的结构

ByteBuffer主要由是由装数据的内存缓冲区和操作数据的4个指针变量(mark标记,position位置, limit界限,capacity容量)组成。
内存缓冲区:字节数组final byte[] hb;
ByteBuffer的主要功能也是由这两部分配合实现的,如put()方法,就是向数组byte[] hb存放数据。

Java代码   收藏代码
  1. ByteBuffer bb = ByteBuffer.allocate(10);   
  2. // 向bb装入byte数据  
  3. bb.put((byte)9);  

 
底层源码的实现如下

Java代码   收藏代码
  1. class HeapByteBuffer  
  2.     extends ByteBuffer  
  3. {  
  4.     ......  
  5.     public ByteBuffer put(byte x) {  
  6.       hb[ix(nextPutIndex())] = x;  
  7.       return this;  
  8.     }  
  9.       
  10.     ......  
  11.     final int nextPutIndex() {      
  12.       if (position >= limit)  
  13.       throw new BufferOverflowException();  
  14.        return position++;  
  15.     }  
  16.     ......  
  17. }  

 

如上所述,bb.put((byte)9);执行时,先判断position 是否超过 limit,否则指针position向前移一位,将字节(byte)9存入position所指byte[] hb索引位置。

get()方法相似;

Java代码   收藏代码
  1. public byte get() {  
  2.    return hb[ix(nextGetIndex())];  
  3. }  

 

4个指针的涵义

position:位置指针。微观上,指向底层字节数组byte[] hb的某个索引位置;宏观上,是ByteBuffer的操作位置,如get()完成后,position指向当前(取出)元素的下一位,put()方法执行完成后,position指向当前(存入)元素的下一位;它是核心位置指针。

 

mark标记:保存某个时刻的position指针的值,通过调用mark()实现;当mark被置为负值时,表示废弃标记。

 

capacity容量:表示ByteBuffer的总长度/总容量,也即底层字节数组byte[] hb的容量,一般不可变,用于读取。

 

limit界限:也是位置指针,表示待操作数据的界限,它总是和读取或存入操作相关联,limit指针可以被  改变,可以认为limit<=capacity。

 

  ByteBuffer结构如下图所示



 

 

 

四. ByteBuffer的关键方法实现

  1.取元素

Java代码   收藏代码
  1. public abstract byte get();  
  2.   
  3. //HeapByteBuffer子类实现  
  4. public byte get() {  
  5.    return hb[ix(nextGetIndex())];  
  6. }  
  7.   
  8.   
  9. //HeapByteBuffer子类方法  
  10. final int nextGetIndex() {                
  11.     if (position >= limit)  
  12.  throw new BufferUnderflowException();  
  13.    return position++;  
  14. }   

  2.存元素

Java代码   收藏代码
  1. public abstract ByteBuffer put(byte b);  
  2.   
  3.  //HeapByteBuffer子类实现  
  4.  public ByteBuffer put(byte x) {  
  5.      hb[ix(nextPutIndex())] = x;  
  6.      return this;  
  7.  }  

    
  3.清除数据   

Java代码   收藏代码
  1. public final Buffer clear() {  
  2.     position = 0;  
  3.     limit = capacity;  
  4.     mark = -1;  
  5.     return this;  
  6. }  

     
    可见,对于clear()方法,ByteBuffer只是重置position指针和limit指针,废弃mark标记,并没有真正清空缓冲区/底层字节数组byte[] hb的数据;
    ByteBuffer也没有提供真正清空缓冲区数据的接口,数据总是被覆盖而不是清空。
    例如,对于Socket读操作,若从socket中read到数据后,需要从头开始存放到缓冲区,而不是从上次的位置开始继续/连续存放,则需要clear(),重置position指针,但此时需要注意,若read到的数据没有填满缓冲区,则socket的read完成后,不能使用array()方法取出缓冲区的数据,因为array()返回的是整个缓冲区的数据,而不是上次read到的数据。


  4. 以字节数组形式返回整个缓冲区的数据/byte[] hb的数据

Java代码   收藏代码
  1. public final byte[] array() {  
  2.     if (hb == null)  
  3.  throw new UnsupportedOperationException();  
  4.      if (isReadOnly)  
  5.         throw new ReadOnlyBufferException();  
  6.      return hb;  
  7. }  

 

  5.flip-位置重置

Java代码   收藏代码
  1. public final Buffer flip() {  
  2.     limit = position;  
  3.     position = 0;  
  4.     mark = -1;  
  5.     return this;  
  6. }  

    socket的read操作完成后,若需要write刚才read到的数据,则需要在write执行前执行flip(),以重置操作位置指针,保存操作数据的界限,保证write数据准确。   
    
 6.rewind-位置重置

Java代码   收藏代码
  1. public final Buffer rewind() {  
  2.      position = 0;  
  3.      mark = -1;  
  4.      return this;  
  5. }  

   Rewinds this buffer. The position is set to zero and the mark is discarded.
  和flip()相比较而言,没有执行limit = position;

 

7.判断剩余的操作数据或者剩余的操作空间

Java代码   收藏代码
  1. public final int remaining() {  
  2.     return limit - position;  
  3. }  

   常用于判断socket的write操作中未写出的数据;

 

 8.标记

Java代码   收藏代码
  1. public final Buffer mark() {  
  2.     mark = position;  
  3.     return this;  
  4. }  

   
  9.重置到标记

Java代码   收藏代码
  1. public final Buffer reset() {  
  2.     int m = mark;  
  3.     if (m < 0)  
  4.       throw new InvalidMarkException();  
  5.     position = m;  
  6.     return this;  
  7. }  

 
五.创建ByteBuffer对象的方式

   1.allocate方式

Java代码   收藏代码
  1. public static ByteBuffer allocate(int capacity) {  
  2.   if (capacity < 0)  
  3.       throw new IllegalArgumentException();  
  4.       return new HeapByteBuffer(capacity, capacity);  
  5. }  
  6.   
  7. HeapByteBuffer(int cap, int lim) {  // package-private  
  8.      super(-10, lim, cap, new byte[cap], 0);  
  9.      /* 
  10.      hb = new byte[cap]; 
  11.      offset = 0; 
  12.      */  
  13. }  
  14.   
  15.   
  16. // Creates a new buffer with the given mark, position, limit, capacity,  
  17. // backing array, and array offset  
  18. //  
  19. ByteBuffer(int mark, int pos, int lim, int cap, // package-private  
  20.         byte[] hb, int offset)  
  21. {  
  22.   super(mark, pos, lim, cap);  
  23.   this.hb = hb;  
  24.   this.offset = offset;  
  25. }  
  26.   
  27.   
  28. // Creates a new buffer with the given mark, position, limit, and capacity,  
  29. // after checking invariants.  
  30. //  
  31. Buffer(int mark, int pos, int lim, int cap) { // package-private  
  32.     if (cap < 0)  
  33.       throw new IllegalArgumentException();  
  34.      this.capacity = cap;  
  35.      limit(lim);  
  36.      position(pos);  
  37.      if (mark >= 0) {  
  38.        if (mark > pos)  
  39.           throw new IllegalArgumentException();  
  40.        this.mark = mark;  
  41.      }  
  42.  }  
  43. p;  

    由此可见,allocate方式创建ByteBuffer对象的主要工作包括: 新建底层字节数组byte[] hb(长度为capacity),mark置为-1,position置为0,limit置为capacity,capacity为用户指定的长度。

 

   2.wrap方式

Java代码   收藏代码
  1. public static ByteBuffer wrap(byte[] array) {  
  2. return wrap(array, 0, array.length);  
  3.    }  
  4.   
  5.   
  6.    public static ByteBuffer wrap(byte[] array,  
  7.                 int offset, int length)  
  8.    {  
  9.        try {  
  10.           return new HeapByteBuffer(array, offset, length);  
  11.        } catch (IllegalArgumentException x) {  
  12.           throw new IndexOutOfBoundsException();  
  13.        }  
  14.    }  
  15.   
  16.    HeapByteBuffer(byte[] buf, int off, int len) { // package-private  
  17.         super(-1, off, off + len, buf.length, buf, 0);  
  18.        /* 
  19.        hb = buf; 
  20.        offset = 0; 
  21.        */  
  22.     }  

   wrap方式和allocate方式本质相同,不过因为由用户指定的参数不同,参数为byte[] array,所以不需要新建字节数组,byte[] hb置为byte[] array,mark置为-1,position置为0,limit置为array.length,capacity置为array.length。

 

       六、结论

        由此可见,ByteBuffer的底层结构清晰,不复杂,源码仍是弄清原理的最佳文档。
读完此文,应该当Java nio的SocketChannel进行read或者write操作时,ByteBuffer的四个指针如何移动有了清晰的认识。






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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值