我发现我的应用程序中存在一个瓶颈,随着我文件中的数据增长而不断增长(参见下面的VisualVM截图)。
以下是getFileContentsAsList
代码。如何才能使性能更好?我已经阅读了几篇有关高效文件I / O的帖子,有些人建议Scanner
作为一种有效读取文件的方法。我也尝试了Apache Commons readFileToString
,但是它也没有快速运行。
导致应用程序运行速度变慢的数据文件是8 KB ......这对我来说似乎不算太大。
如果这似乎是一个更好的路线,我可以转换为像Apache Derby这样的嵌入式数据库。最终寻找有助于应用程序运行更快的内容(这是一个Java 1.7 Swing应用程序BTW)。
以下是getFileContentsAsList
的代码:
public static List<String> getFileContentsAsList(String filePath) throws IOException {
if (ReceiptPrinterStringUtils.isNullOrEmpty(filePath)) throw new IllegalArgumentException("File path must not be null or empty");
Scanner s = null;
List<String> records = new ArrayList<String>();
try {
s = new Scanner(new BufferedReader(new FileReader(filePath)));
s.useDelimiter(FileDelimiters.RECORD);
while (s.hasNext()) {
records.add(s.next());
}
} finally {
if (s != null) {
s.close();
}
}
return records;
}
答案 0 :(得分:1)
必要时,ArrayList的大小乘以1.5。这是O(log(N))。 (在Vector中使用了双倍。)如果我试图加速它,我肯定会在这里使用O(1)LinkedList和BufferedReader.readLine()而不是Scanner。很难相信阅读一个8k文件的时间是一个令人担忧的问题。你可以在一秒钟内阅读数百万行。
答案 1 :(得分:1)
所以,如果你做了很多的话,file.io会变得非常昂贵...正如我的屏幕截图和包含file.io调用的原始代码getFileContentsAsList
所见,调用得相当多(18.425次) VisualVM是一个真正的工具宝石,可以指出这些瓶颈!
在考虑各种提高性能的方法之后,我突然意识到最好的方法是尽可能少地进行file.io调用。因此,我决定使用私有静态变量来保存文件内容,并且只在静态初始化程序中执行file.io以及写入文件时。由于我的应用程序(幸运的是)没有做过多的写作(但过度阅读),这使得性能更好的应用程序。
以下是包含getFileContentsAsList
方法的整个类的源代码。我拍摄了该方法的快照,它现在以57.2毫秒(从3116毫秒开始)运行。此外,这是我运行时间最长的方法,现在是我运行时间最长的第四种方法。前5个最长的运行方法现在总共运行498.8毫秒,而原始屏幕截图中运行的总共3812.9毫秒。这是一个百分比减少约85%
[100 *(498.8 - 3812.9)/ 3812.9]。
package com.mbc.receiptprinter.util;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import org.apache.commons.io.FileUtils;
import com.mbc.receiptprinter.constant.FileDelimiters;
import com.mbc.receiptprinter.constant.FilePaths;
/*
* Various File utility functions. This class uses the Apache Commons FileUtils class.
*/
public class ReceiptPrinterFileUtils {
private static Map<String, String> fileContents = new HashMap<String, String>();
private static Map<String, Boolean> fileHasBeenUpdated = new HashMap<String, Boolean>();
static {
for (FilePaths fp : FilePaths.values()) {
File f = new File(fp.getPath());
try {
FileUtils.touch(f);
fileHasBeenUpdated.put(fp.getPath(), false);
fileContents.put(fp.getPath(), FileUtils.readFileToString(f));
} catch (IOException e) {
ReceiptPrinterLogger.logMessage(ReceiptPrinterFileUtils.class,
Level.SEVERE,
"IOException while performing FileUtils.touch in static block of ReceiptPrinterFileUtils", e);
}
}
}
public static String getFileContents(String filePath) throws IOException {
if (ReceiptPrinterStringUtils.isNullOrEmpty(filePath)) throw new IllegalArgumentException("File path must not be null or empty");
File f = new File(filePath);
if (fileHasBeenUpdated.get(filePath)) {
fileContents.put(filePath, FileUtils.readFileToString(f));
fileHasBeenUpdated.put(filePath, false);
}
return fileContents.get(filePath);
}
public static List<String> convertFileContentsToList(String fileContents) {
List<String> records = new ArrayList<String>();
if (fileContents.contains(FileDelimiters.RECORD)) {
records = Arrays.asList(fileContents.split(FileDelimiters.RECORD));
}
return records;
}
public static void writeStringToFile(String filePath, String data) throws IOException {
fileHasBeenUpdated.put(filePath, true);
FileUtils.writeStringToFile(new File(filePath), data);
}
public static void writeStringToFile(String filePath, String data, boolean append) throws IOException {
fileHasBeenUpdated.put(filePath, true);
FileUtils.writeStringToFile(new File(filePath), data, append);
}
}
答案 2 :(得分:0)
ArrayList
在阅读和写作方面表现良好,如果长度不经常变化。在您的应用程序中,长度经常变化(大小加倍,当它已满并添加了一个元素时),并且您的应用程序需要将数组复制到一个新的更长的数组中。
您可以使用LinkedList
,其中附加了新元素,不需要复制操作。
List<String> records = new LinkedList<String>();
或者您可以使用近似的完成字数初始化ArrayList
。这将减少复制操作的数量。
List<String> records = new ArrayList<String>(2000);