【JAVA】递归以及NIO2实现文件夹的相关操作(复制、删除、移动、搜索文件)

在java中,无法直接对文件夹(目录)进行复制、移动、删除等操作。自己可以通过递归来实现。下面代码给出递归实现的参考。

注意:对文件夹的相关操作简单封装成工具类,代码注重递归的实现,不关注对传入的路径的判断。比如说:复制操作中将父目录复制到其子目录下,这样是非法的。但在对应的函数中并未进行判断。

做过简单的测试,未发现问题。若有问题,欢迎指出!

package com.directory;

import org.junit.Test;

import java.io.*;

/**
 * @author passerbyYSQ
 * @create 2019-11-15 20:24
 */
public class DirUtils {
    private static int bufSize = 1024;             //作为缓冲区的字节数组的大小
    private static byte[] buf = new byte[bufSize]; //作为缓冲区的字节数组

    public static int getBufSize() {
        return bufSize;
    }

    //调用者可以自己修改作为缓冲区的字节数组的大小
    public static void setBufSize(int bufSize) {
        DirUtils.bufSize = bufSize;
    }

    @Test
    public void testCopy() {
        try {
            long costTime = copy("F:\\web前端设计与开发", "F:\\temp");
            System.out.println("耗时:" + costTime + "ms"); //耗时:143ms
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void testDelete() {
        long sta = System.currentTimeMillis();
        delete("F:\\temp\\web前端设计与开发");
        long end = System.currentTimeMillis();
        System.out.println("耗时:" + (end - sta) + "ms"); //耗时:73ms
    }

    @Test
    public void testMove() {
        try {
            move("F:\\web前端设计与开发", "F:\\temp");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void testFindFile() {
        long sta = System.currentTimeMillis();
        findFile("F:\\web前端设计与开发", "index.html");
        long end = System.currentTimeMillis();
        System.out.println("耗时:" + (end - sta) + "ms"); //耗时:45ms
    }

    /**
     * 将指定目录(或者文件)移动到指定的路径(目录)
     * 注意:两个路径要求是绝对路径
     * @param srcPath   将要复制的目录或者文件
     * @param destPath  目标路径(目录)
     * @return          耗费的时间(单位:ms)
     */
    public static long move(String srcPath, String destPath) throws FileNotFoundException {
        long sta = System.currentTimeMillis();

        //先将源文件或者目录复制到目标目录
        copy(srcPath, destPath); //如果srcPath填错,直接将异常抛给调用者

        //再删除源文件或者源目录
        delete(srcPath);

        long end = System.currentTimeMillis();
        return (end - sta);
    }

    /**
     * 将指定目录(或者文件)复制到指定的路径(目录)
     * 注意:两个路径要求是绝对路径
     * @param srcPath   将要复制的目录或者文件
     * @param destPath  目标路径(目录)
     * @return          耗费的时间(单位:ms)
     */
    public static long copy(String srcPath, String destPath) throws FileNotFoundException {
        long sta = System.currentTimeMillis();

        File file = new File(srcPath);
        //如果源目录或者源文件在硬盘上不存在,则向调用者抛出异常
        if (!file.exists()) {
            throw new FileNotFoundException();
        }

        //如果要复制的是个目录,则在目标目录下建一个同名目录
        if (file.isDirectory()) {
            destPath += ("\\"+ file.getName());
        }

        copying(srcPath, destPath);
        long end = System.currentTimeMillis();
        return (end - sta);
    }

    private static void copying(String srcPath, String destPath) {
        File file = new File(srcPath);

        if (file.isFile()) {
            //在复制之前,判断当前文件的父目录是否存在,不存在则创建后再复制
            String parentPath = new File(destPath).getParent();
            if (parentPath != null) {
                File parentFile = new File(parentPath);
                if (!parentFile.exists()) {
                    parentFile.mkdirs();
                }
            }

            BufferedInputStream bis = null;  //缓冲字节输入流
            BufferedOutputStream bos = null;       //缓冲字节输出流
            try {
                bis = new BufferedInputStream(new FileInputStream(file));
                bos = new BufferedOutputStream(new FileOutputStream(destPath));

                int len;
                while ((len = bis.read(buf)) != -1) {
                    bos.write(buf, 0, len);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (bis != null) {  //关闭了外层的包装流,内层流也会自动关闭
                    try {
                        bis.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (bos != null) {
                    try {
                        bos.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
        } else {
            File[] fileList = file.listFiles();
            for (File f : fileList) {
                copying(f.getAbsolutePath(), destPath + "/" + f.getName());
            }
        }
    }

    /**
     * 删除指定的目录或者文件
     * 注意:如果是删除目录,该函数将该目录内的所有子文件以及子目录全删除之后,
     *      还会把这个空目录给删除掉
     * @param filePath  指定的目录或者文件的绝对路径
     */
    public static void delete(String filePath) {
        File file = new File(filePath);
        if (file.isFile()) {
            file.delete();
        } else {
            File[] fileList = file.listFiles();
            for (File f : fileList) {
                delete(filePath + "/" + f.getName());

                //如果当前是文件夹,在删除该文件夹的所有文件及子文件夹之后(回溯时),将该空文件夹删除
                //如果在这里加上代码,那么最终的外层目录不会删除
//                if (f.isDirectory()) {
//                    f.delete();
//                }
            }
        }
        //回溯时,把空文件夹给删掉
        file.delete();
    }

    /**
     * 在指定的目录中搜索指定的文件,若找到,则将绝对路径输出到控制台上
     * 注意:不区分大小写
     * @param srcPath   搜索的目录
     * @param filename  目标文件的文件名
     */
    public static void findFile(String srcPath , String filename) {
        File file = new File(srcPath);
        if (file.isFile()) {
            if (file.getName().equalsIgnoreCase(filename)) {
                System.out.println(srcPath);  //file.getAbsolutePath()也可以
            }
        } else {
            File[] fileList = file.listFiles();
            for (File f : fileList) {
                findFile(srcPath + "/" + f.getName(), filename);
            }
        }
    }
}

关于NIO2,参见大佬博客:https://blog.csdn.net/oMaoYanEr/article/details/80961130

经过简单测试,NIO2的效率比自己用递归实现快。

至于为什么在复制文件夹时耗时差不多,原因是因为我在用walkFileTree()中使用了String的替换,String是不可变的字符序列,每次替换都要在字符串常量池中开辟一个新的空间来装新的字符串,并将指针指向这块的新的内存。这个过程相对来说是非常耗时的。尤其是当这棵目录树毕竟庞大时,递归的深度就会较大,那么就会频繁地进行上述的替换操作。

所以我猜测,我这种实现复制操作的方法,在对一棵比较大的目录树进行复制操作时,它的时间开销会比递归实现要高!但我目前没有想到其他实现方法。

package com.directory;

import org.junit.Test;

import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;

/**
 * @author passerbyYSQ
 * @create 2019-11-16 10:25
 */
public class NIO2DirUtils {

    @Test
    public void testDelete() {
        long costTime = delete("F:\\temp\\web前端设计与开发");
        System.out.println("耗时:" + costTime + "ms"); //耗时:35ms
    }

    @Test
    public void testCopy() {
        long costTime = copy("F:\\web前端设计与开发", "F:\\temp");
        //long costTime = copy("F:\\web前端设计与开发\\实验17:异步请求.zip", "F:\\temp");
        System.out.println("耗时:" + costTime + "ms"); //耗时:138ms
    }

    @Test
    public void testFindFile() {
        long costTime = findFile("F:\\temp\\web前端设计与开发", "Index.html");
        System.out.println("耗时:" + costTime + "ms"); //耗时:9ms
    }

    /**
     * 删除指定目录或者文件
     * @param srcPath   可以是绝对路径或者相对路径
     * @return          耗时(单位:ms)
     */
    public static long delete(String srcPath) {
        long sta = System.currentTimeMillis();

        Path start = Paths.get(srcPath);
        try {
            Files.walkFileTree(start, new SimpleFileVisitor<Path>() {

                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    Files.delete(file);
                    return FileVisitResult.CONTINUE;
                }

                @Override
                public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
                    Files.delete(dir);
                    return FileVisitResult.CONTINUE;
                }
            });
        } catch (IOException e) {
            e.printStackTrace();
        }

        long end = System.currentTimeMillis();
        return (end - sta);
    }

    /**
     * 源目录或者源文件  -复制->  目标目录下
     * @param srcPath   源目录或者源文件的路径(绝对路径或者相对路径)
     * @param destPath  目标目录的路径(绝对路径或者相对路径)
     */
    public static long copy(String srcPath, String destPath) {
        long sta = System.currentTimeMillis();
        Path start = Paths.get(srcPath);
        final String prefix = destPath + "/" + start.getFileName();

        try {
            Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    String newFilePos = file.toAbsolutePath().toString().replace(srcPath, prefix);
                    Path newFile = Paths.get(newFilePos);

                    if (!Files.exists(newFile.getParent())) {
                        Files.createDirectories(newFile.getParent());
                    }

                    Files.copy(file, newFile);
                    return FileVisitResult.CONTINUE;
                }
            });
        } catch (IOException e) {
            e.printStackTrace();
        }

        long end = System.currentTimeMillis();
        return (end - sta);
    }

    /**
     * 在指定的目录中搜索指定的文件,若找到,则将绝对路径输出到控制台上
     * 注意:不区分大小写
     * @param srcPath   搜索的目录
     * @param filename  目标文件的文件名
     */
    public static long findFile(String srcPath, String filename) {
        long sta = System.currentTimeMillis();
        Path start = Paths.get(srcPath);

        try {
            Files.walkFileTree(start, new SimpleFileVisitor<Path>() {
                @Override
                public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                    if (file.getFileName().toString().equalsIgnoreCase(filename)) {
                        System.out.println(file.toAbsolutePath()); //会自动调用toString()方法
                        //return FileVisitResult.TERMINATE;//这里不建议写这一行代码,因为目录下可能不止有一个同名文件(在不同子目录)
                    }
                    return FileVisitResult.CONTINUE;
                }
            });
        } catch (IOException e) {
            e.printStackTrace();
        }

        long end = System.currentTimeMillis();
        return (end - sta);
    }
}

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章