JAVA 拷贝文件的几种方式小结

 更新时间:2024年03月26日 09:37:26   作者:码头的薯条  
本文主要介绍了JAVA拷贝文件的几种方式,包含普通拷贝,mmap内存映射的方式拷贝,零拷贝sendFile方式实现和多线程的方式实现拷贝,具有一定的参考价值,感兴趣的可以了解一下

1. 前言

闲话少叙,今天主要讲讲 JAVA 四种拷贝文件的方式,分析一下他们对内存使用的方式和各自应用的场景,其实也是对之前学过的知识做一个回顾吧,毕竟太久不回顾的话,记忆就像拼图,随着时间流逝就只剩下散落一地的碎片了。

2. 普通拷贝

protected void copyFile(File source, File target) {
    try (FileInputStream is = new FileInputStream(source);
         FileOutputStream os = new FileOutputStream(target);) {
         // 分配内存空间
        byte[] buffer = new byte[4096];
        while (is.read(buffer) != -1) {
            os.write(buffer);
        }
    } catch (Exception e) {
        logger.error(e);
    }
}

第一种是最简单的,就是初始化一个输入输出流,然后在 JAVA 内部分配一块 4096 字节的内存空间,然后不断将文件写入这个内存空间中,并输出到指定文件。

但是需要注意的是,这样的方式虽然简单,但是它的数据流实际上是经过了 4 层传输的

image.png

也就是我们的文件需要经过内核到我们 JAVA 虚拟机内部的内存 再到 内核的 socket 缓冲区,再到文件。

3. mmap 内存映射的方式拷贝

protected void copyFile(File source, File target) {
    try (FileInputStream is = new FileInputStream(source);
         FileOutputStream os = new FileOutputStream(target);
         FileChannel ic = is.getChannel();
         FileChannel oc = os.getChannel();) {
        // 这里开辟的内存直接映射在内核中
        ByteBuffer buffer = ByteBuffer.allocateDirect(4096);
        while (ic.read(buffer) != -1) {
            buffer.flip();
            oc.write(buffer);
            buffer.clear();
        }
    } catch (Exception e) {
        logger.error(e);
    }
}

第二种由于直接将内存映射在了堆外,也就可以节省普通拷贝中第二步的过程,即不在需要将内核缓冲区中的内容再读到给 java 虚拟机分配的内存中了,比较适合需要 JAVA程序进行文件处理,或者一些小文件的传输

image.png

或者也可以通过封装好的

new RandomAccessFile(file, "r").getChannel().map(FileChannel.MapMode.READ_ONLY, 0, 1024);

来实现,底层是通过 directByteBufferConstructor.newInstance 分配堆外内存来实现的。

4. 零拷贝 sendFile 方式实现

protected void copyFile(File source, File target) {
    try {
        if (!target.exists()) {
            target.createNewFile();
        }
    } catch (Exception e) {
        logger.error(e);
    }
    try (FileChannel is = new RandomAccessFile(source, "r").getChannel();
        FileChannel os = new RandomAccessFile(target, "rw").getChannel()) {
        is.transferTo(0, source.length(), os);
    } catch (Exception e) {
        logger.error(e);
    }
}

第三种其实也就是我们俗称的零拷贝的方式,在 Linux 2.1 版本中,引入了 sendFile 方法,也就是可以跳过用户空间直接实现传输,java 程序中通过 新io 中 file 的 transformTo 方法,底层调用 liunx 内核级的 sendFile 方法,将内核数据直接拷贝到了 socket 缓冲区,从而节省了拷贝次数和消耗。

image.png

既然第三种方式相对于第一种和第二种来说,可以完全不经过 java 应用程序,为什么不都直接都用第三种就好了呢?

正是因为它完全不经过 java 程序,也就是说我们无法对文件内容进行二次修改了,第三种方式比较适用于我们将无需经过程序处理的大文件。

需要注意的是,之所以会有程序的内存空间和内核的内存空间的区别,其实主要就是为了隔离,防止恶意程序可以直接访问内核的内存空间

5. 多线程的方式实现拷贝

// 定义一个线程的数量
private Integer threadCount = 5;

protected void copyFile(File source, File target) {
    long workLoad = source.length() / threadCount;
    for (Integer i = 0; i < threadCount; i++) {
        ThreadFileRunnable threadFileRunnable = new ThreadFileRunnable(source, target, i * workLoad, workLoad);
        new Thread(threadFileRunnable, "copy-thread" + i).start();
    }
}

private class ThreadFileRunnable implements Runnable {
    private File source;
    private File target;
    // 定义每个线程开始复制时跳过的字节长度和工作负载大小
    private long skipLen;
    private long workLoad;
    // 定义IO操作的单位大小,这里设置为1024字节
    private final int IO_UNIT = 1024;

    public ThreadFileRunnable (File source, File target, long skipLen, long workLoad) {
        this.source = source;
        this.target = target;
        this.skipLen = skipLen;
        this.workLoad = workLoad;
    }

    @Override
    public void run() {
        try {
            try (FileInputStream is = new FileInputStream(this.source);
                 BufferedInputStream bis = new BufferedInputStream(is);
                 // 创建目标文件的RandomAccessFile,以读写模式打开
                 RandomAccessFile rof = new RandomAccessFile(this.target, "rw");) {
                // 跳过指定偏移量
                bis.skip(this.skipLen);
                // 将读写指针移动到指定偏移量
                rof.seek(this.skipLen);
                byte[] bytes = new byte[IO_UNIT];
                // 计算需要进行的IO操作次数
                long io_num = this.workLoad / IO_UNIT + 1;
                // 如果工作负载大小能被IO_UNIT整除,则IO操作次数减1
                if (this.workLoad % IO_UNIT == 0) {
                    io_num--;
                }
                int count = bis.read(bytes);
                while (io_num != 0) {
                    rof.write(bytes,0,count);
                    count = bis.read(bytes,0,count);
                    io_num--;
                }
            }
        } catch (Exception e) {
            // 捕获并打印异常信息
            e.printStackTrace();
        }
    }
}

第四种如果文件特别大的时候,我们还可以通过多线程的方式来进行文件的读写,可以充分利用 CPU 多核效率来进一步提升文件的处理效率。

5. 总结

对于 JAVA 文件拷贝来说,本文只是展示和介绍了冰山一角,实际上对于读写流操作,操作系统的实现经过了长时间的演化,从 CPU 中断pagecache,从 sendFileDMA,以及网络传输过程中的 bio nio pollepoll,操作系统经过很多年的演化其中文件和网络的传输处理的复杂程度可想而知。

我想我们可以通过一些小的点管中窥豹,了解一些基础的知识,不用太深入,也能对日常的开发工作和面试有一定帮助。

参考链接 Linux 中的零拷贝

到此这篇关于JAVA 拷贝文件的几种方式的文章就介绍到这了,更多相关JAVA 拷贝文件的几种方式内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • java实现简单的爬虫之今日头条

    java实现简单的爬虫之今日头条

    最近在学习搜索方面的东西,需要了解网络爬虫方面的知识,虽然有很多开源的强大的爬虫,但本着学习的态度,想到之前在做资讯站的时候需要用到爬虫来获取一些文章,今天刚好有空就研究了一下.在网上看到了一个demo,使用的是Jsoup,我拿过来修改了一下,有需要的朋友可以参考
    2016-11-11
  • java中javamail发送带附件的邮件实现方法

    java中javamail发送带附件的邮件实现方法

    这篇文章主要介绍了java中javamail发送带附件的邮件实现方法,较为详细的分析了JavaMail发送邮件的用法,是非常实用的技巧,需要的朋友可以参考下
    2015-01-01
  • Java中接收键盘输入的三种方法

    Java中接收键盘输入的三种方法

    这篇文章主要介绍了Java中接收键盘输入的三种方法,本文给出3个方法实现通过读取控制台的输入与用户实现交互,需要的朋友可以参考下
    2015-06-06
  • IDEA项目使用SpringBoot+MyBatis-Plus的方法

    IDEA项目使用SpringBoot+MyBatis-Plus的方法

    这篇文章主要介绍了IDEA项目使用SpringBoot+MyBatis-Plus的方法,本文分步骤通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-10-10
  • SpringCloud如何利用Feign访问外部http请求

    SpringCloud如何利用Feign访问外部http请求

    这篇文章主要介绍了SpringCloud如何利用Feign访问外部http请求,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-03-03
  • SpringBoot集成FTP与SFTP连接池流程

    SpringBoot集成FTP与SFTP连接池流程

    在项目开发中,一般文件存储很少再使用SFTP服务,但是也不排除合作伙伴使用SFTP来存储项目中的文件或者通过SFTP来实现文件数据的交互,这篇文章主要介绍了SpringBoot集成FTP与SFTP连接池
    2022-12-12
  • Java使用itextpdf实现Excel转PDF

    Java使用itextpdf实现Excel转PDF

    这篇文章主要为大家详细介绍了Java如何使用itextpdf实现Excel转PDF,并且支持xlsx和xls两种格,文中的示例代码讲解详细,希望对大家有所帮助
    2024-01-01
  • java开发之闹钟的实现代码

    java开发之闹钟的实现代码

    本篇文章介绍了,在java中闹钟的实现代码。需要的朋友参考下
    2013-05-05
  • java控制台输出版多人聊天室

    java控制台输出版多人聊天室

    这篇文章主要为大家详细介绍了java控制台输出版多人聊天室,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-09-09
  • java使用http实现文件下载学习示例

    java使用http实现文件下载学习示例

    这篇文章主要介绍了java使用http实现文件下载学习示例,需要的朋友可以参考下
    2014-04-04

最新评论