从目录中挑选n个随机文件

时间:2018-01-11 08:55:59

标签: java directory

我有一个包含超过10万个文件夹的文件夹。如果我使用listFiles(),那么它需要花费很多时间,因为它返回文件夹中的所有条目。我想要的是,我将处理的文件夹中的随机条目,并将移动到不同的位置。

2 个答案:

答案 0 :(得分:0)

我很想知道listFiles()会带来什么样的表现,所以我进行了测试。有100,000名儿童,我看到延迟0.051秒。你可能会看到这个速度保持相对较好(我发现的任何内容都不会暗示Java中的任何实质性增长;任何快速降级都将在本地出现)。虽然这种延迟相对较小,但我查看了 listFiles的工作方式,以确定是否可以进行任何潜在的改进。

改进1

第一种解决方案是使用File.list()而不是File.listFiles()。如果查看listFiles()方法的代码,可以看到Java如何找到文件夹的子代。

    public File[] listFiles() {
        String[] ss = list();
        if (ss == null) return null;
        int n = ss.length;
        File[] fs = new File[n];
        for (int i = 0; i < n; i++) {
            fs[i] = new File(ss[i], this);
        }
        return fs;
    }

listFiles()方法获取子项名称的数组,即字符串,并为每个子项创建一个File对象。 File对象的迭代和实例化将为您的任务创建不必要的无意中;如果忽略从String[]File[]的转换,您只需要一个较便宜的文件。幸运的是,list();方法是公共的,因此您可以使用此方法来获得轻微的性能提升。

粗略测试表明,这会将时间缩短约25%(搜索包含100,000名儿童的文件夹时)。

改进2

下一个合乎逻辑的步骤是查看list()并查看它的作用。事情变得有点粘:

    public String[] list() {
        SecurityManager security = System.getSecurityManager();
        if (security != null) {
            security.checkRead(path);
        }
        if (isInvalid()) {
            return null;
        }
        return fs.list(this);
    }

假设您可以跳过安全性和验证检查,您可以按fs.list(this);跟踪它所需的位置。接下来,你会看到一个兔子洞:

  1. fs.list(this)
  2. DefaultFileSystem.getFileSystem().list(File f)
  3. new WinNTFileSystem.list(File f)
  4. 这是你停下来的地方。 .list(文件f)is declared native`意味着它已使用JNI在本机代码中实现。一路下线访问受到限制意味着

    如果您想尽可能深入,可以使用反射来访问这些方法。我相信你可以使用的最低级别是本机方法WinNTFileSystem.file(File f),但我强烈建议不要这样做。

    /* Setup */
    
    // Get FileSystem from File class
    Field fieldFileSystem = File.class.getDeclaredField("fs");
    fieldFileSystem.setAccessible(true);
    Object fs = fieldFileSystem.get(null);
    
    // Get WinNTFileSystem class
    Class<?> classWinNTFileSystem = Class.forName("java.io.WinNTFileSystem");
    
    // Get native `list` method from WinNTFileSystem class
    Method methodList = classWinNTFileSystem .getMethod("list", File.class);
    methodList.setAccessible(true);
    
    
    /* Each time you want to invoke the method */
    String[] files = (String[]) methodList.invoke(fs, root);
    

    此次性能升级差异很大。有时我看到比使用前一种方法略好,而其他人看到了超过50%的显着改善,尽管我对这种表现持怀疑态度。使用此方法,您应该看到至少略微增加File.list()(假设您只创建一次Method对象并通过代码重用它。)

    注意

    使用密钥不是文件名,您不会发现任何显着的性能提升超出我的显示。为了索引文件,你需要,你需要列表,因为没有本地实现&#34; get child at index n&#34;。您可以使用密钥或索引作为文件名本身,只需使用new File(root, "12353");创建一个新的File对象。

答案 1 :(得分:0)

实际上java具有DirectoryStream接口可用于迭代目录而无需将其内容预加载到内存中。下面提到了相同的示例代码。

        Path logFolder = Paths.get(windowsClientParentFolder);
        try (DirectoryStream<Path> stream = Files.newDirectoryStream(logFolder)) {
            for (Path entry : stream) {
                String folderName = entry.getFileName().toString();
                //process the folder
            }
        } catch (IOException ex) {
            System.out.println("Exception occurred while reading folders.");
        }