通过VisualVM找到文件I / O瓶颈

时间:2013-09-06 12:34:50

标签: java swing jvisualvm

我发现我的应用程序中存在一个瓶颈,随着我文件中的数据增长而不断增长(参见下面的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;
}

Application CPU Hot Spots

3 个答案:

答案 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);