Java:如何优化读取/更新/写入许多小文件的内存占用



我需要改进一个开源工具(Releng)(与JDK 1.5兼容),它可以更新源文件中的版权头。(例如版权2000,2011)。

它读取文件并插入更新的修订日期(例如2014)。

目前它占用了太多的内存,以至于性能慢得像爬行一样。我需要重写文件解析器,以便它使用更少的内存/运行更快。

我已经写了一个基本的文件解析器(下面),读取目录(项目/文件)中的所有文件。然后,它增加文件中找到的前四位数字,并打印运行时信息。

[编辑]在小范围内,当前结果执行25个垃圾收集,垃圾收集花费12毫秒。在大规模情况下,我的内存开销非常大,以致于GC影响了性能。

Runs     Time(ms) avrg(ms)  GC_count   GC_time
200      4096     20        25         12
200      4158     20        25         12
200      4072     20        25         12
200      4169     20        25         13

是否有可能重用文件或字符串对象(和其他对象??)来减少垃圾收集计数?

优化指南建议重用对象。我考虑过使用Stringbuilder而不是字符串。但据我所知,它只有在你做大量的连接时才有用。在这种情况下,哪一点没有做到?我也不知道如何重用下面代码中的任何其他对象(例如文件?)?

我如何在这种情况下重用对象(或优化下面的代码)?

欢迎任何想法/建议。

import java.io.File;
import java.io.IOException;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;

public class Test {
    //Use Bash script to create 2000 files, each having a 4 digit number.
     /*
      #!/bin/sh
      rm files/test*
      for i in {1..2000}
      do
      echo "2000" > files/test$i
      done
     */
    /*
     * Example output:
     * runs: 200
     * Run time: 4822 average: 24
     * Gc runs: Total Garbage Collections: 28
     * Total Garbage Collection Time (ms): 17
     */
    private static String filesPath = System.getProperty("user.dir") + "/src/files";
    public static void main(String args[]) {
        final File folder = new File(filesPath);
        ArrayList<String> paths = listFilesForFolder(folder);
        if (paths == null) {
            System.out.println("no files found");
            return;
        }

        long start = System.currentTimeMillis();
        // ..
        // your code
        int runs = 200;
        System.out.println("Run: ");
        for (int i = 1; i <= runs; i++) {
            System.out.print(" " + i);
            updateFiles(paths);
        }
        System.out.println("");
        // ..
        long end = System.currentTimeMillis();
        long runtime = end - start;
        System.out.println("Runs     Time     avrg      GC_count   GC_time");
        System.out.println(runs + "      " + Long.toString(runtime) + "     " + (runtime / runs) + "       " + printGCStats());
    }
    private static ArrayList<String> listFilesForFolder(final File folder) {
        ArrayList<String> paths = new ArrayList<>();
        for (final File fileEntry : folder.listFiles()) {
            if (fileEntry.isDirectory()) {
                listFilesForFolder(fileEntry);
            } else {
                paths.add(filesPath + "/" + fileEntry.getName());
            }
        }
        if (paths.size() == 0) {
            return null;
        } else {
            return paths;
        }
    }
    private static void updateFiles(final ArrayList<String> paths) {
        for (String path : paths) {
            try {
                String content = readFile(path, StandardCharsets.UTF_8);
                int year = Integer.parseInt(content.substring(0, 4));
                year++;
                Files.write(Paths.get(path), Integer.toString(year).getBytes(),
                        StandardOpenOption.CREATE);
            } catch (IOException e) {
                System.out.println("Failed to read: " + path);
            }
        }
    }
    static String readFile(String path, Charset encoding) throws IOException {
        byte[] encoded = Files.readAllBytes(Paths.get(path)); // closes file.
        return new String(encoded, encoding);
    }
    //PROFILING HELPER
    public static String printGCStats() {
        long totalGarbageCollections = 0;
        long garbageCollectionTime = 0;
        for (GarbageCollectorMXBean gc : ManagementFactory.getGarbageCollectorMXBeans()) {
            long count = gc.getCollectionCount();
            if (count >= 0) {
                totalGarbageCollections += count;
            }
            long time = gc.getCollectionTime();
            if (time >= 0) {
                garbageCollectionTime += time;
            }
        }
        return " " + totalGarbageCollections + "         " + garbageCollectionTime;
    }
}

最后,上面的代码实际上工作得很好。

我发现在生产代码中,代码没有关闭文件缓冲区,这会导致内存泄漏,从而导致大量文件的性能问题。

修复后,它可以很好地伸缩。

最新更新