上一篇简单介绍了NIO,这一篇将介绍FileChannel结合Buffer的用法,主要介绍Buffer

FileChannel的简单使用&Buffer的介绍

一、FileChannel例子

上一篇说到,这个Channel属于文件通道,专门读取文件信息,NIO读取文件内容的简单的例子:


public static void readFile() {
RandomAccessFile file = null;
try {
file = new RandomAccessFile("D:\\rua.txt", "rw");
FileChannel fileChannel = file.getChannel(); //获取文件通道
ByteBuffer buf = ByteBuffer.allocate(2); //分配容积为2字节的一块Buffer,用来读取数据
int bytesRead = fileChannel.read(buf); //从通道里读取数据到Buffer内(最大不超过Buffer容积)
while (bytesRead != -1) { //当读不到任何东西时返回-1
buf.flip(); //切换到Buffer读模式,读模式下可以读取到之前写入Buffer的数据
while (buf.hasRemaining()) { //循环输出Buffer中的数据
System.out.print((char) buf.get());
}
buf.compact(); //或者调用clear,切换回Buffer的写模式
bytesRead = fileChannel.read(buf); //跟上面一样,再次从通道读取数据到Buffer中
}
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (file != null) {
file.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

rua.txt文件内容为:123456789 上述代码运行后输出如下:


123456789

文件正常读取,可以结合上面的注释,来分析下过程,接下来要利用上面的例子介绍Buffer的一些概念。

二、Buffer的概念

2.1:Buffer操作的步骤

第一篇说过,Buffer是一个缓冲区,是一个容器,负责从通道读取数据或者写数据给通道,通过上面的例子,我们可以看到Buffer在读取通道数据时的几个步骤:

step1:分配空间


ByteBuffer.allocate(1024); //除此之外,还可以通过allocateDirector分配空间(具体不了解,先放一边,回头补)

step2:从通道读取数据,写入Buffer


int bytesRead = fileChannel.read(buf);

step3:读取Buffer内的内容,切换回Buffer读模式


buf.flip(); //切换到Buffer读模式,读模式下可以读取到之前写入Buffer的数据

step4:循环执行读写操作,每取完一次Buffer中的值,都切换回写模式,再次从通道读取数据到Buffer


while (bytesRead != -1) { //当读不到任何东西时返回-1
buf.flip(); //切换到Buffer读模式,读模式下可以读取到之前写入Buffer的数据
while (buf.hasRemaining()) { //循环输出Buffer中的数据
System.out.print((char) buf.get());
}
buf.compact(); //或者调用clear,切换回Buffer的写模式
bytesRead = fileChannel.read(buf); //跟上面一样,再次从通道读取数据到Buffer中
}

2.2:Buffer读写流程详解

2.2.1:重要属性的介绍

Buffer具备的几个重要概念:

capacity:缓冲区数组的总长度(容积)

position:下一个需要操作的数据元素的位置

limit:缓冲区不可操作的下一个元素的位置,limit <= capacity

mark:用于记录position的前一个位置或默认是-1

2.2.2:Buffer内部的操作流程

结合上面的概念,除了mark(再往下介绍),其余几个指标的操作变化如下图:

初始化一个容积为10的Buffer,初识位置limit = capacity,position位于第一个位置

图1

当容器内写入了5个数据元素之后,position的位置变到了第6个位置,标记当前写入到哪里了

图2

这时候不再写入数据了,开始切换回Buffer的读模式(flip),发生的变化如下:

图3

发现,原先读模式下的position的位置被limit替换掉了,而position被重置为了第一个位置,这是因为现在读模式下想要读取之前写入的内容,为了保证读取的数据都是可读的(之前写入的),就需要有一个标记,来记录之前写模式下,操作到哪里了,position被重置为第一个位置,也很容易理解,因为切换了读模式,从头开始读取已写入的数据。

通过上面的描述,我们清楚了buffer是如何利用position、limit、capacity来完成读写操作的,下面我们来介绍下具体读写操作时Bufer发生的操作:

Buffer切换读模式的方法有:flip

Buffer切换写模式的方法有:clear、compact

Buffer读数据:get


使用clear切换回写模式的时候,position会被置为0(也就是最初的位置),limit置为capacity(也就是最后的位置),意味着切换写模式之前未读的数据,将会被新一轮的写入覆盖,就再也找不回来了,所以除了clear这个操作,Buffer还提供了compact方法来切换读模式,这个方法会把所有未读的数据拷贝到Buffer的起始位置,然后position指向最后一个未读数据的后一位,这样,下次开启读模式的时候,position操作同上,置为0,因此之前未读完被落下的数据也就在这时候被读到了。

下面我们来还原下这个转换过程:

图4

走到上面的步骤后,我们切换成写模式,下面这个图分别表示了clearcompact两个方法下的两种操作:

图5

根据图4和图5,结合上面的话,更容易理解clear和compact两种方式切换写模式所做的内部操作,以及为什么clear会丢数据,而compact不会。

下面通过一开始的例子,把中间读取数据的地方稍微做下修改:


ByteBuffer buf = ByteBuffer.allocate(2);
int bytesRead = readChannel.read(buf);
while (bytesRead != -1) {
buf.flip();
char result = (char) buf.get(); //虽然读进来了2个字节,但这里只取一个
System.out.print(result);
buf.clear(); // 使用clear切换回Buffer的写模式
bytesRead = readChannel.read(buf);
}
System.out.println("-----------程序结束");

输出结果:


13579-----------程序结束

发现丢了一些数据,现在把clear改成compact,运行结果为:


12345678-----------程序结束

发现,除了9,都输出来了(至于9为啥没输出,因为每次只取了一个字节的数据呀~)

那么,如果切换到读模式,但是不读,然后切换回写模式继续写,会发生什么?

改造上述代码如下:


ByteBuffer buf = ByteBuffer.allocate(2);
int bytesRead = readChannel.read(buf);
while (bytesRead != -1) {
buf.flip();
buf.clear(); // 不读,立刻切回写模式
bytesRead = readChannel.read(buf);
}
System.out.println("-----------程序结束");

调用clear的情况下输出:


-----------程序结束

而调用compact方法,却发生阻塞(死循环)了,结合之前的图,我们可以知道,如果不读,意味着在compact下会把未读的数据copy到Buffer里,如果一点都不读,那么意味着被copy的这批数据会占满整个Buffer,以至于position没有下一个位置可用,就会发生文件里的数据没办法被安排进缓冲区(意味着文件读不完),bytesRead一直不等于-1,发生死循环。

通过上面的图和例子,基本上可以理清楚读模式、写模式(包含不同的切换方式)下的Buffer内部处理方式。

2.3:Buffer的其他操作

除了上面几种常规用法,Buffer还提供了其他的几个操作方法:

2.3.1:rewind

这个方法可以在读模式下,重置position的位置,也就是说在get执行后。position发生了位移,这个方法可以重置position的位置为初始位置,看例子:


ByteBuffer buf = ByteBuffer.allocate(2); //每次可读入两个字节
int bytesRead = readChannel.read(buf);
while (bytesRead != -1) {
buf.flip();
System.out.print((char) buf.get());
buf.rewind(); //重置position
System.out.print((char) buf.get());//这时候读到的数据跟上面是同一个
buf.clear();
bytesRead = readChannel.read(buf);
}

运行结果:


1133557799

可以看到,每次循环拿到两个字节,但两次获取的数据都是同一个,因为rewind把读取游标重置成初始位置了(也即是位置0)

⭐️这里说下,如果把上面的第二个get方法去掉,然后把clear模式改成compact模式同样也会发生死循环,因为rewind重置了游标,重置后又没有get方法再次读取,导致把本次的两个字节又复制进了Buffer,跟之前说的不读一样,会导致Buffer没有多余的空间放文件里的数据,导致一直读不完,发生死循环。

2.3.2:mark & reset

这两个方法放到一起说,因为mark跟reset不放在一起使用,没有任何意义。

mark:用于标记当前position的位置

reset:用于恢复被mark标记的position的位置

例子:


ByteBuffer buf = ByteBuffer.allocate(2);
int bytesRead = readChannel.read(buf);
while (bytesRead != -1) {
buf.flip();
buf.mark(); //标记当前位置,这里也就是初始位置
System.out.print((char) buf.get()); //读取到了初识位置数据
buf.reset(); //重置position到mark标记时那个值
System.out.print((char) buf.get()); //这里由于被reset了,因此输出的还是初识位置的数据
buf.clear();
bytesRead = readChannel.read(buf);
}
输出结果:

1133557799

会发现,上下两个打印都是是一样的数据。嘛,还是跟上面一样,再回顾一下,这里如果用compact会怎么操作?如果使用compact会打印如下语句:


1122334455667788

这个现在也很好理解了,因为重置了位置所以每个数据被打印了两次,由于mark的原因,每次实际上相当于只读了一个数据,所以剩下的一个数据被顺延到下次循环里打印,以此类推。

2.3.3:equals & compareTo

equals:用来比较两个Buffer是否相等,判等条件为

①类型相同(byte、char等)

②剩余元素个数相等

③剩余元素相同

compareTo:比较两个Buffer中剩余元素的大小,如果满足如下条件,则认为buffer1小于buffer2:

①buffer1中第一个与buffer2不相等的元素小于buffer2的那个元素

②所有元素相同,但是buffer1先比buffer2耗尽

三、实例:边读边写

例子:将rua.txt里的内容在读的同时写入文件haha.txt里


readFile = new RandomAccessFile("D:\\rua.txt", "r");
writeFile = new RandomAccessFile("D:\\haha.txt", "rw");
FileChannel readChannel = readFile.getChannel(); //获取只读文件通道
FileChannel writeChannel = writeFile.getChannel(); //获取写文件通道 ByteBuffer readBuf = ByteBuffer.allocate(2); //分配容积为2字节的一块Buffer,用来读取数据
int bytesRead = readChannel.read(readBuf); //从通道里读取数据到Buffer内(最大不超过Buffer容积)
while (bytesRead != -1) { //当读不到任何东西时返回-1
readBuf.flip(); //切换到Buffer读模式,读模式下可以读取到之前写入Buffer的数据
writeChannel.write(readBuf); //将现在的buffer里的数据写到文件haha.txt里
readBuf.compact(); // 切换回Buffer的写模式
bytesRead = readChannel.read(readBuf); //跟上面一样,再次从通道读取数据到Buffer中
}

结果haha.txt里的内容为:123456789

四、Buffer的分类

ByteBuffer 支持存放字节类型数据,抽象类,有DirectByteBuffer、HeapByteBuffer、MappedByteBuffer两个子类,下面进行说明
CharBuffer 支持存放char类型数据
DoubleBuffer 支持存放double类型数据
FloatBuffer 支持存放float类型数据
IntBuffer 支持存放int类型数据
LongBuffer 支持存放long类型数据
ShortBuffer 支持存放short类型数据

表1

通过上表可以看到,Buffer有很多实现,其中大部分都对应一种基本类型,ByteBuffer比较特殊,下面介绍下它的两个子类。

ByteBuffer---->HeapByteBuffer

直接通过byte数组实现的在java堆上的缓冲区。

ByteBuffer---->DirectByteBuffer

直接在java堆外申请的一块内存,将文件映射到该内存空间,在大文件读写方面的效率非常高。

ByteBuffer----->MappedByteBuffer

同样写效率非常高。

关于这几个Buffer后续会专门整理一篇文章来写。

Java NIO学习与记录(二):FileChannel与Buffer用法与说明的更多相关文章

  1. Java NIO学习与记录(八): Reactor两种多线程模型的实现

    Reactor两种多线程模型的实现 注:本篇文章例子基于上一篇进行:Java NIO学习与记录(七): Reactor单线程模型的实现 紧接着上篇Reactor单线程模型的例子来,假设Handler的 ...

  2. Java NIO 学习笔记(二)----聚集和分散,通道到通道

    目录: Java NIO 学习笔记(一)----概述,Channel/Buffer Java NIO 学习笔记(二)----聚集和分散,通道到通道 Java NIO 学习笔记(三)----Select ...

  3. Java NIO学习与记录(七): Reactor单线程模型的实现

    Reactor单线程模型的实现 一.Selector&Channel 写这个模型需要提前了解Selector以及Channel,之前记录过FileChannel,除此之外还有以下几种Chann ...

  4. Java NIO学习与记录(五): 操作系统的I/O模型

    操作系统的I/O模型 在开始介绍NIO Reactor模式之前,先来介绍下操作系统的五种I/O模型,了解了这些模型,对理解java nio会有不小的帮助. 先来看下一个服务端处理一次网络请求的流程图: ...

  5. Java NIO学习与记录(三): Scatter&Gather介绍及使用

     Scatter&Gather介绍及使用 上一篇知道了Buffer的工作机制,以及FileChannel的简单用法,这一篇介绍下 Scatter&Gather 1.Scatter(分散 ...

  6. Java NIO学习与记录(一):初识NIO

    初识 工作中有些地方用到了netty,netty是一个NIO框架,对于NIO却不是那么熟悉,这个系列的文章是我在学习NIO时的一个记录,也期待自己可以更好的掌握NIO. 一.NIO是什么? 非阻塞式I ...

  7. Java NIO学习与记录(六): NIO线程模型

    NIO线程模型 上一篇说的是基于操作系统的IO处理模型,那么这一篇来介绍下服务器端基于IO模型和自身线程的处理方式. 一.传统阻塞IO模型下的线程处理模式 这种处理模型是基于阻塞IO进行的,上一篇讲过 ...

  8. Java NIO学习与记录(四): SocketChannel与BIO服务器

    SocketChannel与BIO服务器 SocketChannel可以创建连接TCP服务的客户端,用于为服务发送数据,SocketChannel的写操作和连接操作在非阻塞模式下不会发生阻塞,这篇文章 ...

  9. Java NIO 学习笔记(六)----异步文件通道 AsynchronousFileChannel

    目录: Java NIO 学习笔记(一)----概述,Channel/Buffer Java NIO 学习笔记(二)----聚集和分散,通道到通道 Java NIO 学习笔记(三)----Select ...

随机推荐

  1. 光源 材质 简析——基于《real time rendering》第三版 第五章

    对于真是世界的渲染,有三个重要的组成部分,光源,材质,以及摄像机.下面,我们一个一个来简单介绍一下. 光源:方向光,点光源,聚光灯.但是,在和物体表面交互的时候,光源对物体表面的影响是依赖光的辐照度( ...

  2. python参数Sample Code

    import time import datetime import getopt import sys try: opts, args = getopt.getopt(sys.argv[1:], & ...

  3. kaggle-泰坦尼克号Titanic-3

    根据以上两篇的分析,下面我们还要对数据进行处理,观察Age和Fare两个属性,乘客的数值变化幅度较大!根据逻辑回归和梯度下降的了解,如果属性值之间scale差距较大,将对收敛速度造成较大影响,甚至不收 ...

  4. iOS6后的内存警告处理

    [iOS6后的内存警告处理] The memory used by a view to draw itself onscreen is potentially quite large. However ...

  5. VS2017设置默认管理员权限启动

    找到vs安装目录下的:C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\devenv.exe 右键- ...

  6. HDU1233 还是畅通工程 2017-04-12 19:49 64人阅读 评论(0) 收藏

    还是畅通工程 Time Limit : 4000/2000ms (Java/Other)   Memory Limit : 65536/32768K (Java/Other) Total Submis ...

  7. c# 调用微信小程序

    //微信也不给个c#调用的例子 只好自己造咯:ps:大佬勿喷 1 public string GetWx(string code, string iv, string encryptedData) { ...

  8. Android学习之Adapter(数据适配器)

    1.定义     数据适配器是AdapterView视图(如ListView - 列表视图控件.Gallery - 缩略图浏览器控件.GridView - 网格控件.Spinner - 下拉列表控件. ...

  9. Android 屏幕适应

    基础知识: 屏幕密度: Density-independent pixel (dp):密度无关像素单位(一个相对的值).1dp 的大小相当于一个 160 dpi 屏幕上一个像素的大小. 计算方法:px ...

  10. 开发一个小的php扩展

    今天试了一下在php添加扩展,看了挺多资料,细节上不一致,其他大体是差不多的. 我们来开发一个叫ccvita_string的函数,他的主要作用是返回一段字符,对应的php代码可能如此: functio ...