前言
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();
File destFile = new File(destPath);
if (!destFile.exists()) {
destFile.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) {
FileChannel inChannel = null;
FileChannel outChannel = null;
try {
inChannel = new FileInputStream(src).getChannel();
outChannel = new FileOutputStream(dest).getChannel();
int count = 1024 * 1024 * 8;
long size = inChannel.size();
long position = 0;
while (position < size) {
position += inChannel.transferTo(position, count, outChannel);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
close(outChannel);
close(inChannel);
}
}
测试
- 条件:拷贝23,499个文件,共4.57G
- 结果
拷贝方式 | 耗时 |
---|---|
普通拷贝 | 45~50秒 |
高并行拷贝 | 30~33秒 |
高并行+NIO拷贝 | 25~27秒 |