前面介绍了文件通道的读写操作,其中用到字节缓存ByteBuffer,它是位于通道内部的存储空间,也是通道唯一可用的存储形式。ByteBuffer有两种构建方式,一种是调用静态方法wrap,根据输入的字节数组生成对应的缓存对象;另一种是调用静态方法allocateDirect,根据输入的数值分配指定大小的空缓存。字节缓存又是一种特殊的存储空间,因为它可能会被多次读写,所以为了有效地控制读写操作,Java给它设计了下列五种概念:容量、当前限制量、当前位置、本次剩余空间、标记位置,分别说明如下:
1、容量(capacity):指的是字节缓存的整个长度。容量大小可通过缓存对象的capacity方法获得。
2、当前限制量(limit):指的是当前读写操作所能处理的最大空间大小。当前限制量可通过缓存对象的limit方法获得(不带输入参数),携带输入参数的limit方法用来设置当前限制量的数值。如果不设置当前限制量的大小,则limit数值默认为字节缓存的容量大小。
3、当前位置(position):指的是字节缓存当前操作的起始位置。当前位置可通过缓存对象的position方法获得(不带输入参数),携带输入参数的position方法用来设置当前位置的数值。字节缓存一开始的当前位置是0,每次进行读写操作位置之后,当前位置都会往后跟着挪动。
4、本次剩余空间(remaining):它的数值等于当前限制量减去当前位置(即limit-position)。本次剩余空间可通过缓存对象的remaining方法获得。
5、标记位置(mark):其概念类似缓存输入流的标记,同样是调用mark方法在当前位置做个标记,以便后续调用reset方法能够回到上次标记的位置。
举个例子,现在分配了一个容量大小为10的字节缓存,并且设置它的当前限制量为8,接着将当前位置移到第三个字节处(下标为2),那么该字节缓存的存储结构应当如下图所示。


搞清楚了字节缓存的内部结构,再来看与字节缓存有关的数据流向。字节缓存与磁盘文件之间通过文件通道FileChannel交互,与内存字符串之间通过字节数组byte[]交互,于是内存中的一个字符串想要与磁盘上的某个文件内容相互转换的话,就存在以下两种数据流转过程:
1、把字符串写入文件,此时数据流向为:字符串String→字节数组byte[]→字节缓存ByteBuffer→指定路径的文件。
2、把文件内容读到字符串,此时数据流向为:指定路径的文件→字节缓存ByteBuffer→字节数组byte[]→字符串String。
其中与字节缓存有关的读写操作又可拆分为下列四种方法调用:
1、字节数组byte[]→字节缓存ByteBuffer,该操作除了调用ByteBuffer的静态方法wrap之外,还能通过缓存对象的put方法往字节缓存写入字节数组。
2、字节缓存ByteBuffer→指定路径的文件,该操作需要调用通道对象的write方法,往磁盘文件写入字节缓存中的数据。
3、指定路径的文件→字节缓存ByteBuffer,该操作需要调用通道对象的read方法,把磁盘文件中的数据读到字节缓存。
4、字节缓存ByteBuffer→字节数组byte[],该操作需要通过缓存对象的get方法,把字节缓存中的数据取到字节数组。
详细的数据流转过程可见下图,其中动作①和动作②实现了将字符串写入文件的功能,动作③和动作④实现了将文件内容读到字符串的功能。


注意到上图的动作①与动作③都是把数据输入给字节缓存,因此这两个动作可视为对字节缓存的写操作。而动作②与动作④都是从字节缓存中取出数据,因此这两个动作可视为对字节缓存的读操作。那么反复读写可能产生不同的处理需求,比如把当前位置挪回字节缓存的开头,接下来是要写入数据还是读出数据,为此ByteBuffer又提供了下列四个方法:
clear:缓冲区数据写入通道之后,如果还想把新数据写入缓冲区,就要先调用clear方法清空它。
compact:只清除已经读过的数据,剩余的未读数据会移到缓冲区开头,新增的数据将加到未读数据后面。
flip:把缓冲区从写模式切换到读模式。从缓冲区读取数据之前,必须先调用flip方法。
rewind:让缓冲区的指针回到开头,以便重新再来一遍。
上面的四个方法在部分功能上互有异同点,为了更好地梳理它们之间的区别,下面整理了一个表格,说明每个方法在调用之后将会引起哪些参数的变化。
     position      limit     mark
clear    0     容量大小     -1
compact   0     容量大小    -1
flip     0    上次的当前位置   -1
rewind     0     保持不变        -1

就具体的代码逻辑而言,一般在写入字节缓存之前(上图的动作①与动作③),需要先调用compact方法;在读取字节缓存之前(上图的动作②与动作④),需要先调用flip方法。当然如果是创建字节缓存后的第一次操作,就不必调用compact方法或者flip方法,因为在一开始字节缓存的当前位置都是指向0,无需再将当前位置挪回缓存开头了。回头看上一篇文章末尾通过文件通道读取文件的代码片段:

			int size = (int) channel.size(); // 获取文件通道的大小(即文件长度)
// 分配指定大小的字节缓存
ByteBuffer buffer = ByteBuffer.allocateDirect(size);
channel.read(buffer); // 把文件通道中的数据读到字节缓存
buffer.flip(); // 把缓冲区从写模式切换到读模式。从缓冲区读取数据之前,必须先调用flip方法
byte[] bytes = new byte[size]; // 创建与文件大小相同长度的字节数组
buffer.get(bytes); // 把字节缓存中的数据取到字节数组

根据前面的文字介绍,能够很好地解释以上代码的方法调用次序。由于通道对象的read方法是创建字节缓存之后的首个读写操作,因此无需先调用compact方法;而缓存对象的get方法不是首个读写操作,就必须在get之前先调用flip方法了。

更多Java技术文章参见《Java开发笔记(序)章节目录

Java开发笔记(九十三)深入理解字节缓存的更多相关文章

  1. Java开发笔记(八十九)缓存字节I/O流

    文件输出流FileOutputStream跟FileWriter同样有个毛病,每次调用write方法都会直接写到磁盘,使得频繁的写操作性能极其低下.正如FileWriter搭上了缓存兄弟Buffere ...

  2. Java开发笔记(三十五)字符串格式化

    前面介绍了字符串变量的四种赋值方式,对于简单的赋值来说完全够用了,即便是两个字符串拼接,也只需通过加号把两个目标串连起来即可.但对于复杂的赋值来说就麻烦了,假设现在需要拼接一个很长的字符串,字符串内部 ...

  3. Java开发笔记(三十)大小数BigDecimal

    前面介绍的BigInteger只能表达任意整数,但不能表达小数,要想表达任意小数,还需专门的大小数类型BigDecimal.如果说设计BigInteger的目的是替代int和long类型,那么设计Bi ...

  4. Java开发笔记(三十二)字符型与整型相互转化

    前面提到字符类型是一种新的变量类型,然而编码实践的过程中却发现,某个具体的字符值居然可以赋值给整型变量!就像下面的例子代码那样,把字符值赋给整型变量,编译器不但没报错,而且还能正常运行! // 字符允 ...

  5. Java开发笔记(三十四)字符串的赋值及类型转换

    不管是基本的char字符型,还是包装字符类型Character,它们的每个变量只能存放一个字符,无法满足对一串字符的加工.为了能够直接操作一连串的字符,Java设计了专门的字符串类型String,该类 ...

  6. Java开发笔记(三十六)字符串的常用方法

    不管是给字符串赋值,还是对字符串格式化,都属于往字符串填充内容,一旦内容填充完毕,则需开展进一步的处理.譬如一段Word文本,常见的加工操作就有查找.替换.追加.截取等等,按照字符串的处理结果异同,可 ...

  7. Java开发笔记(三十七)利用正则串分割字符串

    前面介绍了处理字符串的常用方法,还有一种分割字符串的场景也很常见,也就是按照某个规则将字符串切割为若干子串.分割规则通常是指定某个分隔符,根据字符串内部的分隔符将字符串进行分割,例如逗号.空格等等都可 ...

  8. Java开发笔记(三十八)利用正则表达式校验字符串

    前面多次提到了正则串.正则表达式,那么正则表达式究竟是符合什么定义的字符串呢?正则表达式是编程语言处理字符串格式的一种逻辑式子,它利用若干保留字符定义了形形色色的匹配规则,从而通过一个式子来覆盖满足了 ...

  9. Java开发笔记(三十九)日期工具Date

    Date是Java最早的日期工具,编程中经常通过它来获取系统的当前时间.当然使用Date也很简单,只要一个new关键字就能创建日期实例,就像以下代码示范的那样: // 创建一个新的日期实例,默认保存的 ...

  10. Java开发笔记(三十一)字符类型的表达

    前面介绍的Java编程,要么是与数字有关的计算,要么是与逻辑有关的推理,充其量只能实现计算器和状态机.若想让Java运用于更广阔的业务领域,就得使其支撑更加血肉丰满的业务场景,而丰满的前提是能够表达大 ...

随机推荐

  1. MVC为Html对象建立一个扩展方法,使用自己的控件就像使用TextBox一样方便

    先看一下我想要的结果: 很容易它就是一个单选按钮组,当我后台为Html对象(HtmlHelper的一个实例,它被定义在System.Web.Mvc名称空间下的WebViewPage类,即它对于所有MV ...

  2. R.layout引用不了布局文件

    删除import android.R 引用包所在的R文件..

  3. ECharts 使用

    最近项目中要做图形报表,要求使用echarts实现,图形报表有很多中实现之前也接触过,但echarts还是头一次听说,正好可以趁这个机会好好学习一下它. 之前不知道就不知道啦,现在知道了就了不得了,一 ...

  4. Ubuntu18开启redis服务自启动

    设置redis服务开机自启动. 1.创建配置文件夹 sudo mkdir /etc/redis sudo cp /usr/local/redis/redis.conf /etc/redis sudo ...

  5. 什么叫强类型的DATASET

    强类型DataSet,是指需要预先定义对应表的各个字段的属性和取值方式的数据集.对于所有这些属性都需要从DataSet, DataTable, DataRow继承,生成相应的用户自定义类.强类型的一个 ...

  6. 使用C#开发HTTP服务器系列之访问主页

    各位朋友大家好,我是秦元培,欢迎大家关注我的博客,我的博客地址是http://qinyuanpei.com.在这个系列文章的第一篇中,我们着重认识和了解了HTTP协议,并在此基础上实现了一个可交互的W ...

  7. ValueError: Some of types cannot be determined by the first 100 rows, please try again with sampling

    ValueError: Some of types cannot be determined by the first 100 rows, please try again with sampling ...

  8. ['1' for i in range(4)]

    ' for i in range(4)]) 结果: [']

  9. E20180309-hm-xa

    conformance   n. 顺应,一致; symmetric   adj. 相称性的,均衡的; raw  adj. 生的,未加工的; 无经验的; 新近完成的; 发炎的,疼痛的; exceed  ...

  10. P3308 [SDOI2014]LIS(最小割+退流)

    传送门 设\(f[i]\)为以\(i\)结尾的最长上升子序列.可以考虑建这样一张图,对于所有的\(i<j,f[j]=f[i+1]\)连边\((i,j)\),\(f[i]=1\)的话连边\((S, ...