第12讲 | Java有几种文件拷贝方式?哪一种最高效?

典型回答

Java 有多种比较典型的文件拷贝实现方式,例如
利用 java.io 类库,直接为源文件构建一个 FileInputStream 读取,然后再为目标文件构建一个 FileOutputStream,完成写入工作。

public static void copyFileByStream(File source, File dest) throws
        IOException {
    try (InputStream is = new FileInputStream(source);
         OutputStream os = new FileOutputStream(dest);){
        byte[] buffer = new byte[1024];
        int length;
        while ((length = is.read(buffer)) > 0) {
            os.write(buffer, 0, length);
        }
    }
 }

或者,利用 java.nio 类库提供的 transferTo 或 transferFrom 方法实现。

public static void copyFileByChannel(File source, File dest) throws
        IOException {
    try (FileChannel sourceChannel = new FileInputStream(source)
            .getChannel();
         FileChannel targetChannel = new FileOutputStream(dest).getChannel
                 ();){
        for (long count = sourceChannel.size() ;count>0 ;) {
            long transferred = sourceChannel.transferTo(
                    sourceChannel.position(), count, targetChannel);            sourceChannel.position(sourceChannel.position() + transferred);
            count -= transferred;
        }
    }
 }

当然,Java 标准类库本身已经提供了几种 Files.copy的实现。

对于 Copy 的效率,这个其实与操作系统和配置等情况相关,,总体上来说,NIO transferTo/From 的方式可能更快,因为它更能利用现代操作系统底层机制,避免不必要拷贝和上下文切换。

拷贝实现机制分析

用户态空间(User Space)内核态空间(Kernel Space),这是操作系统层面的基本概念,操作系统内核、硬件驱动等运行在内核态空间,具有相对高的特权;而用户态空间,则是给普通应用和服务使用。

当我们使用输入输出流进行读写时,实际上是进行了多次上下文切换,比如应用读取数据时,先在内核态将数据从磁盘读取到内核缓存,再切换到用户态将数据从内核缓存读取到用户缓存。写入操作也是类似,仅仅是步骤相反。


image.png

这种方式会带来一定的额外开销,可能会降低 IO 效率。

而基于 NIO transferTo 的实现方式,在 Linux 和 Unix 上,则会使用到零拷贝技术,数据传输并不需要用户态参与,省去了上下文切换的开销和不必要的内存拷贝,进而可能提高应用拷贝性能。注意,transferTo 不仅仅是可以用在文件拷贝中,与其类似的,例如读取磁盘文件,然后进行 Socket 发送,同样可以享受这种机制带来的性能和扩展性提高。

image.png

Java IO/NIO 源码结构

Java 标准库也提供了文件拷贝方法(java.nio.file.Files.copy)

private static long copy(InputStream source, OutputStream sink)
      throws IOException

public static Path copy(Path source, Path target, CopyOption... options)
  throws IOException

public static long copy(InputStream in, Path target, CopyOption... options)
  throws IOException

public static long copy(Path source, OutputStream out) 
throws IOException

copy 不仅仅是支持文件之间操作,没有人限定输入输出流一定是针对文件。

public static Path copy(Path source, Path target, CopyOption... options)
    throws IOException
 {
    FileSystemProvider provider = provider(source);
    if (provider(target) == provider) {
        // same provider
        provider.copy(source, target, options);// 这是本文分析的路径
    } else {
        // different providers
        CopyMoveHelper.copyToForeignTarget(source, target, options);
    }
    return target;
}

copy 方法其实不是利用 transferTo,而是本地技术实现的用户态拷贝。

如何提高类似拷贝等 IO 操作的性能,有一些宽泛的原则:

  • 使用缓存等机制,合理减少 IO 次数
  • 使用 transferTo 等机制,减少上下文切换和额外io
  • 尽量减少不必要的转换过程,比如编解码;对象序列化和反序列化

NIO Buffer

Buffer 是 NIO 操作数据的基本工具,Java 为每种原始数据类型都提供了相应的 Buffer 实现(布尔除外)。

image.png

Buffer 有几个基本属性:

  • capcity,它反映这个 Buffer 到底有多大,也就是数组的长度
  • position,要操作的数据起始位置。
  • limit,相当于操作的限额。在读取或者写入时,limit 的意义不一样,
    -mark,记录上一次 postion 的位置,默认是 0,算是一个便利性的考虑,往往不是必须的。

Buffer 的基本操作:

  • 我们创建了一个 ByteBuffer,准备放入数据,capcity 当然就是缓冲区大小,而 position 是 0,limit 默认就是 capcity 的大小。
  • 当我们写入几个字节的数据时,position 就会跟着水涨船高,但是它不可能超过 limit 的大小。
  • 如果我们想把前面写入的数据读出来,需要调用 flip 方法,将 position 设置为 0,limit 设置为以前的 position 那里。
  • 如果还想从头再读一遍,可以调用 rewind,让 limit 不变,position 再次设置为 0。

Direct Buffer 和垃圾收集

Direct Buffer:如果我们看 Buffer 的方法定义,你会发现它定义了 isDirect() 方法,返回当前 Buffer 是否是 Direct 类型。这是因为Java 提供了堆内和堆外(Direct)Buffer,通过allocate 或者 allocateDirect 方法直接创建。

MappedByteBuffer:它将文件按照指定大小直接映为内存区域,当程序访问这个内存区域时将直接操作这块儿文件数据,省去了将数据从内核空间向用户空间传输的损耗。我们可以使用FileChannel.map创建 MappedByteBuffer,它本质上也是种 Direct Buffer。

Java 会尽量对 Direct Buffer 仅做本地 IO 操作,对于很多大数据量的 IO 密集操作,可...可能会带来非常大的性能优势,因为:

  • Direct Buffer 生命周期内内存地址都不会再发生更改,进而内核可以安全地对其进行访问,很多 IO 操作会很高效。
  • 减少了堆内对象存储的可能额外维护工作,所以访问效率可能有所提高。
  • Direct Buffer 创建和销毁过程中,都会比一般的堆内 Buffer 增加部分开销,所以通常都建议用于长期使用,数据较大的场景。

DirectBuffer 不在堆上,所以 Xmx 之类参数,其实并不能影响 Direct Buffer 等堆外成员所使用的内存额度,我们可以这样设置:

-XX:MaxDirectMemorySize=512M

从参数设置和内存问题排查角度来看,这意味着我们在计算 Java 可以使用的内存大小的时候,不能只考虑堆的需要,还有 Direct Buffer 等一系列堆外因素。如果出现内存不足,堆外内存占用也是一种可能性。

大多数垃圾收集过程中,都不会主动收集 Direct Buffer,它的回收是通过Cleaner(一个内部实现)和幻象引用(PhantomReference)机制,对它的销毁往往要拖到 full GC 的时候,所以使用不当很容易导致OOM。

对于 Direct Buffer 的回收的几个建议:

  • 在应用程序中,显式地调用 System.gc() 来强制触发.
  • 在大量使用 Direct Buffer 的部分框架中,框架会自己在程序中调用释放方法,Netty 就是这么做的。
  • 重复使用 Direct Buffer。

极客时间版权所有: https://time.geekbang.org/column/article/8393
极客时间版权所有: https://time.geekbang.org/column/article/8393
rticle/8393
极客时间版权所有: https://time.geekbang.org/column/article/8393
极客时间版权所有: https://time.geekbang.org/column/article/8393
极客时间版权所有: https://time.geekbang.org/column/article/8393
极客时间版权所有: https://time.geekbang.org/column/article/8393...
极客时间版权所有: https://time.geekbang.org/column/article/8393
极客时间版权所有: https://time.geekbang.org/column/article/8393

极客时间版权所有: https://time.geekbang.org/column/article/8393
极客时间版权所有: https://time.geekbang.org/column/article/8393

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,264评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,549评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,389评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,616评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,461评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,351评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,776评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,414评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,722评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,760评论 2 314
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,537评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,381评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,787评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,030评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,304评论 1 252
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,734评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,943评论 2 336

推荐阅读更多精彩内容