Java高效拷贝——高并行、NIO

标签: 高效拷贝 高并行 NIO Stream java
8人阅读 评论(0) 收藏 举报
分类:

前言

Java文件拷贝可能是个比较简单的事,只要有点基础的都会。但要想玩出点花样,或者说高效的进行文件拷贝,却要花点功夫(代码逻辑设计、知识面)。在这里,我们从高并行拷贝与NIO拷贝出发,再结合它们,从而提高拷贝效率!

传统的拷贝方式

  • 传统的拷贝方式为串行拷贝,较为简单
  • 其方式为:1.获取数据源路径 -> 2.判断是文件还是目录 -> 3.如果是文件就拷贝,否则递归遍历该目录的子文件
  • 代码示例
import java.io.*;

/**
 * 普通的文件拷贝工具
 *
 * @author ALion
 * @version 2018/4/15 15:11
 */
public class FileUtils {

    /**
     * 拷贝目录、文件
     *
     * @param sourcePath 源路径
     * @param destPath 目标路径
     */
    public static void copyDir(String sourcePath, String destPath) throws IOException {
        File file = new File(sourcePath);
        String[] filePath = file.list();

        if (!(new File(destPath)).exists()) {
            (new File(destPath)).mkdir();
        }

        if (filePath != null) {
            for (String aFilePath : filePath) {
                if ((new File(sourcePath + "/" + aFilePath)).isDirectory()) {
                    copyDir(sourcePath + "/" + aFilePath, destPath + "/" + aFilePath);
                } else {
                    copyFile(sourcePath + "/" + aFilePath, destPath + "/" + aFilePath);
                }

            }
        }
    }

    /**
     * 拷贝单个文件
     *
     * @param sourcePath 源路径
     * @param newPath目标路径
     */
    private static void copyFile(String sourcePath, String newPath) throws IOException {
        File oldFile = new File(sourcePath);
        File file = new File(newPath);
        FileInputStream in = new FileInputStream(oldFile);
        FileOutputStream out = new FileOutputStream(file);;

        byte[] buffer=new byte[2048];

        while((in.read(buffer)) != -1){
            out.write(buffer);
            out.flush();
        }
    }

}

高并行拷贝

  • 通常需要拷贝的文件不是单个存在的,利用并行的方式进行拷贝能够明显的提升效率
  • 其方式为:1.获取数据源的路径 -> 2.解析出该路径下的所有目录与文件夹 3.利用高并行的方式,优先拷贝所有目录,再拷贝所有文件
  • 代码示例
import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;

/**
 * 高并行文件拷贝工具
 *
 * @author ALion
 * @version 2018/4/15 15:11
 */
public class FileUtils {

    /**
     * 拷贝目录、文件
     *
     * @param src  源路径
     * @param dest 目标路径
     */
    public static void copyAll(String src, String dest) {
        if (!check(src, dest)) return;

        String root = dest + "/" + new File(src).getName();

        //递归解析出该目录下的所有目录、文件路径
        HashMap<String, ArrayList<String>> dirsAndFiles = getDirsAndFiles(src);
        try {
            //parallelStream是Java8的方法,提高集合遍历的并行度
            //先拷贝目录
            ArrayList<String> dirList = dirsAndFiles.get("dirs");
            dirList.parallelStream().forEach(dir -> {
                new File(dest + dir).mkdirs();
            });

            //再拷贝文件
            ArrayList<String> fileList = dirsAndFiles.get("files");
            fileList.parallelStream().forEach(file -> {
                String parent = new File(src).getParent();
                copyFile(parent + file, dest + file);
            });
        } catch (Exception e) {
            e.printStackTrace();
            //创建失败时,恢复初始
            recover(root);
        }

    }

    /**
     * 拷贝单个文件
     *
     * @param src  源文件
     * @param dest 目标文件
     */
    public static void copyFile(String src, String dest) {
        FileInputStream is = null;
        FileOutputStream os = null;
        try {
            is = new FileInputStream(src);
            os = new FileOutputStream(dest);
            byte[] buffer = new byte[2048];
            int len = 0;
            while (-1 != (len = is.read(buffer))) {
                os.write(buffer, 0, len);
                os.flush();
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            close(is);
            close(os);
        }
    }

    /**
     * 删除路径的所有文件
     *
     * @param path 路径
     */
    public static void removeAll(String path) {
        if (!new File(path).exists()) {
            System.out.println("目标路径不存在");
            return;
        }

        HashMap<String, ArrayList<String>> dirsAndFiles = getDirsAndFiles(path);

        ArrayList<String> fileList = dirsAndFiles.get("files");
        fileList.parallelStream().forEach(file -> {
            String parent = new File(path).getParent();
            new File(parent + file).deleteOnExit();
        });

        ArrayList<String> dirList = dirsAndFiles.get("dirs");
        dirList.parallelStream().forEach(dir -> {
            String parent = new File(path).getParent();
            new File(parent + dir).deleteOnExit();
        });
    }

    private static ArrayList<String> dirList = new ArrayList<>();
    private static ArrayList<String> fileList = new ArrayList<>();

    /**
     * 获取路径下的文件、目录
     *
     * @param path 目标路径
     * @return Map[文件或路径标识, 文件集合或目录集合]
     */
    public static HashMap<String, ArrayList<String>> getDirsAndFiles(String path) {
        HashMap<String, ArrayList<String>> map = new HashMap<>();

        searchDirsAndFiles(path, "");
        map.put("dirs", dirList);
        map.put("files", fileList);

        return map;
    }

    /**
     * 搜索路径下的文件、目录,并存入dirList、fileList
     *
     * @param path 目标路径
     */
    private static void searchDirsAndFiles(String path, String parent) {
        File file = new File(path);
        String savePath = parent + "/" + file.getName();
        if (file.isFile()) {
            fileList.add(savePath);
        } else {
            dirList.add(savePath);

            File[] files = file.listFiles();
            if (files != null) {
                for (File childFile : files) {
                    searchDirsAndFiles(childFile.getAbsolutePath(), savePath);
                }
            }
        }
    }

    /**
     * 检查源路径和目标路径
     *
     * @param src  源路径
     * @param dest 目标路径
     * @return 是否异常
     */
    private static boolean check(String src, String dest) {
        File srcFile = new File(src);
        if (!srcFile.exists()) {
            System.out.println("源路径不存在");
            return false;
        }
        if (!new File(dest).exists()) {
            System.out.println("目标路径不存在");
            return false;
        }
        if (new File(dest + "/" + srcFile.getName()).exists()) {
            System.out.println("目标路径下已存在该文件");
            return false;
        }
        return true;
    }

    /**
     * 恢复文件目录到无
     *
     * @param root 目标路径
     */
    private static void recover(String root) {
        System.out.println("文件创建异常,删除已创建的文件。");
        removeAll(root);
    }

    /**
     * 关闭流
     *
     * @param closeable 可关闭的接口
     */
    private static void close(Closeable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

}

NIO拷贝

  • NIO拷贝使得读取磁盘文件时,不用通过内核缓冲区再到用户进程缓冲区的做拷贝操作。操作系统会通过一些页面调度算法来将磁盘文件载入对分页区进行高速缓存的物理内存。接着就可以通过映射后物理内存来读取磁盘文件,从而提高效率。
  • 代码示例
    /**
     * 拷贝单个文件(已修改该段代码为NIO拷贝方式)
     *
     * @param src  源文件
     * @param dest 目标文件
     */
    public static void copyFile(String src, String dest) {
        FileInputStream is = null;
        FileOutputStream os = null;
        FileChannel isc = null;
        FileChannel osc = null;
        try {
            is = new FileInputStream(src);
            os = new FileOutputStream(dest);
            isc = is.getChannel();
            osc = os.getChannel();
            isc.transferTo(0, isc.size(), osc);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            close(isc);
            close(osc);
            close(is);
            close(os);
        }
    }

测试

  • 条件:拷贝1441个文件,共1.05G
  • 结果
拷贝方式 耗时
普通拷贝 20~25秒
高并行拷贝 11~13秒
高并行+NIO拷贝 10~11秒
查看评论

组件重用需要专人负责

不知道诸位所在的公司如何,至少在我现在的公司,代码的重用性是很差的。同一个模块在多个项目组之间重复开发,甚至同一个模块在同一个项目的不同版本之间也是不同的人重复开发......我们把大量的时间放在了做...
  • optman
  • optman
  • 2001-09-10 19:35:00
  • 817

java读写文件时nio、bio对比

1.   基本 概念       IO 是主存和外部设备 ( 硬盘、终端和网络等 ) 拷贝数据的过程。 IO 是操作系统的底层功能实现,底层通过 I/O 指令进行完成。 所有语言运行时系统提供执行 ...
  • liuxiao723846
  • liuxiao723846
  • 2015-04-15 23:10:06
  • 1148

Java Nio 零拷贝

传统的IO处理方式 上下文切换包括:用户空间(User space),内核空间(Kemel space) 首先,当读取一个硬盘上的文件时,上下文会从用户空间切换到内核空间,由内核空间以DMA(Dir...
  • yinhaonefu
  • yinhaonefu
  • 2017-11-06 08:39:38
  • 548

java文件普通复制和NIO复制

自从jdk1.4以后就引入了NIO文件通道的概念,并且是由io的FileinputStream和FileoutputStream获取 以下代码传入参数 File Copyfile 复制的文件File ...
  • chenshuzhuo
  • chenshuzhuo
  • 2017-04-19 14:49:02
  • 987

可能是最完美的Android复制拷贝文件的实例(Java NIO速度快)

此处我使用的是使用Java NIO中的管道到管道传输,包括了transferFrom方法。 经过测试比文件流复制的速度更快! private final static String FileNam...
  • mvpstevenlin
  • mvpstevenlin
  • 2017-01-05 10:52:59
  • 2133

java nio: walkFileTree实现文件夹复制移动删除

从java 1.7开始,java提供了java.noi.file.Files类用于更方便的实现文件/文件夹操作。\ 在Files中提供了丰富的静态方法用于文件操作,Files也提供了文件移动和复制操...
  • 10km
  • 10km
  • 2017-01-13 15:33:15
  • 2391

Java NIO学习笔记四(零拷贝详解)

什么是零拷贝 维基上是这么描述零拷贝的:零拷贝描述的是CPU不执行拷贝数据从一个存储区域到另一个存储区域的任务,这通常用于通过网络传输一个文件时以减少CPU周期和内存带宽。 零拷贝给我们带来的...
  • u013096088
  • u013096088
  • 2018-01-21 20:20:57
  • 276

java nio 复制文件功能

  package com.ghb.crp.file;     import java.io.BufferedInputStream;   import java.io.BufferedOutputS...
  • ghb5371548
  • ghb5371548
  • 2010-05-18 12:03:00
  • 1652

NIO 零拷贝深入分析

什么是零拷贝零拷贝 大概的理解就是在操作数据时, 不需要将数据 从一个内存区域拷贝到另一个内存区域. 因为少了内存的拷贝, 因此 CPU 的效率就得到的提升,同时它是操作系统层面上的操作LINUX与 ...
  • lm324114
  • lm324114
  • 2017-10-13 10:22:27
  • 449

为什么NIO比BIO效率高

NIO比BIO效率高,主要原因是什么呢? 网上大多给出了两者的区别,可是具体效率高在哪里呢。 首先我们看一下各自的特点 BIO: socketServer的accept方法是阻塞的。 当有连...
  • wy0123
  • wy0123
  • 2018-02-26 22:53:50
  • 77
    个人资料
    持之以恒
    等级:
    访问量: 3万+
    积分: 797
    排名: 6万+
    最新评论