在Java中检查文件是否存在的最快方法

5

目前我的任务是使用Java创建一个工具,可以检查链接是否正确。该链接由Jericho HTML解析器提供,我的工作只是检查文件是否存在/链接是否正确。这部分已经完成,难点在于优化代码,因为我的程序运行速度相对较慢,每次运行需要65毫秒。

public static String checkRelativeURL(String originalFileLoc, String relativeLoc){
        StringBuilder sb = new StringBuilder();
        String absolute = Common.relativeToAbsolute(originalFileLoc, relativeLoc); //built in function to replace the link from relative link to absolute path
        sb.append(absolute);
        sb.append("\t");
        try {
            Path path = Paths.get(absolute);
            sb.append(Files.exists(path));
        }catch (InvalidPathException | NullPointerException ex) {
            sb.append(false);
        }
        sb.append("\t");
        return sb.toString();
    }

在这行代码上执行时间为65毫秒

Path path = Paths.get(absolute);
sb.append(Files.exists(path));

我曾尝试使用过

标签

File file = new File(absolute);
sb.append(file.isFile());

它仍然需要大约65~100毫秒的时间。

所以,有没有其他更快的方法来检查文件是否存在?

由于我要处理超过70k个HTML文件,并且每毫秒都很重要,谢谢 :(

编辑:

我尝试将所有文件列入某个列表中,但这并没有真正帮助,因为列出所有文件需要超过20分钟....

我用于列出所有文件的代码如下:

static public void listFiles2(String filepath){
        Path path = Paths.get(filepath);
        File file = null;
        String pathString = new String();
        try {
            if(path.toFile().isDirectory()){
                DirectoryStream<Path> stream = Files.newDirectoryStream(path);
                for(Path entry : stream){
                    file = entry.toFile();
                    pathString = entry.toString();
                    if(file.isDirectory()){
                        listFiles2(pathString);
                    }
                    if (file.isFile()){
                        filesInProject.add(pathString);
                        System.out.println(pathString);
                    }
                }
                stream.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

4
由于您正在“处理超过70k个HTML文件”,您是否可以加载整个目录树并进行检查? - phuclv
2
如果你再做一次,第二次运行是否仍需要65毫秒? - Andreas
1
如果抛出异常,程序将需要更长的时间来执行。 - Scary Wombat
1
如果抛出异常,它会花费稍微更长的时间,但与系统调用和检查目录中文件是否存在的成本相比,这是微不足道的。 - user207421
1
@EiZenHoweLL:那么您可以尝试使用多线程。 - Minh
显示剩余12条评论
1个回答

2
如果您事先知道目标操作系统设置(通常是这种情况),最快的方法是通过调用进程,例如使用Runtime.exec,通过shell列出许多文件。
在Windows上,您可以使用
dir /s /b   

在Linux上
ls -R -1

您可以检查操作系统是什么,并使用适当的命令(如果不支持,则出错或退回到目录流)。
如果您希望简单,并且不需要报告进度,则可以避免处理进程IO,并将列表存储到临时文件中,例如ls -R -1 > /tmp/filelist.txt。或者,您可以直接从进程输出中读取。使用足够大的缓冲流、读取器或类似物进行读取。
在SSD上,它会在眨眼间完成,在现代HDD上则需要几秒钟(这种方法对于50万个文件来说不是问题)。
一旦您拥有了列表,就可以根据最大文件数和内存需求以不同的方式处理它。如果要求宽松,例如桌面程序,您可以使用非常简单的代码,例如将完整的文件列表预加载到HashSet中,并在需要时检查存在性。通过删除公共根来缩短路径将需要更少的内存。您还可以通过仅保留文件名哈希而不是全名(通常根减少将可能更多地减少)来减少内存。

如果你愿意,你可以进一步优化它,现在问题只是检查一个字符串是否存在于存储在内存或文件中的字符串列表中的问题,这有许多众所周知的最优解。

以下是针对Windows的非常简略的示例。它在HDD(而不是SSD)驱动器根目录上执行dir命令,读取列表并基准测试(好吧,有点类似)字符串集和md5集方法的时间和内存:

public static void main(String args[]) throws Exception {

    final Runtime rt = Runtime.getRuntime();
    System.out.println("mem " + (rt.totalMemory() - rt.freeMemory())
            / (1024 * 1024) + " Mb");

    long time = System.currentTimeMillis();
    // windows command: cd to t:\ and run recursive dir
    Process p = rt.exec("cmd /c \"t: & dir /s /b   > filelist.txt\"");
    if (p.waitFor() != 0)
        throw new Exception("command has failed");
    System.out.println("done executing shell, took "
            + (System.currentTimeMillis() - time) + "ms");
    System.out.println();

    File f = new File("T:/filelist.txt");

    // load into hash set
    time = System.currentTimeMillis();
    Set<String> fileNames = new HashSet<String>(500000);
    try (BufferedReader reader = new BufferedReader(new InputStreamReader(
            new FileInputStream(f), StandardCharsets.UTF_8),
            50 * 1024 * 1024)) {
        for (String line = reader.readLine(); line != null; line = reader
                .readLine()) {
            fileNames.add(line);
        }
    }
    System.out.println(fileNames.size() + " file names loaded took "
            + (System.currentTimeMillis() - time) + "ms");
    System.gc();
    System.out.println("mem " + (rt.totalMemory() - rt.freeMemory())
            / (1024 * 1024) + " Mb");

    time = System.currentTimeMillis();
    // check files
    for (int i = 0; i < 70_000; i++) {
        StringBuilder fileToCheck = new StringBuilder();
        while (fileToCheck.length() < 256)
            fileToCheck.append(Double.toString(Math.random()));
        if (fileNames.contains(fileToCheck))
            System.out.println("to prevent optimization, never executes");
    }
    System.out.println();
    System.out.println("hash set 70K checks took "
            + (System.currentTimeMillis() - time) + "ms");
    System.gc();
    System.out.println("mem " + (rt.totalMemory() - rt.freeMemory())
            / (1024 * 1024) + " Mb");

    // Test memory/performance with MD5 hash set approach instead of full
    // names
    time = System.currentTimeMillis();
    Set<String> nameHashes = new HashSet<String>(50000);
    MessageDigest md5 = MessageDigest.getInstance("MD5");
    for (String name : fileNames) {
        String nameMd5 = new String(md5.digest(name
                .getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
        nameHashes.add(nameMd5);
    }
    System.out.println();
    System.out.println(fileNames.size() + " md5 hashes created, took "
            + (System.currentTimeMillis() - time) + "ms");
    fileNames.clear();
    fileNames = null;
    System.gc();
    Thread.sleep(100);
    System.gc();
    System.out.println("mem " + (rt.totalMemory() - rt.freeMemory())
            / (1024 * 1024) + " Mb");

    time = System.currentTimeMillis();
    // check files
    for (int i = 0; i < 70_000; i++) {
        StringBuilder fileToCheck = new StringBuilder();
        while (fileToCheck.length() < 256)
            fileToCheck.append(Double.toString(Math.random()));
        String md5ToCheck = new String(md5.digest(fileToCheck.toString()
                .getBytes(StandardCharsets.UTF_8)), StandardCharsets.UTF_8);
        if (nameHashes.contains(md5ToCheck))
            System.out.println("to prevent optimization, never executes");
    }
    System.out.println("md5 hash set 70K checks took "
            + (System.currentTimeMillis() - time) + "ms");
    System.gc();
    System.out.println("mem " + (rt.totalMemory() - rt.freeMemory())
            / (1024 * 1024) + " Mb");
}

输出:

mem 3 Mb
done executing shell, took 5686ms

403108 file names loaded took 382ms
mem 117 Mb

hash set 70K checks took 283ms
mem 117 Mb

403108 md5 hashes created, took 486ms
mem 52 Mb
md5 hash set 70K checks took 366ms
mem 48 Mb

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接