前面介绍了字节缓存的一堆概念,可能有的朋友还来不及消化,虽然文件通道的用法比起传统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开发笔记(九十四)文件通道的性能优势的更多相关文章

  1. Java开发笔记(四十二)日历工具的常见应用

    前面介绍了日历工具Calendar的基本用法,乍看起来Calendar与Date两个半斤八两,似乎没有多大区别,那又何苦庸人自扰鼓捣一个新玩意呢?显然这样小瞧了Calendar,其实它的作用大着呢,接 ...

  2. Java开发笔记(四十五)成员属性与成员方法

    前面介绍了许多数据类型,除了基本类型如整型int.双精度型double.布尔型boolean之外,还有高级一些的如包装整型Integer.字符串类型String.本地日期类型LocalDate等等,那 ...

  3. Java开发笔记(四十)日期与字符串的互相转换

    前面介绍了如何通过Date工具获取各个时间数值,但是用户更喜欢形如“2018-11-24 23:04:18”这种结构清晰.简洁明了的字符串,而非啰里八唆依次汇报每个时间单位及其数值的描述.既然日期时间 ...

  4. Java开发笔记(四十一)日历工具Calendar

    前面的文章提到,Date是Java最早的日期工具,估计当时的设计师是个技术宅男,未经过充分调研就拍脑袋写下了Date的源码,造成该工具存在先天不足,比如getYear方法返回的不是纯正的公元纪年.ge ...

  5. Java开发笔记(四十三)更好用的本地日期时间

    话说Java一连设计了两套时间工具,分别是日期类型Date,以及日历类型Calendar,按理说用在编码开发中绰绰有余了.然而随着Java的日益广泛使用,人们还是发现了它们的种种弊端.且不说先天不良的 ...

  6. Java开发笔记(四十四)本地日期时间与字符串的互相转换

    之前介绍Calendar的时候,提到日历实例无法直接输出格式化后的时间字符串,必须先把Calendar类型转换成Date类型,再通过格式化工具SimpleDateFormat获得字符串.而日期时间的格 ...

  7. Java开发笔记(四十六)类的构造方法

    前面介绍了如何定义一个简单的类,以及它的成员属性和成员方法,从示例代码可以看到,不管是OrangeSimple还是OrangeMember,都要先利用关键字new创建一个实例,然后才能通过实例名称访问 ...

  8. Java开发笔记(四十七)关键字this的用法

    前面介绍了类的基本定义,包括成员属性.成员方法.构造方法几个组成要素,可谓是具备了类的完整封装形态.不过在进行下一阶段的学习之前,有必要梳理一下前述的类定义代码,看看是否存在哪些需要优化的地方.首先观 ...

  9. Java开发笔记(四十八)类的简单继承

    前面介绍了类的基本用法,主要是如何封装一个类的各项要素,包括成员属性.成员方法.构造方法等,想必大家对类的简单运用早已驾轻就熟.所谓“物以类聚,人以群分”,之所以某些事物会聚在一起,乃是因为它们拥有类 ...

随机推荐

  1. mysql 发生系统错误1067

    一般是由配置文件错误语法不正确引起的,如my.ini本人在mysql mysql-5.6.29-winx64 配置过程中遇到“发生系统错误1067”主要由于下面两个目录写的格式不正确引起的正确写法如下 ...

  2. 以太坊EVM1.0缺陷

    256位的虚拟机 目前主流的CPU是32位或64位,在这些机器上进行256位运算需要将256位分段成多个64位指令执行,执行效率比32/64位低,在存储上方面,保存一个数需要256位的存储空间,绝大多 ...

  3. codeforces 454B. Little Pony and Sort by Shift 解题报告

    题目链接:http://codeforces.com/problemset/problem/454/B 题目意思:给出一个序列你 a1, a2, ..., an. 问每次操作只能通过将最后一个数拿出来 ...

  4. hdu 1286 找新朋友(欧拉函数)

    题意:欧拉函数 思路:欧拉函数 模板,代码略.

  5. 怎么往mac中finder个人收藏里添加文件夹

    1.打开Finder,点击左上角finder偏好设置 2.选择边栏 3.如果侧栏中没有的文件夹,直接长按文件夹直接拖入.

  6. SpringBoot启动的时候不去校验数据库连接配置是否正确

    spring boot在启动的时候只会检查你是否配置了数据库连接, 而不会检测配置的是否正确 这样会出现的问题是: 只有在你使用数据库的时候才知道配置出错, 我们希望是在程序启动的时候就进行检查, 如 ...

  7. centos7用lvm扩展xfs文件系统的根分区

    centos7中默认使用的是xfs文件系统,此文件系统的特点,可以另外查找资料,这里说一下对文件系统的扩容: 1.先看一下没扩容之前的分区大小 2.添加一块新磁盘,并进行分区.格式化(格式化的时候用m ...

  8. 斯坦福CS231n—深度学习与计算机视觉----学习笔记 课时11

    课时11 神经网络训练细节part1(下) 2010年,Glorot等人写的论文,我们称之为Xavier初始化,他们关注了神经元的方差表达式.他们推荐一种初始化方式,那就是对每个神经元的输入进行开根号 ...

  9. ORACLE PL/SQL 实例精解之第六章 迭代控制之一

    6.1 简单循环 简单循环,就想其名称一张,是一种最基本循环.简单循环具有如下结构 LOOP STATEMENT 1; STATEMENT 2; ... STATEMENT N; END LOOP; ...

  10. 深度解密Go语言之 map

    目录 什么是 map 为什么要用 map map 的底层如何实现 map 内存模型 创建 map 哈希函数 key 定位过程 map 的两种 get 操作 如何进行扩容 map 的遍历 map 的赋值 ...