【问题标题】:Fastest way of reading a CSV file in Java用 Java 读取 CSV 文件的最快方法
【发布时间】:2015-09-13 21:40:56
【问题描述】:

我注意到在读取大文件(在我的例子中是 CSV 文件)时使用java.util.Scanner 非常慢。

我想改变我目前读取文件的方式,以提高性能。以下是我目前所拥有的。请注意,我正在为 Android 开发:

InputStreamReader inputStreamReader;
    try {
        inputStreamReader = new InputStreamReader(context.getAssets().open("MyFile.csv"));
        Scanner inputStream = new Scanner(inputStreamReader);
        inputStream.nextLine(); // Ignores the first line
        while (inputStream.hasNext()) {
            String data = inputStream.nextLine(); // Gets a whole line
            String[] line = data.split(","); // Splits the line up into a string array

            if (line.length > 1) {
                // Do stuff, e.g:
                String value = line[1];
            }
        }
        inputStream.close();
    } catch (IOException e) {
        e.printStackTrace();
    }

使用Traceview,我发现主要的性能问题,具体是:java.util.Scanner.nextLine()java.util.Scanner.hasNext()

我看过其他问题(例如this one),也遇到过一些CSV 阅读器,例如Apache Commons CSV,但他们似乎没有太多关于如何使用它们的信息,而且我不确定它们会快多少。

我也听说过在this one 之类的答案中使用FileReaderBufferedReader,但同样,我不知道改进是否会显着。

我的文件长约 30,000 行,使用我目前拥有的代码(上图),从大约 600 行以下读取值至少需要 1 分钟,所以我没有计时需要多长时间读取 2,000 多行以下的值,但有时,在读取信息时,Android 应用会变得无响应并崩溃。

虽然我可以简单地更改部分代码并亲自查看,但我想知道是否有任何我没有提到的更快的替代方案,或者我是否应该只使用FileReaderBufferedReader。将大文件拆分为较小的文件,并根据我要检索的信息选择读取哪个文件会更快吗?最好,我也想知道为什么最快的方法是最快的(即是什么让它快)。

【问题讨论】:

  • 你可能想阅读thisthis
  • 仅供参考,我有一个 140,000 字的“字典”(实际上只是一个单词列表),它使用 Scanner 处理得非常快。但不是在安卓设备上。我知道您正在考虑的三个选择之间没有太大的性能差异。但我不是专家。
  • 试试 BufferedReader
  • 将读取操作设为Callable并扔到ExecutorService中
  • 使用BufferedReader.,您可以在一两秒内阅读数百万行,而 30,000 行应该几乎无法察觉。当您只阅读行时,没有理由使用Scanner

标签: java android performance csv


【解决方案1】:

uniVocity-parsers 拥有您所发现的最快的 CSV 解析器(比 OpenCSV 快 2 倍,比 Apache Commons CSV 快 3 倍),并具有许多独特的功能。

这里有一个简单的例子说明如何使用它:

CsvParserSettings settings = new CsvParserSettings(); // many options here, have a look at the tutorial

CsvParser parser = new CsvParser(settings);

// parses all rows in one go
List<String[]> allRows = parser.parseAll(new FileReader(new File("your/file.csv")));

为了加快处理速度,您可以选择您感兴趣的列:

parserSettings.selectFields("Column X", "Column A", "Column Y");

通常,您应该能够在 2 秒左右解析 400 万行。使用列选择,速度将提高大约 30%。

如果您使用RowProcessor,它会更快。有许多开箱即用的实现来处理对象、POJOS 等的转换。文档解释了所有可用的功能。它的工作原理是这样的:

// let's get the values of all columns using a column processor
ColumnProcessor rowProcessor = new ColumnProcessor();
parserSettings.setRowProcessor(rowProcessor);

//the parse() method will submit all rows to the row processor
parser.parse(new FileReader(new File("/examples/example.csv")));

//get the result from your row processor:
Map<String, List<String>> columnValues = rowProcessor.getColumnValuesAsMapOfNames();

我们还建立了一个简单的速度比较项目here

【讨论】:

    【解决方案2】:

    您的代码很适合加载大文件。但是,当操作比您预期的要长时,最好在任务中而不是在 UI 线程中执行它,以防止缺乏响应。

    AsyncTask 类有助于做到这一点:

    private class LoadFilesTask extends AsyncTask<String, Integer, Long> {
        protected Long doInBackground(String... str) {
            long lineNumber = 0;
            InputStreamReader inputStreamReader;
            try {
                inputStreamReader = new
                        InputStreamReader(context.getAssets().open(str[0]));
                Scanner inputStream = new Scanner(inputStreamReader);
                inputStream.nextLine(); // Ignores the first line
    
                while (inputStream.hasNext()) {
                    lineNumber++;
                    String data = inputStream.nextLine(); // Gets a whole line
                    String[] line = data.split(","); // Splits the line up into a string array
    
                    if (line.length > 1) {
                        // Do stuff, e.g:
                        String value = line[1];
                    }
                }
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            return lineNumber;
        }
    
        //If you need to show the progress use this method
        protected void onProgressUpdate(Integer... progress) {
            setYourCustomProgressPercent(progress[0]);
        }
    
        //This method is triggered at the end of the process, in your case when the loading has finished
        protected void onPostExecute(Long result) {
            showDialog("File Loaded: " + result + " lines");
        }
    }
    

    ...并执行为:

    new LoadFilesTask().execute("MyFile.csv");
    

    【讨论】:

    • “任务”和“UI 线程”有什么区别,它们是如何工作的?此外,doInBackground 方法似乎返回了一个Long - 我不熟悉使用AsyncTask,所以我也想知道该方法是如何工作的。最后,当您执行context.getAssets().open(str[0]) 时,您是在打开文件中的第 0 行,还是以某种方式引用您正在读取的文件?由于我以前没有使用过 AsyncTask,您能解释一下它是如何工作的以及代码的每个部分的作用吗?谢谢。
    • 另外,我还是使用Scanner,还是改成BufferedReader(在您的示例中)会更好。
    • UI 线程是你的应用程序运行的主线程,通常这是运行用户界面(即视图、布局、用户与应用程序交互的管理)的线程,AsyncTask 是基本上,一个线程在后台运行在 UI 线程之外并与之并行运行,并且没有交互(与 UI 没有那么多),因此它可以在不烦人的用户界面或降低响应性的情况下进行长时间的操作。我写的sn-p只是一个例子,你可以参考这个官方链接看看:developer.android.com/reference/android/os/AsyncTask.html
    • 简单地使用 String.split(",") 手动解析 CSV 是您可以给出的可靠读取 CSV 的最糟糕的建议。如果任何值包含冒号字符,这将中断。为此使用 CSV 解析器。此外,readLine 会导致包含“\n”字符的值出现问题。
    【解决方案3】:

    您应该改用 BufferedReader:

    BufferedReader reader = null;
    try {
        reader = new BufferedReader( new InputStreamReader(context.getAssets().open("MyFile.csv"))) ;
        reader.readLine(); // Ignores the first line
        String data;
        while ((data = reader.readLine()) != null) { // Gets a whole line
            String[] line = data.split(","); // Splits the line up into a string array
            if (line.length > 1) {
                // Do stuff, e.g:
                String value = line[1];
            }
        }
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (reader != null) {
            try {
                reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            } 
        } 
    }
    

    【讨论】:

    • 你不应该使用 readLine 因为它不会处理带有行分隔符的值。此外,String.split(",") 不适用于包含冒号字符的值。对 CSV 使用 CSV 解析器,而不是手动编写解析器。
    猜你喜欢
    • 2019-07-31
    • 2014-09-26
    • 2015-11-23
    • 1970-01-01
    • 1970-01-01
    • 2021-10-15
    • 2013-10-29
    • 2012-08-26
    相关资源
    最近更新 更多