【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);
    }
}

 

 

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