ByteBuffer类是在Java NIO中常常使用的一个缓冲区类,使用它可以进行高效的IO操作,但是,如果对常用方法的理解有错误,那么就会出现意想不到的bug。

ByteBuffer类的常用方法

先来看看一个基本的程序

public void test() throws IOException
{
ByteBuffer buff = ByteBuffer.allocate(128);
FileChannel fin = null;
FileChannel fout = null;
try
{
fin = new FileInputStream("filein").getChannel();
fout = new FileOutputStream("fileout").getChannel();
while(fin.read(buff) != -1) {
buff.flip();
fout.write(buff);
buff.clear();
}
}
catch (FileNotFoundException e)
{ } finally {
try {
if(fin != null) {
fin.close();
}
if(fout != null) {
fout.close();
}
} catch(IOException e) {
throw e;
}
}
}

  

在test方法中,首先通过ByteBuffer.allocate()方法分配了一段内存空间,作为缓存,allocate方法对缓存自动清零,然后打开一个输入文件管道fin和一个输出文件管道fout,在循环中先从fin读出数据存放到buff缓冲区中,再将buff缓冲中的内容写入fout。当然这对于先从文件中读,然后再写这样的场景,这不是高效的做法。 
可以看到先从fin中读出数据后,首先要调用ByteBuffer.flip()方法,若将数据写入输出文件后,还要调用ByteBuffer.clear()方法,为什么要这样做呢?

ByteBuffer可以作为一个缓冲区,是因为它是内存中的一段连续的空间,在ByteBuffer对象内部定义了四个索引,分别是mark,position,limit,capacity,其中

  • mark用于对当前position的标记

  • position表示当前可读写的指针,如果是向ByteBuffer对象中写入一个字节,那么就会向position所指向的地址写入这个字节,如果是从ByteBuffer读出一个字节,那么就会读出position所指向的地址读出这个字节,读写完成后,position加1

  • limit是可以读写的边界,当position到达limit时,就表示将ByteBuffer中的内容读完,或者将ByteBuffer写满了。

  • capacity是这个ByteBuffer的容量,上面的程序中调用 ByteBuffer.allocate(128) 就表示创建了一个容量为capacity字节的ByteBuffer对象。

了解了这四个变量之后,再来看看前面的程序。之所以调用ByteBuffer.flip()方法是因为在向ByteBuffer写入数据后,position为缓冲区中刚刚读入的数据的最后一个字节的位置,flip方法将limit值置为position值,position置0,这样在调用get*()方法从ByteBuffer中取数据时就可以取到ByteBuffer中的有效数据,JDK中flip方法的代码如下:

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

  

在调用 four.write(buff) 时,就将buff缓冲区中的数据写入到输出管道,此时调用ByteBuffer.clear()方法为下次从管道中读取数据做准备,但是调用clear方法并不将缓冲区的数据清空,而是设置position,mark,limit这三个变量的值,JDK中clear方法的代码如下:

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

  

这个方法命名给人的感觉就是将数据清空了,但是实际上却不是的,它并没有清空缓冲区中的数据,而至重置了对象中的三个索引值,如果不清空的话,假设此次该ByteBuffer中的数据是满的,下次读取的数据不足以填满缓冲区,那么就会存在上一次已经处理的的数据,所以在判断缓冲区中是否还有可用数据时,使用ByteBuffer.hasRemaining()方法,在JDK中,这个方法的代码如下:

public final boolean hasRemaining() {
return position < limit;
}

  

在该方法中,比较了position和limit的值,用以判断是否还有可用数据。

在ByteBuffer类中,还有个方法是compact,对于ByteBuffer,其子类HeapByteBuffer的compact方法实现是这样的:

public ByteBuffer compact() {
System.arraycopy(hb, ix(position()), hb, ix(0), remaining());
position(remaining());
limit(capacity());
return this;
}

  

如果position()方法返回当前缓冲区中的position值,remaining()方法返回limit与position这段区间的长度,JDK中的remaining()方法代码如下

public final int remaining() {
return limit - position;
}

  

所以compact()方法中第一条语句作用是将数组hb当前position所指向的位置开始复制长度为limit-position的数据到hb数组的开始出,其中使用到了ix()函数,这个函数是将参数值加上一个offset值,offset即一个偏移值,在这样的比如一个这样的场景对于一个很大的缓冲区,将其分成两段,第一段的起始位置是p1,长度是q1,第二段起始位置是p2,长度是q2,那么可以分别将这两段包装成一个HeapByteBuffer对象,然后这两个HeapByteBuffer对象(ByteBuffer的子类,默认实现)的offset属性分别设置为p1和p2,这样就可以通过在内部使用ix()函数来简化ByteBuffer对外提供的接口,在使用者看来,与默认的ByteBuffer并没有区别。

在compact函数中,接着将当前的缓冲区的position索引置为 limit-position ,limit索引置为缓冲区的容量,这样调用compact方法中就可以将缓冲区的有效数据全部移到缓冲区的首部,而position指向下一个可写位置。

比如刚刚创建一个ByteBuffer对象buff时,position=0,limit=capacity,那么此时调用buff.hasRemaining()则会返回 true ,这样来判断缓冲区中是否有数据是不行的,因为此时缓冲区中的存储的全部是0,但是调用一次 compact() 方法就可以将position置为limit值,这样再通过buff.hasRemaining()就会返回 false ,可以与后面的逻辑一起处理了。

ByteBuffer还有一个名为mark的方法,该方法设置mark索引为position的值,JDK中的代码如下:

public final Buffer mark() {
mark = position;
return this;
}

  

与其功能相反的方法为reset方法,即将position的值设置为mark,JDK中的代码如下:

public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}

  

此外还有一个名为rewind的方法,这个方法将position索引置为0,mark索引置为-1,JDK中的代码如下:

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

  

通过这些方法,就可以很方便的操作一个缓冲区,关键是要理解这些方法具体的作用,以及对三个索引值的影响(capacity是不变的)。

ByteBuffer继承自Buffer类,上面的方法四个索引值都定义在Buffer类中,操作索引值的方法也都定义在Buffer类中。

总结

通过对ByteBuffer中的四个索引值操作方法的分析,加深了对ByteBuffer的理解。理解ByteBuffer和其他几种Buffer的关键是要理解在使用中各个方法是如何操作索引值的,特别要注意的是clear方法并没有清除缓冲区的内容。

深入理解ByteBuffer的更多相关文章

  1. 深入理解ByteBuffer(转)

    转:http://blog.csdn.net/workformywork/article/details/26699345?utm_source=tuicool&utm_medium=refe ...

  2. NIO 之 ByteBuffer

    前言 对于刚接触ByteBuffer人来说,想要完全理解会稍微有点困难,正巧前几天有人问我,想到好久没写文章,就整理一下. 概念理解 对于ByteBuffer的一些概念不理解的情况下,如果直接打开源码 ...

  3. JAVA IO 以及 NIO 理解

    由于Netty,了解了一些异步IO的知识,JAVA里面NIO就是原来的IO的一个补充,本文主要记录下在JAVA中IO的底层实现原理,以及对Zerocopy技术介绍. IO,其实意味着:数据不停地搬入搬 ...

  4. 堆外内存操作类ByteBuffer

    本篇主要讲解如何使用直接内存(堆外内存),并按照下面的步骤进行说明: 1 相关背景-->读写操作-->关键属性-->读写实践-->扩展-->参考说明 希望对想使用直接内存 ...

  5. ByteBuffer解析

    一.前言 前一篇文章我们介绍了Android中直播视频技术的基础大纲知识,这里就开始一一讲解各个知识点,首先主要来看一下视频直播中的一个重要的基础核心类:ByteBuffer,这个类看上去都知道了,是 ...

  6. 从tcp原理角度理解Broken pipe和Connection reset by peer的区别

    从tcp原理角度理解Broken pipe和Connection reset by peer的区别 http://lovestblog.cn/blog/2014/05/20/tcp-broken-pi ...

  7. ByteBuffer常用方法详解

    原文  http://blog.csdn.net/u012345283/article/details/38357851 缓冲区(Buffer)就是在内存中预留指定大小的存储空间用来对输入/输出(I/ ...

  8. Java I/O之NIO概念理解

    JDK1.4的java.nio.*包引入了新的Java I/O新类库,其目的在于提高速度.实际上,旧的I/O包已经使用nio重新实现过,以便充分利用这种速度提高,因此即使我们不显式地用nio编码,也能 ...

  9. 深入理解JVM(八)——java堆分析

    上一节介绍了针对JVM的监控工具,包括JPS可以查看当前所有的java进程,jstack查看线程栈可以帮助你分析是否有死锁等情况,jmap可以导出java堆文件在MAT工具上进行分析等等.这些工具都非 ...

随机推荐

  1. 稳固而窒息 jquery attr 和 Prop的区别

    通常在获取或者设置checked,selected,readonly,disabled等的时候使用prop效果更好 attr是通过setAtrribute和getAttribute来设置的,使用的是D ...

  2. selenium 实现浏览器 前进和后退

    #coding=utf-8from selenium import webdriver driver = webdriver.Chrome()driver.get("https://www. ...

  3. 如何让maven 将工程依赖的jar 复制到WEB-INF/lib 目录下

    1.在默认生命周期的compile阶段执行 maven-dependency-plugin:copy-dependencies命令即可:<plugin><groupId>org ...

  4. SQL Server2005 两台服务器上的数据库互相同步(转载)

    1.1测试环境 Item 发布机 A 订阅机 B OS Windows 2003 Server Windows 2003 Server SQL SQL Server 2005 企业版 SQL Serv ...

  5. 如何查看Linux操作系统的位数

    如何查看Linux操作系统的位数 1.编程实现: 在程序中返回sizeof(int)的值,返回的结果是操作系统的字节数.若返回4则是32位操作系统,返回8即是64位. 2.2.getconf命令: g ...

  6. Android Study 之 初识ButterKnife(8.5.1)及简单运用

    LZ-Says:突然间不知道说什么好,祝大家编码无bug吧~ 前言 话说,Android开发的兄弟们都知道,每次初始化控件,设置对应的事件.写的那点过程多并且恶心.我们先一块回想下不堪的以前~ 那些年 ...

  7. 【Android】15.3 Notification基础知识

    分类:C#.Android.VS2015: 创建日期:2016-02-29 一.如何向用户发出通知 1.使用Toast通知用户 前台任务中的通知(Notifications)一般用于长时间显示用户正在 ...

  8. id ,NSObject, id<NSObject>区别

    转自:http://blog.csdn.net/happytengfei/article/details/11473931 我们经常会混淆以下三种申明(我是没有留意过):    1. id foo1; ...

  9. 上传图片流到服务器(AFN方法) (多张图片)(图片流)

      上传图片流到服务器(AFN方法) (多张图片)(图片流) 第一步//获取图片 UIAlertController *actionSheet = [UIAlertController alertCo ...

  10. hadoop之mapreduce编程实例(系统日志初步清洗过滤处理)

    刚刚开始接触hadoop的时候,总觉得必须要先安装hadoop集群才能开始学习MR编程,其实并不用这样,当然如果你有条件有机器那最好是自己安装配置一个hadoop集群,这样你会更容易理解其工作原理.我 ...