Java开发笔记(九十四)文件通道的性能优势
前面介绍了字节缓存的一堆概念,可能有的朋友还来不及消化,虽然文件通道的用法比起传统I/O有所简化,可是平白多了个操控繁琐的字节缓存,分明比较传统I/O更加复杂了。尽管字节缓存享有缓存方面的性能优势,但传统I/O也有缓存输入输出流呀,大家都有缓存机制,凭什么说NIO的文件处理更高效?之所以目前还看不出文件通道的性能优势,是因为前面介绍的仅限于它的基本用法,尚未涉及到高级特性,接下来阐述文件通道的真正杀手锏:使用通道复制文件。
复制文件的常规做法很简单,从源文件中读出数据,再将数据写进目标文件。采取文件通道和字节缓存的话,按照传统思路实现的文件复制代码示例如下:
// 使用文件通道和字节缓存复制文件
private static void copyChannelBuffer() {
// 分别创建源文件的文件通道,以及目标文件的文件通道
try (FileChannel src = new FileInputStream(mSrcName).getChannel();
FileChannel dest = new FileOutputStream(mDestName).getChannel()) {
int size = (int) src.size(); // 获取源文件的大小
// 分配指定大小的字节缓存
ByteBuffer buffer = ByteBuffer.allocateDirect(size);
src.read(buffer); // 把源文件中的数据读到字节缓存
buffer.flip(); // 从缓冲区读取数据之前,必须先调用flip方法
dest.write(buffer); // 把字节缓存中的数据写入目标文件
} catch (Exception e) {
e.printStackTrace();
}
}
上述代码与缓存输入输出流的实现代码看起来半斤八两,似乎程序运行效率也差不了多少,然而事实上的性能差距可大了。虽然应用程序的代码像是能够直接读写文件,但是应用程序依附于操作系统,它发出的文件读写指令需要经由操作系统来完成。也就是说,应用程序从磁盘文件读取数据的流程实际上是这样的:磁盘文件→操作系统→应用数据;应用程序把内存数据写入磁盘文件的流程则是这样的:应用数据→操作系统→磁盘文件。注意操作系统和应用程序分配到的存储空间是不一样的,设备的内存在运行时被划分为系统内存与用户内存两大块,其中系统内存装载了系统程序及其使用的内存空间,剩下的用户内存才能依次分给每个应用作为应用程序自身的内存空间。譬如电脑开机之后,刚进入桌面尚未打开任何一个应用程序,电脑内存就已经被消耗了相当一大块,正是操作系统自行占据系统内存的缘故。于是操作系统收到读文件指令之后,先把磁盘文件的数据读到系统内存当中,然后才由应用程序把系统内存中的数据读到应用内存;写文件操作同理,应用程序先把内存数据写到系统内存,再由操作系统把系统内存中的数据写入磁盘文件。因此,传统IO复制文件的完整数据流程正如下图所示:

由图示可见,传统IO在复制文件的过程中一共花费了四个步骤,分别是:步骤①(磁盘文件→系统内存)、步骤②(系统内存→应用内存)、步骤③(应用内存→系统内存)、步骤③(系统内存→磁盘文件),这四个步骤跑下来,难怪传统IO的处理效率高不到哪里去。
使用文件通道就不一样了,通道本身是专门负责I/O操作的处理机,字节缓存又是通道内部的存储空间,故而利用通道复制文件的话,既无需动用操作系统的系统内存,也无需动用应用程序的应用内存。那么使用文件通道完成文件复制功能仅仅需要两个步骤,即先将磁盘上的原文件内容读到通道中的字节缓存,再将字节缓存中的数据写入磁盘上的新文件,更直观的数据流转过程如下图所示:

由上图可见,采用通道复制文件才花了有两个步骤:步骤①(磁盘文件→字节缓存)、步骤②(字节缓存→磁盘文件),显然通道复制的性能要优于传统IO了。
针对文件复制功能,由于已经明确要把源文件的全部内容完成写入新文件,因此不必显式通过字节缓存完成数据的读取与写入动作,可以直接调用通道对象的transferTo方法或者transferFrom方法完成文件复制。其中transferTo方法操作的是源文件通道,它把数据传给目标文件通道;transferFrom方法操作的是目标文件通道,它从源文件通道传入数据。详细的调用代码例子如下所示:
// 使用文件通道直接复制文件
private static void copyChannelDirect() {
// 分别创建源文件的文件通道,以及目标文件的文件通道
try (FileChannel src = new FileInputStream(mSrcName).getChannel();
FileChannel dest = new FileOutputStream(mDestName).getChannel();) {
// 下面的transferTo和transferFrom都可以完成文件复制功能,选择其中一个即可
src.transferTo(0, src.size(), dest); // 操作源文件通道,把数据传给目标文件通道
//dest.transferFrom(src, 0, src.size()); // 操作目标文件通道,从源文件通道传入数据
} catch (Exception e) {
e.printStackTrace();
}
}
更多Java技术文章参见《Java开发笔记(序)章节目录》
Java开发笔记(九十四)文件通道的性能优势的更多相关文章
- Java开发笔记(四十二)日历工具的常见应用
前面介绍了日历工具Calendar的基本用法,乍看起来Calendar与Date两个半斤八两,似乎没有多大区别,那又何苦庸人自扰鼓捣一个新玩意呢?显然这样小瞧了Calendar,其实它的作用大着呢,接 ...
- Java开发笔记(四十五)成员属性与成员方法
前面介绍了许多数据类型,除了基本类型如整型int.双精度型double.布尔型boolean之外,还有高级一些的如包装整型Integer.字符串类型String.本地日期类型LocalDate等等,那 ...
- Java开发笔记(四十)日期与字符串的互相转换
前面介绍了如何通过Date工具获取各个时间数值,但是用户更喜欢形如“2018-11-24 23:04:18”这种结构清晰.简洁明了的字符串,而非啰里八唆依次汇报每个时间单位及其数值的描述.既然日期时间 ...
- Java开发笔记(四十一)日历工具Calendar
前面的文章提到,Date是Java最早的日期工具,估计当时的设计师是个技术宅男,未经过充分调研就拍脑袋写下了Date的源码,造成该工具存在先天不足,比如getYear方法返回的不是纯正的公元纪年.ge ...
- Java开发笔记(四十三)更好用的本地日期时间
话说Java一连设计了两套时间工具,分别是日期类型Date,以及日历类型Calendar,按理说用在编码开发中绰绰有余了.然而随着Java的日益广泛使用,人们还是发现了它们的种种弊端.且不说先天不良的 ...
- Java开发笔记(四十四)本地日期时间与字符串的互相转换
之前介绍Calendar的时候,提到日历实例无法直接输出格式化后的时间字符串,必须先把Calendar类型转换成Date类型,再通过格式化工具SimpleDateFormat获得字符串.而日期时间的格 ...
- Java开发笔记(四十六)类的构造方法
前面介绍了如何定义一个简单的类,以及它的成员属性和成员方法,从示例代码可以看到,不管是OrangeSimple还是OrangeMember,都要先利用关键字new创建一个实例,然后才能通过实例名称访问 ...
- Java开发笔记(四十七)关键字this的用法
前面介绍了类的基本定义,包括成员属性.成员方法.构造方法几个组成要素,可谓是具备了类的完整封装形态.不过在进行下一阶段的学习之前,有必要梳理一下前述的类定义代码,看看是否存在哪些需要优化的地方.首先观 ...
- Java开发笔记(四十八)类的简单继承
前面介绍了类的基本用法,主要是如何封装一个类的各项要素,包括成员属性.成员方法.构造方法等,想必大家对类的简单运用早已驾轻就熟.所谓“物以类聚,人以群分”,之所以某些事物会聚在一起,乃是因为它们拥有类 ...
随机推荐
- java 监听文件或者文件夹变化的几种方式
1.log4j的实现的文件内容变化监听 package com.jp.filemonitor; import org.apache.log4j.helpers.FileWatchdog; public ...
- 关于div li 等标签之间自带间距
可以用float来清除标签之间的间距. ps :ul使用font-size:0 唯一的缺点就是要再次设置LI的font-size
- codeforces A. Kitahara Haruki's Gift 解题报告
题目链接:http://codeforces.com/problemset/problem/433/A 题目意思:给定 n 个只由100和200组成的数,问能不能分成均等的两份. 题目其实不难,要考虑 ...
- relative和absolute
relative 相对定位 1. 幻影瞬移 absolute属性也有瞬移技能,不同之处在于:absolute属性以天空或其他外界限制计算瞬移位置:而relative属性由于是凡人肉体,偏移能力有限,只 ...
- 【旧文章搬运】ZwQuerySystemInformation枚举进线程信息
原文发表于百度空间,2008-10-15========================================================================== 很古老的东 ...
- k8s-部署dashboard1.10.1-十七
一.获取镜像和填坑 我的k8s是1.13.1,这里dashboard用的1.10.1: 由于国内不能访问Google,而且大部分人可能也没有其他途径访问:只能在阿里云或者其他镜像网站上获取了: 镜像获 ...
- 021--python装饰器
一.装饰器含义 装饰器本质就是函数,为其它函数添加附加功能 二.装饰器原则 1.不修改被修饰函数的代码 2.不修改被修饰函数的调用方式 三.装饰器知识 装饰器 = 高阶函数 + 函数嵌套 + 闭包 四 ...
- winform 屏蔽 空格键
private void call_KeyPress(object sender, KeyPressEventArgs e) { if (e.KeyChar == (char)Keys.Space) ...
- Ribbon整合Eureka,出现 No instances available for XXX 异常
请观察这里的片段有没有问题? @Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } ...
- E20180331-hm
corresponding adj. 相当的,对应的; 通信的; 符合的,符合; 一致的; implicitly adv. 含蓄地; 暗示地; 无疑问地; 无保留地; causal adj. 具有因 ...