Java文件拷贝的几种方式

一、前言

        文件拷贝(传输)涉及到Java中的输入和输出流(InputStream,OutputStream),FileChannel等知识点,把文件拷贝学明白了,IO流的相关知识点在头脑中也会更加清晰。这篇博客介绍几种文件拷贝的方法,其中有一些方法是较为底层的(说白了就是自己手写的方法)在实际生产环境中不推荐使用,但是通过这些较为底层的方法才能更好的理解 “流” 的操作过程,有一些方法是JDK提供的、还有一些方法是各种工具类提供的。JDK提供的和各种工具类提供的,建议在生产环境中使用。

        此篇博客主要分为传统的阻塞IO(Blocking I/O)实现的文件拷贝和基于NIO(No-Blocking I/O)的FileChannel方式实现的文件拷贝。

        题外话:既然别人已经提供了优秀的文件拷贝方法,我们没有必要去重复造轮子,但是!如果想用好这些工具类,前提是你要明白底层原理。只有这样,当你使用那些优秀的工具类时才能游刃有余地去使用,并且看别人的源码时还可以学习一些编码的新知识,如果只是囫囵吞枣的用别人提供的工具,当时是解决了你的问题,但是总感觉用的一头雾水】

二、BIO拷贝文件的几种方式

1. 拷贝文件 - V1【生产环境不推荐使用】

        最原始的文件拷贝方式(使用BIO的方式),也没有使用 try-with-resources 方式管理资源,生产环境不推荐使用。

    private static void copyFileV1() throws IOException {
        // 源文件
        File sourceFile = new File("test.txt");
        // 目标文件
        File targetFile = new File("test_copy.txt");

        FileInputStream fis = new FileInputStream(sourceFile);
        FileOutputStream fos = new FileOutputStream(targetFile);

        System.out.println("fis.available(): " + fis.available());
        byte[] buffer = new byte[1024];
        int length;
        // int total = 0;
        // int number = 0;
        while ((length = fis.read(buffer)) != -1) {
            // total += length;
            // number++;
            fos.write(buffer, 0, length);
        }
        // System.out.println("total bytes: " + total);
        // System.out.println("number: " + number);
        fos.close();
        fis.close();
    }

 2. 拷贝文件 - V2 【生产环境不推荐使用】

        使用BIO的方式,使用try-with-resources的方式释放资源,生产环境不推荐使用。

    private static void copyFileV2() throws IOException {
        // 源文件
        File sourceFile = new File("test.txt");
        // 目标文件
        File targetFile = new File("test_copy.txt");

        try (FileInputStream fis = new FileInputStream(sourceFile);
             FileOutputStream fos = new FileOutputStream(targetFile)) {
            byte[] buffer = new byte[1024];
            int length;
            while ((length = fis.read(buffer)) != -1) {
                fos.write(buffer, 0, length);
            }
        }
    }

3. 拷贝文件 - V3. 使用JDK提供的Files.copy方法 【生产环境推荐使用】

        使用JDK提供的Files.copy工具类。JDK 提供的 Files.copy() 方法本身并没有特定的文件大小限制。这个方法是用于从源通道复制到目标通道,通常用于文件拷贝。拷贝操作的效率和限制更多地取决于底层的文件系统、可用内存、JVM堆大小以及操作系统的限制等因素。

        然而,对于大文件的拷贝,使用 Files.copy() 方法可能不是最高效的方式,因为它可能会涉及将整个文件内容加载到内存中。对于大文件,推荐的做法是使用带缓冲区的分块读取和写入,这样可以减少内存消耗并提高性能,参考【3.  拷贝文件 - V2】。

    private static void copyFileV3() throws IOException {
        Path sourceFile = Paths.get("test.txt");
        Path targetFile = Paths.get("test_copy.txt");
        Files.copy(sourceFile, targetFile);
        //Files.copy(sourceFile, targetFile, StandardCopyOption.COPY_ATTRIBUTES);
    }

4. 拷贝文件 - V4. 使用Hutool工具提供的文件拷贝方法 【生产环境推荐使用】

        需要导入hutool工具包依赖,如下。其【底层使用的是JDK提供的Files工具类】。

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.8.8</version>
</dependency>
    private static void copyFileV4() {
        // 源文件
        File sourceFile = new File("test.txt");
        // 目标文件
        File targetFile = new File("test_copy.txt");

        FileUtil.copy(sourceFile, targetFile, false);
    }

5.  拷贝文件 - V5. 使用google提供的 guava 中的工具类 【生产环境推荐使用】

        需要导入 guava工具类。如下。【底层使用的和 copyFileV2 一样的方式,但是使用这个工具类的好处是可以帮我们自动释放流资源, 底层创建的缓冲数组大小是8192个字节】

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>19.0</version>
</dependency>
    private static void copyFileV5() throws IOException {
        // 源文件
        File sourceFile = new File("test.txt");
        // 目标文件
        File targetFile = new File("test_copy.txt");
        com.google.common.io.Files.copy(sourceFile, targetFile);
    }

 三、基于NIO的FileChannel 拷贝文件的几种方式

1.  拷贝文件 - V1. 【生产环境不推荐使用】

        使用FileChannel拷贝文件(最基本的使用方式),没有考虑文件大小问题 。

    private static void v1() throws IOException {
        // 获取文件输入流
        File file = new File("test.txt");
        FileInputStream fileInputStream = new FileInputStream(file);
        // 从文件输入流获取通道
        FileChannel inputStreamChannel = fileInputStream.getChannel();

        // 获取文件输出流
        FileOutputStream fileOutputStream = new FileOutputStream("test_copy.txt");
        // 从文件输出流获取通道
        FileChannel outputStreamChannel = fileOutputStream.getChannel();

        // 创建ByteBuffer, 文件内容不大,这里的演示就一次性读取,部分多次读取了
        ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());

        // 把输入流通道的数据读取到数据缓冲区
        inputStreamChannel.read(byteBuffer);

        // 切换成读模式
        byteBuffer.flip();

        // 把缓冲区(ByteBuffer)中的数据写入到输出流通道
        outputStreamChannel.write(byteBuffer);

        // 关闭资源
        fileOutputStream.close();
        fileInputStream.close();
        outputStreamChannel.close();
        inputStreamChannel.close();
    }

2.  拷贝文件 - V2. 【生产环境不推荐使用】

        相比于上面的示例,这个示例代码考虑了文件的大小,使用缓冲区一次读取1024个字节的数据。

    private static void v2_1() {

        try (FileInputStream fileInputStream = new FileInputStream("test.txt");
             FileChannel inputStreamChannel = fileInputStream.getChannel();

             FileOutputStream fileOutputStream = new FileOutputStream("test_copy.txt");
             FileChannel outputStreamChannel = fileOutputStream.getChannel()) {

            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            while ((inputStreamChannel.read(byteBuffer)) != -1) {
                byteBuffer.flip(); // Prepare the buffer for writing
                outputStreamChannel.write(byteBuffer);
                byteBuffer.clear(); // Prepare the buffer for reading again
            }
            System.out.println("Large file copied successfully.");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

        这个示例和上面的v2_1方法是一样的,只不过获取FileChannel的方式不一样。 

    private static void v2_2() {
        Path source = Paths.get("test.txt");
        Path target = Paths.get("test_copy.txt");
        
        try (FileChannel sourceChannel = FileChannel.open(source, StandardOpenOption.READ);
             FileChannel targetChannel = FileChannel.open(target, StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {
            
            ByteBuffer buffer = ByteBuffer.allocate(8 * 1024); // 8 KB buffer
            while (sourceChannel.read(buffer) != -1) {
                buffer.flip(); // Prepare the buffer for writing
                targetChannel.write(buffer);
                buffer.clear(); // Prepare the buffer for reading again
            }
            
            System.out.println("Large file copied successfully.");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

 3.  拷贝文件 - V3. 【生产环境推荐使用】

        使用FileChannel的transferTo方法拷贝文件(文件传输,效率比自己写的更高,JDK中方法transferTo的底层都会使用零拷贝进行优化) 【transferTo方法一次性传递的文件大小上限是2G,所以此代码考虑了如果要拷贝的文件大小超过2G的问题。】

    private static void v3() {
        try (FileInputStream fileInputStream = new FileInputStream("test.txt");
             FileChannel inputStreamChannel = fileInputStream.getChannel();
             FileOutputStream fileOutputStream = new FileOutputStream("test_copy.txt");
             FileChannel outputStreamChannel = fileOutputStream.getChannel()) {
            long size = inputStreamChannel.size();
            // left变量表示还剩余多少字节要传递
            for (long left = size; left > 0; ) {
                log.info("position:{},left:{}", (size - left), left);
                left = left - inputStreamChannel.transferTo((size - left), left, outputStreamChannel);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

  • 9
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java 提供了多种文件操作的方式,下面介绍常用的几种: 1. 文件读取 使用 Java 的 I/O 流可以从文件中读取数据。可以使用 BufferedReader 或 Scanner 类来读取文本文件,使用 FileInputStream 类来读取二进制文件。示例代码如下: ```java // 读取文本文件 File file = new File("file.txt"); BufferedReader reader = new BufferedReader(new FileReader(file)); String line; while ((line = reader.readLine()) != null) { System.out.println(line); } reader.close(); // 读取二进制文件 File file = new File("file.bin"); InputStream inputStream = new FileInputStream(file); byte[] buffer = new byte[1024]; int length; while ((length = inputStream.read(buffer)) != -1) { // 处理读取的数据 } inputStream.close(); ``` 2. 文件写入 使用 Java 的 I/O 流可以将数据写入文件中。可以使用 BufferedWriter 或 PrintWriter 类来写入文本文件,使用 FileOutputStream 类来写入二进制文件。示例代码如下: ```java // 写入文本文件 File file = new File("file.txt"); BufferedWriter writer = new BufferedWriter(new FileWriter(file)); writer.write("Hello, world!"); writer.newLine(); writer.write("This is a test file."); writer.close(); // 写入二进制文件 File file = new File("file.bin"); OutputStream outputStream = new FileOutputStream(file); byte[] buffer = new byte[]{1, 2, 3, 4, 5}; outputStream.write(buffer); outputStream.close(); ``` 3. 文件拷贝 可以使用 Java 的 I/O 流将一个文件的内容复制到另一个文件。示例代码如下: ```java File sourceFile = new File("source.txt"); File destFile = new File("dest.txt"); InputStream inputStream = new FileInputStream(sourceFile); OutputStream outputStream = new FileOutputStream(destFile); byte[] buffer = new byte[1024]; int length; while ((length = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, length); } inputStream.close(); outputStream.close(); ``` 4. 文件夹操作 Java 提供了 File 类来操作文件文件夹。可以使用 mkdir() 方法创建文件夹,使用 list() 或 listFiles() 方法列出文件夹中的文件文件夹,使用 delete() 方法删除文件文件夹。示例代码如下: ```java // 创建文件File directory = new File("dir"); directory.mkdir(); // 列出文件夹中的文件文件File directory = new File("dir"); String[] files = directory.list(); for (String file : files) { System.out.println(file); } // 删除文件文件File file = new File("file.txt"); file.delete(); ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值