2

I've found a bottleneck in my app that keeps growing as data in my files grow (see attached screenshot of VisualVM below).

Below is the getFileContentsAsList code. How can this be made better performance-wise? I've read several posts on efficient File I/O and some have suggested Scanner as a way to efficiently read from a file. I've also tried Apache Commons readFileToString but that's not running fast as well.

The data file that's causing the app to run slower is 8 KB...that doesn't seem too big to me.

I could convert to an embedded database like Apache Derby if that seems like a better route. Ultimately looking for what will help the application run faster (It's a Java 1.7 Swing app BTW).

Here's the code for 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

4

3 に答える 3

1

The size of an ArrayList is multiplied by 1.5 when necessary. This is O(log(N)). (Doubling was used in Vector.) I would certainly use an O(1) LinkedList here, and BufferedReader.readLine() rather than a Scanner if I was trying to speed it up. It's hard to believe that the time to read one 8k file is seriously a concern. You can read millions of lines in a second.

于 2013-09-06T14:27:30.123 に答える
1

したがって、file.io は、何度も実行すると非常に高価になります...私のスクリーン ショットと元のコードに見られるように、getFileContentsAsListfile.io 呼び出しを含む はかなりの回数 (18.425 回) 呼び出されます。VisualVM は、このようなボトルネックを指摘するツールの真の逸品です!

パフォーマンスを改善するためのさまざまな方法を検討した結果、file.io の呼び出しをできるだけ少なくすることがおそらく最善の方法であることに気づきました。そこで、プライベートな静的変数を使用してファイルの内容を保持し、静的イニシャライザーでファイルが書き込まれるときにのみ file.io を実行することにしました。私のアプリケーションは (幸いなことに) 過剰な書き込み (ただし過剰な読み取り) を行っていないため、アプリケーションのパフォーマンスが大幅に向上します。

getFileContentsAsListメソッドを含むクラス全体のソースは次のとおりです。そのメソッドのスナップショットを撮ったところ、現在は 57.2 ミリ秒 (3116 ミリ秒から短縮) で実行されています。また、これは私の最長実行方法であり、現在では 4 番目に実行時間の長い方法です。合計 3812.9 ミリ秒実行された元のスクリーンショットのメソッドとは対照的に、実行時間の長い上位 5 つのメソッドは合計 498.8 ミリ秒実行されます。これは、約 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 に答える