Java遍历文件夹

Java遍历文件夹


简单写了一个打印目录下所有文件以及文件夹的代码,类比于树的遍历,写了深度优先和广度优先的遍历。并且还写了个JDK1.7接口提供的的版本。

深度广度遍历都用到的双端队列(ArrayDeque),不了解双端队列的话可以去学习一下。我简单说一下,双端队列既可以用作队列,也可以用作栈,而且效率比较高。


1.1 深度优先遍历

使用栈存储File对象,出栈后,输出file信息,若是文件夹,就将文件夹下的子文件以及子文件夹全部压入栈,栈空则遍历完成。

代码

    //深度优先遍历 Depth First Search
    //使用栈
    static void dfs(File file) {
        if (file == null || !file.exists()) {
            return;
        }

        final Deque<File> stack = new ArrayDeque<>();
        stack.push(file);

//        stack.pop();
//        stack.pollFirst();
        while ((file = stack.pollFirst()) != null) {
            System.out.println(file);
/*
            if (!file.exists()) {
                //文件存在,但系统拒绝访问
            }
*/
            if (file.isDirectory()) {
                File[] files = file.listFiles();
                if (files == null) {
                    System.err.println(file + "\t文件夹访问受限,跳过");
                    continue;
                }
                for (int filesLength = files.length, i = filesLength - 1; i >= 0; i--) {
                    stack.push(files[i]);
                }
            }
        }

    }

此处注意:

  1. 使用file.listFiles()得到的子文件以及文件夹不一定能访问。若调用file.exists()返回为false则为无法访问。
  2. file为文件夹,则file.listFiles()应返回File数组。此处做判断是为了程序的鲁棒性。

1.2 广度优先遍历

使用队列储存File对象,利用队列先入先出的特性,遍历文件夹。大学时,我就用C语言写了一个层次遍历二叉树的实现,只是当时遍历出的结果没有分层。这次在此基础上,使用list记录了每一层的文件及文件夹个数,并输出。

代码

    //广度优先遍历 Breath First Search
    //使用队列
    static List<Integer> bfs(File file) {
        if (file == null || !file.exists()) {
            return Collections.emptyList();
        }

        final Queue<File> queue = new ArrayDeque<>();
        queue.offer(file);

        //每一层包含元素的个数
        final List<Integer> list = new ArrayList<>();

        //i 循环次数
        //j 层数
        //n 该层包含元素个数
        int i = 0, j = 0, n = 0;
        list.add(1);

        while ((file = queue.poll()) != null) {
            System.out.println(file);
            if (file.isDirectory()) {
                File[] files = file.listFiles();
                if (files == null) {
                    System.err.println(file + "\t文件夹访问受限,跳过");
                    continue;
                }
                n += files.length;
                Arrays.stream(files).forEach(queue::offer);
            }

            i++;
            if (i == list.get(j)) {
                //遍历一层结束
                list.add(n);
                i = 0;
                j++;
                n = 0;
            }
        }

1.3 Java提供 Files.walkFileTree()

这个就没什么好说的了,属于深度优先遍历。大量文件及文件夹时,使用此方法遍历效果十分好。

代码

    //nio
    static void nio(Path path) throws IOException {

        Files.walkFileTree(path, new FileVisitor<Path>() {
            //访问文件夹之前调用此方法
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                System.out.println(dir);
//                System.out.println(attrs);
                return FileVisitResult.CONTINUE;
            }

            //访问文件时调用此方法
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                System.out.println(file);
//                System.out.println(attrs);
                return FileVisitResult.CONTINUE;
            }

            //访问文件失败时调用此方法
            @Override
            public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
                System.err.print(file + "\t文件访问受限: ");
                if (exc == null) {
                    System.err.println("未知错误");
                } else {
                    System.err.println(exc);
//                    throw exc;
                }
                return FileVisitResult.CONTINUE;

            }

            //访问文件夹之后调用此方法
            @Override
            public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
//                System.out.println(dir);
                if (exc != null) {
                    System.err.println(dir + "\t文件夹访问受限,跳过" + exc);
//                    throw exc;
                }
                return FileVisitResult.CONTINUE;
            }
        });
    }


删除文件夹的想法


然后我又想着,既然遍历都做完了,要不再试试删除。三个方法。

  1. 使用Java提供 Files.walkFileTree()。这个是真的便利,只需要在访问文件时和文件夹后调用删除方法即可。
  2. 使用递归,这个也是相当简单粗暴的方法了。遍历文件夹就可以用递归,代码也时相当简单就不做了。
  3. 和深度优先遍历相似,删除文件之后,再删除文件夹。

2. 删除文件夹

深度优先遍历,先上代码:

    static void delete(File file) {
        if (file == null || !file.exists()) {
            return;
        }

        Deque<File> stack = new ArrayDeque<>();
        stack.push(file);

        while (!stack.isEmpty()) {
            file = stack.peek();
            if (!file.exists()) {
                System.out.println(file + " 文件无法访问");
//                stack.poll();
//                continue;
            }
            if (file.isDirectory()) {
                File[] files = file.listFiles();
                if (files != null && files.length != 0) {
                    //为了代码的简洁,选择全部入栈,不做文件、文件夹的判断
                    for (int filesLength = files.length, i = filesLength - 1; i >= 0; i--) {
                        stack.push(files[i]);
                    }
                    continue;
                }
            }
            //file是文件,或空文件夹时,直接删除
            if (file.delete())
                System.out.println(file + " 删除成功");
            else {
                System.out.println(file + " 删除失败");
                return;//重点标记
            }
            stack.poll();

        }
    }

去掉代码中重点标记的哪一行return,实际运行此代码会发现一个问题,若有一个文件无法成功删除,便会陷入出栈之后再入栈的死循环当中。

即便时在重点标记上加上一个“删除失败后,出栈File对象,到下个要删除的文件夹”的逻辑后,也不能排除两个文件中文件都无法删除而再度陷入循环的困境中。

此时就需要改变入栈的元素,记录下子文件及文件夹的数量,来判断出栈了。至于这个坑,就不一行会埋上了。


最后附上全部代码(包含import)

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

public class FolderTraversal {
//    private static final Deque<File> deque = new ArrayDeque<>();

    //深度优先遍历 Depth First Search
    //使用栈
    static void dfs(File file) {
        if (file == null || !file.exists()) {
            return;
        }

        final Deque<File> stack = new ArrayDeque<>();
        stack.push(file);

//        stack.pop();
//        stack.pollFirst();
        while ((file = stack.pollFirst()) != null) {
            System.out.println(file);
/*
            if (!file.exists()) {
                //文件存在,但系统拒绝访问
            }
*/
            if (file.isDirectory()) {
                File[] files = file.listFiles();
                if (files == null) {
                    System.err.println(file + "\t文件夹访问受限,跳过");
                    continue;
                }
                for (int filesLength = files.length, i = filesLength - 1; i >= 0; i--) {
                    stack.push(files[i]);
                }
            }
        }

    }

    static void delete(File file) {
        if (file == null || !file.exists()) {
            return;
        }

        Deque<File> stack = new ArrayDeque<>();
        stack.push(file);

        while (!stack.isEmpty()) {
            file = stack.peek();
            if (!file.exists()) {
                System.out.println(file + " 文件无法访问");
//                stack.poll();
//                continue;
            }
            if (file.isDirectory()) {
                File[] files = file.listFiles();
                if (files != null && files.length != 0) {
                    //为了代码的简洁,选择全部入栈,不做文件、文件夹的判断
                    for (int filesLength = files.length, i = filesLength - 1; i >= 0; i--) {
                        stack.push(files[i]);
                    }
                    continue;
                }
            }
            //file是文件,或空文件夹时,直接删除
            if (file.delete())
                System.out.println(file + " 删除成功");
            else {
                System.out.println(file + " 删除失败");
                return;
            }
            stack.poll();

        }
    }

    //广度优先遍历 Breath First Search
    //使用队列
    static List<Integer> bfs(File file) {
        if (file == null || !file.exists()) {
            return Collections.emptyList();
        }

        final Queue<File> queue = new ArrayDeque<>();
        queue.offer(file);

        //每一层包含元素的个数
        final List<Integer> list = new ArrayList<>();

        //i 循环次数
        //j 层数
        //n 该层包含元素个数
        int i = 0, j = 0, n = 0;
        list.add(1);

        while ((file = queue.poll()) != null) {
            System.out.println(file);
            if (file.isDirectory()) {
                File[] files = file.listFiles();
                if (files == null) {
                    System.err.println(file + "\t文件夹访问受限,跳过");
                    continue;
                }
                n += files.length;
                Arrays.stream(files).forEach(queue::offer);
            }

            i++;
            if (i == list.get(j)) {
                //遍历一层结束
                list.add(n);
                i = 0;
                j++;
                n = 0;
            }

        }

/*
        queue.add(file);
        queue.remove();
*/
        System.out.println(list);
        return list;
    }

    //nio
    static void nio(Path path) throws IOException {

        Files.walkFileTree(path, new FileVisitor<Path>() {
            //访问文件夹之前调用此方法
            @Override
            public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
                System.out.println(dir);
//                System.out.println(attrs);
                return FileVisitResult.CONTINUE;
            }

            //访问文件时调用此方法
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                System.out.println(file);
//                System.out.println(attrs);
                return FileVisitResult.CONTINUE;
            }

            //访问文件失败时调用此方法
            @Override
            public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
                System.err.print(file + "\t文件访问受限: ");
                if (exc == null) {
                    System.err.println("未知错误");
                } else {
                    System.err.println(exc);
//                    throw exc;
                }
                return FileVisitResult.CONTINUE;

            }

            //访问文件夹之后调用此方法
            @Override
            public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
//                System.out.println(dir);
                if (exc != null) {
                    System.err.println(dir + "\t文件夹访问受限,跳过" + exc);
//                    throw exc;
                }
                return FileVisitResult.CONTINUE;
            }
        });
    }

    public static void main(String[] args) throws IOException {
//        long start = System.currentTimeMillis();

        //java中,链接是一个文件 xxx.lnk
        //  isFile()         判断为true
        //  isDirectory()    判断为false
        File file = new File("files");
//        dfs(file);//103322 59316
//        bfs(file);//98156 67997

//        Path path = Paths.get("");
//        nio(path);//30058  17460

//        long end = System.currentTimeMillis();
//        System.out.println(end - start);
    }
}
posted @ 2022-10-02 22:43  康舒服冰红茶  阅读(2177)  评论(0编辑  收藏  举报