【发布时间】:2014-08-09 00:23:57
【问题描述】:
我需要改进一个开源工具 (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
是否可以重复使用 File 或 String 对象(以及其他对象??)来减少垃圾回收次数?
优化指南建议重用对象。 我考虑过使用 Stringbuilder 而不是 Strings。但据我所知,它只有在你进行大量连接时才有用。在这种情况下哪个没有完成? 我也不知道如何在下面的代码中重用任何其他对象(例如文件?)?
如何在这种情况下重用对象(或优化下面的代码)?
欢迎任何想法/建议。
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;
}
}
【问题讨论】:
-
没有必要为此将每个文件完全读入内存。您应该探索使用 java.io.FileInputStream#getChannel 进行编辑。这将避免您看到的内存压力。
-
一个无用的旁注:用
null替换一个空列表是一种可怕的反模式,这会导致更多的代码或for (String path : paths)中的NPE。顺便说一句,您的数据看起来像是在 GC 中花费了 4000 毫秒中的 12 毫秒,那么为什么要关心呢?但是,当使用字节数组而不是字符串时,您可以节省一些开销。 -
它在小范围内工作正常。一旦我达到 20k 或 100k 个文件,GC 时间就会降低性能。谢谢你的字节数组提示,我会试试的。感谢您提供 NPE 提示,我会修复。
标签: java optimization file-io garbage-collection