2

我在我的应用程序中发现了一个瓶颈,该瓶颈会随着文件中数据的增长而不断增长(请参阅下面附上的 VisualVM 屏幕截图)。

下面是getFileContentsAsList代码。如何在性能方面做得更好?我已经阅读了几篇关于高效文件 I/O 的文章,其中一些建议Scanner作为一种有效地从文件中读取的方法。我也尝试过 Apache Commons readFileToString,但运行速度也不快。

导致应用程序运行速度变慢的数据文件是 8 KB……这对我来说似乎并不算大。

如果这似乎是一条更好的路线,我可以转换为像 Apache Derby 这样的嵌入式数据库。最终寻找什么可以帮助应用程序运行得更快(顺便说一句,这是一个 Java 1.7 Swing 应用程序)。

这是代码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;
}

应用 CPU 热点

4

3 回答 3

1

ArrayList 的大小在必要时乘以 1.5。这是 O(log(N))。(在 Vector 中使用了加倍。)如果我想加快速度,我肯定会在这里使用 O(1) LinkedList 和 BufferedReader.readLine() 而不是 Scanner。很难相信阅读一个 8k 文件的时间是一个严重的问题。您可以在一秒钟内阅读数百万行。

于 2013-09-06T14:27:30.123 回答
1

所以,如果你经常这样做,file.io 会变得非常昂贵......如我的屏幕截图和原始代码getFileContentsAsList中所见,包含 file.io 调用的,被调用了很多(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);
    }
}
于 2013-09-07T03:15:07.853 回答
0

ArrayLists 在阅读和写作方面都有很好的表现,如果 lenth 不经常变化。在您的应用程序中,长度经常变化(大小加倍,当它已满并添加一个元素时)并且您的应用程序需要将您的数组复制到一个新的更长的数组中。

您可以使用 a LinkedList,其中添加了新元素并且不需要复制操作。 List<String> records = new LinkedList<String>();

ArrayList或者您可以使用近似完成的字数来初始化。这将减少复制操作的数量。 List<String> records = new ArrayList<String>(2000);

于 2013-09-06T13:05:56.693 回答