1. 文件及文件夹操作简介

java自身对文件的操作来自于java.io.File类,能够支持:

boolean exists():
Tests whether the file or directory denoted by this abstract pathname exists.
boolean delete():
Deletes the file or directory denoted by this abstract pathname.
boolean mkdir():
Creates the directory named by this abstract pathname.
boolean mkdirs():
Creates the directory named by this abstract pathname, including any necessary but nonexistent parent directories.
boolean renameTo(File dest):
Renames the file denoted by this abstract pathname.

可见,能够进行的操作很有限,不能直接进行复制和移动。对此我们可以有两种方法,一种是通过在java.io.File上建立java.io.FileInputStream和java.io.FileOutputStream,利用字节流进行文件的读取和写入,然而效率不高;另一种是在已经建立的字节流上获取java.nio.channels.FileChannel,利用文件通道来高效实现文件的读取和写入,这种方式是我们要采用的。
注:我们在实现复制和移动操作时,如果将要保存的文件或文件夹已经存在,那么要自动编号命名防止覆盖原始文件(类似于操作系统自身的操作行为)。

2. 代码实现

2.1. 复制

/**
     * 复制文件
     * 从源路径到目标文件夹路径,文件名保持一致
     * 如果目标文件夹不存在则自动创建
     * 如果文件已经存在则自动编号-copy n
     *
     * @param srcFile 源文件绝对路径
     * @param dstDir  目标文件夹绝对路径
     * @return 是否成功复制文件
     */
    public static boolean copyFile(File srcFile, File dstDir) {
        if (!srcFile.exists() || srcFile.isDirectory()) {
            return false;
        }
        if (!dstDir.exists()) {
            dstDir.mkdirs();
        }
        String oldFileName = srcFile.getName();
        Pattern suffixPattern = Pattern.compile("\\.\\w+");
        Matcher matcher = suffixPattern.matcher(oldFileName);
        String nameBody;
        String suffix;
        if (matcher.find()) {
            nameBody = oldFileName.substring(0, matcher.start());
            suffix = oldFileName.substring(matcher.start());
        } else {
            nameBody = oldFileName;
            suffix = "";
        }
        int fileNumber = 0;
        File newFile = new File(dstDir, oldFileName);
        while (newFile.exists()) {
            fileNumber++;
            String newFileName = nameBody + "-copy" + fileNumber + suffix;
            newFile = new File(dstDir, newFileName);
        }
        try {
            FileChannel fileIn = new FileInputStream(srcFile).getChannel();
            FileChannel fileOut = new FileOutputStream(newFile).getChannel();
            fileIn.transferTo(0, fileIn.size(), fileOut);
            fileIn.close();
            fileOut.close();
        } catch (IOException e) {
            return false;
        }
        return true;
    }

    /**
     * 复制文件或文件夹
     * 如果目标文件夹不存在则自动创建
     * 如果文件或文件夹已经存在则自动编号-copy n
     *
     * @param src    源文件或文件夹绝对路径
     * @param dstDir 目标文件夹绝对路径
     * @return 是否成功复制文件或文件夹
     */
    public static boolean copy(File src, File dstDir) {
        if (!src.exists()) {
            return false;
        }
        if (!dstDir.exists()) {
            dstDir.mkdirs();
        }
        if (src.isFile()) {// 文件
            copyFile(src, dstDir);
        } else {// 文件夹
            String oldSrcName = src.getName();
            int srcNumber = 0;
            File newSrcDir = new File(dstDir, oldSrcName);
            while (newSrcDir.exists()) {
                srcNumber++;
                String newSrcName = oldSrcName + "-copy" + srcNumber;
                newSrcDir = new File(dstDir, newSrcName);
            }
            newSrcDir.mkdirs();
            for (File srcSub : src.listFiles()) {
                copy(srcSub, newSrcDir);// 递归复制源文件夹下子文件和文件夹
            }
        }
        return true;
    }

2.2. 移动

/**
     * 移动(剪切)文件
     *
     * @param srcFile
     * @param dstDir
     * @return
     */
    public static boolean moveFile(File srcFile, File dstDir) {
        if (!srcFile.exists() || srcFile.isDirectory()) {
            return false;
        }
        if (!dstDir.exists()) {
            dstDir.mkdirs();
        }
        String oldFileName = srcFile.getName();
        File dstFile = new File(dstDir, oldFileName);
        if (srcFile.renameTo(dstFile)) {// 直接重命名绝对路径速度更快
            return true;
        } else {// 文件已经存在,需要自动编号复制再删除源文件
            copyFile(srcFile, dstDir);
            srcFile.delete();
        }
        return true;
    }

    /**
     * 移动文件或文件夹
     * 如果目标文件夹不存在则自动创建
     * 如果文件或文件夹已经存在则自动编号-copy n
     *
     * @param src    源文件或文件夹绝对路径
     * @param dstDir 目标文件夹绝对路径
     * @return 是否成功移动文件或文件夹
     */
    public static boolean move(File src, File dstDir) {
        if (!src.exists()) {
            return false;
        }
        if (!dstDir.exists()) {
            dstDir.mkdirs();
        }
        if (src.isFile()) {// 文件
            moveFile(src, dstDir);
        } else {// 文件夹
            String oldSrcName = src.getName();
            int srcNumber = 0;
            File newSrcDir = new File(dstDir, oldSrcName);
            while (newSrcDir.exists()) {
                srcNumber++;
                String newSrcName = oldSrcName + "-copy" + srcNumber;
                newSrcDir = new File(dstDir, newSrcName);
            }
            newSrcDir.mkdirs();
            for (File srcSub : src.listFiles()) {
                move(srcSub, newSrcDir);// 递归移动源文件夹下子文件和文件夹
            }
            src.delete();
        }
        return true;
    }

2.3. 删除

/**
     * 删除文件或文件夹
     *
     * @param src 源文件或文件夹绝对路径
     * @return 是否成功删除文件或文件夹
     */
    public static boolean delete(File src) {
        if (!src.exists()) {
            return false;
        }
        if (src.isFile()) {
            src.delete();
        } else {
            for (File srcSub : src.listFiles()) {
                delete(srcSub);// 递归删除源文件夹下子文件和文件夹
            }
            src.delete();
        }
        return true;
    }

2.4. 测试类

public class FileOperator {
    public static void main(String[] args) {
        File srcFile1 = new File("E:\\a");
        File dstDir1 = new File("E:\\b");
        for (int i = 0; i < 5; i++) {
            copy(srcFile1, dstDir1);
        }
        File srcFile2 = new File("E:\\b\\a");
        File srcFile3 = new File("E:\\b\\a-copy1");
        File srcFile4 = new File("E:\\b\\a-copy2");
        File dstDir2 = new File("E:\\c");
        move(srcFile2, dstDir2);
        move(srcFile3, dstDir2);
        move(srcFile4, dstDir2);
        delete(srcFile2);
    }
}