前面介绍的各色流式IO在功能方面着实强大,处理文件的时候该具备的操作应有尽有,可流式IO在性能方面不尽如人意,它的设计原理使得实际运行效率偏低,为此从Java4开始增加了NIO技术,通过全新的架构体系带来了可观的性能提升。
NIO是“Non-blocking IO”的缩写,意思是非阻塞的IO,与之相对应,传统的流式IO又被称作BIO(“Blocking IO”的缩写),意即阻塞的IO。所谓阻塞与非阻塞,说起来挺拗口,令人不知所云,这都是设计师脑袋短路惹的祸,发明了这么难懂的词汇,害得初学者一脸懵逼。其实阻塞与非阻塞的区别,犹如私家车与出租车的区别,私家车买回来以后只供车主一家开,没开的时候要么停在小区地库,要么停在公共停车场,其他人是不能随便坐上这部私家车的,如此一来私家车便处于阻塞模式,车门塞住了外人打不开。而出租车整日在街上穿行,有客人招手就停下来载客,开到目的地乘客下车,然后恢复空车状态重新揽客,这样出租车便处于非阻塞模式,车门没塞住乘客打得开。显然阻塞模式存在资源的极大浪费,一个资源分配给某人之后,即使无事可做也只能空在一边闲得发慌;而非阻塞模式充分发挥了物尽其用的原则,一个资源用完之后马上释放,随时允许下一个人接着使用。
非阻塞的NIO机制画了一个高效的大饼,谁知对于文件来说却是画饼充饥,原来非阻塞模式只适用于网络请求交互,而文件处理总是处于阻塞模式,想想看,某个文件被A用户打开之后,B用户还能往该文件写入数据吗?很明显即使A用户打开文件后啥事都不做,B用户也不能写入该文件,缘于文件已经被A用户霸占了。之所以文件没有非阻塞模式,是因为文件仅仅为磁盘上的某个存储片段,它既不智能也不主动,更无法进行任务调度,只能被动的打开和关闭。既然文件处理不支持非阻塞机制,难道NIO技术对文件来说形同虚设?当然事实并非如此,要知道NIO技术不光光包括非阻塞机制,还包括文件通道、虚拟内存等等手段,可谓博大精深、不一而足。
先看文件通道,众所周知,传统的流式IO分为输入流与输出流,输入与输出拥有各自的Stream工具,输入流工具InputStream只能用来读文件,输出流工具OutputStream只能用来写文件,二者井水不犯河水。那如果打开文件之后,想要一会儿读一会儿写,输入流和输出流可得忙坏了,读的时候招呼InputStream来个全套操作,写的时候再招呼OutputStream来个全套操作,实在是劳民伤财。文件通道就不一样,通道中的数据允许双向流动,流进来意味着读操作,流出去意味着写操作,这样文件的读写操作集中在文件通道里进行,大大节省了系统的资源开销。在操作系统层面,通道是一种专职I/O操作的简单处理器,它专门负责输入输出控制,使得CPU从繁琐的I/O处理中解放出来,从而有效地提高整个系统的资源利用率。
文件通道对应的Java类型名叫FileChannel,它的创建方式主要有两种,第一种要通过输入输出流,即调用输入输出流的getChannel方法获取通道对象。比如下面代码根据文件输入流得到了可读的文件通道:

			// 第一种方式:根据文件输入流获得可读的文件通道
FileChannel channel1 = new FileInputStream(mFileName).getChannel();

又如下面代码根据文件输出流得到了可写的文件通道:

			// 第一种方式:根据文件输出流获得可写的文件通道
FileChannel channel2 = new FileOutputStream(mFileName).getChannel();

第二种方式则要通过随机文件工具,仍旧调用随机文件工具的getChannel方法获取通道对象。此时文件通道对象的构建代码示例如下:

			// 第二种方式:根据随机访问文件获得可读的文件通道
FileChannel channel1 = new RandomAccessFile(mFileName, "r").getChannel();
// 第二种方式:根据随机访问文件获得可写的文件通道
FileChannel channel2 = new RandomAccessFile(mFileName,"rw").getChannel();

得到文件通道对象之后,接着便能调用下列方法完成相应的文件处理动作:
isOpen:判断文件通道是否打开。
size:获取文件通道的大小(即文件长度)。
truncate:截断文件大小到指定长度。
read:把文件通道中的数据读到字节缓存。
write:往文件通道写入字节缓存中的数据。
force:强制写入磁盘,相当于缓存输出流的flush方法。
close:关闭文件通道。
从以上方法列表可知,FileChannel相当于集成了FileInputStream和FileOutputStream,用起来更加方便。下面来个利用文件通道写文件的代码例子,一样的简洁明了:

	// 通过文件通道写入文件
private static void writeChannel() {
String str = "春眠不觉晓,处处闻啼鸟。\n夜来风雨声,花落知多少。";
// 根据文件输出流获得可写的文件通道。注意文件通道支持try(...)的自动关闭操作
try (FileChannel channel = new FileOutputStream(mFileName).getChannel()) {
// 生成字符串对应的字节缓存对象
ByteBuffer buffer = ByteBuffer.wrap(str.getBytes());
channel.write(buffer); // 往文件通道写入字节缓存
//channel.force(true); // 强制写入磁盘,相当于输出流的flush方法
} catch (Exception e) {
e.printStackTrace();
}
}

再来利用文件通道读文件的代码例子,具体如下所示:

	// 通过文件通道读取文件
private static void readChannel() {
// 根据文件输入流获得可读的文件通道。注意文件通道支持try(...)的自动关闭操作
try (FileChannel channel = new FileInputStream(mFileName).getChannel()) {
int size = (int) channel.size(); // 获取文件通道的大小(即文件长度)
// 分配指定大小的字节缓存
ByteBuffer buffer = ByteBuffer.allocateDirect(size);
channel.read(buffer); // 把文件通道中的数据读到字节缓存
buffer.flip(); // 把缓冲区从写模式切换到读模式。从缓冲区读取数据之前,必须先调用flip方法
byte[] bytes = new byte[size]; // 创建与文件大小相同长度的字节数组
buffer.get(bytes); // 把字节缓存中的数据取到字节数组
String content = new String(bytes); // 把字节数组转换为字符串
System.out.println("content="+content);
} catch (Exception e) {
e.printStackTrace();
}
}

看来文件通道在读文件过程中也使用了缓存,整体的代码流程同缓存输入流BufferedInputStream类似,只不过与FileChannel搭配的字节缓存ByteBuffer用着不太顺手,别着急,后面的文章将细细道来ByteBuffer的详细用法。

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

Java开发笔记(九十二)文件通道的基本用法的更多相关文章

  1. Java开发笔记(二十四)方法的组成形式

    经过前面的学习,我们发现演示的Java代码越来越复杂,而且每个例子的代码都堆在入口方法main内部,这会导致如下问题:1.一个方法内部堆砌了太多的代码行,看着费神,维护起来也吃力:2.部分代码描述的是 ...

  2. Java开发笔记(二十三)数组工具Arrays

    数组作为一种组合形式的数据类型,必然要求提供一些处理数组的简便办法,包括数组比较.数组复制.数组排序等等.为此Java专门设计了Arrays工具,该工具包含了几个常用方法,方便程序员对数组进行加工操作 ...

  3. Java开发笔记(二十五)方法的输入参数

    前面通过main方法介绍了方法的定义形式,对于方法的输入参数来说,还有几个值得注意的地方,接下来分别对输入参数的几种用法进行阐述.一个方法可以有输入参数,也可以没有输入参数,倘若无需输入参数,则方法定 ...

  4. Java开发笔记(二十六)方法的输出参数

    前面介绍了方法的输入参数,与输入参数相对应的则为输出参数,输出参数也被称作方法的返回值,意思是经过方法的处理最终得到的运算数值.这个返回值可能是整型数,也可能是双精度数,也可能是数组等其它类型,甚至允 ...

  5. Java开发笔记(二十七)数值包装类型

    方法的出现缘起优化代码结构,但它的意义并不局限于此,正因为有了方法定义,编程语言才更像一门能解决实际问题的工具,而不仅仅是只能用于加减乘除的计算器.在数学的发展过程中,为了表示四则运算,人们创造了加减 ...

  6. Java开发笔记(二十八)布尔包装类型

    前面介绍了数值包装类型,因为不管是整数还是小数,它们的运算操作都是类似的,所以只要学会了Integer的用法,其它数值包装类型即可一并掌握.但是对于布尔类型boolean来说,该类型定义的是“true ...

  7. Java开发笔记(二十九)大整数BigInteger

    早期的编程语言为了节约计算机的内存,给数字变量定义了各种存储规格的数值类型,比如字节型byte只占用一个字节大小,短整型short占用两个字节大小,整型int占用四个字节大小,长整型long占用八个字 ...

  8. Java开发笔记(二)Java工程的帝国区划

    上一篇文章介绍了如何运行了第一个Java程序“Hello World”.然而这个开发环境看起来那么陌生,一个个名字符号完全不知道它们是干啥的呀,对于初学者来说,好比天书一般,多看几眼感觉都要走火入魔了 ...

  9. Java开发笔记(二十二)神奇的冒号

    Java中的标点符号主要有两类用途,一类是运算符,包括加号+.减号-.乘号*.除号/.取余号%.等号=.大于号>.小于号<.与号&.或号|.非号!.异或号^等等,另一类则是分隔符, ...

  10. Java开发笔记(二十)一维数组的用法

    之前介绍的各类变量都是单独声明的,倘若要求定义相同类型的一组变量,则需定义许多同类型的变量,显然耗时耗力且不宜维护.为此,编程语言引入了数组的概念,每个数组都由一组相同类型的数据构成,对外有统一的数组 ...

随机推荐

  1. 016 ppp authentication

    Press RETURN to get started!       Router>en Router#config t Enter configuration commands, one pe ...

  2. Redis集群主备模式部署

    网上有非常多用Ruby安装Redis-cluster的文章.可是在实际环境下不想安装Ruby,所以本文主要介绍了用Redis命令部署Redis集群.而且为集群中每个master实例添加一个slave实 ...

  3. hdu1078 FatMouse and Cheese(记忆化搜索)

    题目链接: http://acm.hdu.edu.cn/showproblem.php?pid=1078 题目大意: 题目中的k表示横向或者竖直最多可曾经进的距离,不可以拐弯.老鼠的出发点是(1,1) ...

  4. Objective-C语言的 if ( self = [super init] )

    我们先假设如今自己创建了个类.我们起名叫MyObject,继承于NSObject. 继承知道吧,就是你这个子类(MyObject)假设什么都不写的话,和父类(NSObject)就是一模一样的. OC里 ...

  5. c# WinForm的一些问题

    工作中,用WinForm写了一段程序,刚开始运行正常,后来替换为公司框架的时候,发现原来用Label拼的表格控件,里面的Text无法显示,后来发现,父控件的ForColor为Control导致,子空间 ...

  6. srcset

    <div id="pg-334-2" class="panel-grid panel-has-style"> <div style=" ...

  7. C++ 代码片段(积累)

    1.MFC中点出对话框显示另存为的一个路径和文件: void CPatchDlg::OnBnClickedBtnPath() { //一下为选择另存为一个路径 m_strSavepath = &quo ...

  8. 重新定义数据库历史的时刻——时间序列数据库Schwartz认为InfluxDB最有前途,Elasticsearch也不错

    转自:http://www.infoq.com/cn/news/2017/04/redefine-database-history 提起VividCortex公司的创建者兼CEO Baron Schw ...

  9. [Codeforces 986E] Prince's Problem

    [题目链接] https://codeforces.com/contest/986/problem/E [算法] X到Y的路径积 , 可以转化为X到根的路径积乘Y到根的路径积 , 除以LCA到根的路径 ...

  10. bzoj1407 [Noi2002]Savage——扩展欧几里得

    题目:https://www.lydsy.com/JudgeOnline/problem.php?id=1407 看到一定有解,而且小于10^6,所以可以枚举: 判断一个解是否可行,就两两判断野人 i ...