【问题标题】:Lazy CSV Filtering / Parsing - Increasing Performance惰性 CSV 过滤/解析 - 提高性能
【发布时间】:2017-01-28 10:15:15
【问题描述】:

延迟过滤 CSV 文件

我需要过滤存储为大量 CSV 文件的数百万条日志记录。记录的大小大大超出了我的可用内存,所以我想采用一种懒惰的方法。

Java 8 流 API

使用jdk8,我们拥有与Apache commons-csv 配对的Streams API,让我们可以轻松完成此任务。

public class LazyFilterer {

    private static Iterable<CSVRecord> getIterable(String fileName) throws IOException {
        return CSVFormat
                .DEFAULT
                .withFirstRecordAsHeader()
                .parse(new BufferedReader(new FileReader(fileName)));
    }

    public static void main(String[] args) throws Exception {
        File dir = new File("csv");

        for (File file : dir.listFiles()) {
            Iterable<CSVRecord> iterable = getIterable(file.getAbsolutePath());

            StreamSupport.stream(iterable.spliterator(), true)
                    .filter(c -> c.get("API_Call").equals("Updates"))
                    .filter(c -> c.get("Remove").isEmpty())
                    .forEach(System.out::println);
        }
    }
}

性能

这张来自 VisualVM 的图表显示了使用比上图更复杂的过滤管道1 解析 2.3 GB CSV 文件期间的内存使用情况。

如您所见,内存使用量在过滤发生时基本保持不变2

你能找到另一种方法来更快地完成相同的任务,同时不增加代码复杂性吗?

欢迎任何语言,Java 不一定是首选!

脚注

[1] - 例如对于与"API_Call" 匹配的每个CSVRecord,我可能需要进行一些JSON 反序列化并在此之后进行额外的过滤,或者甚至为某些记录创建一个对象以促进额外的计算。

[2] - 图表开头的空闲时间是 System.in.read(),用于确保 VisualVM 在计算开始之前已完全加载。

【问题讨论】:

  • 你在自相矛盾。当您同时说“记录的大小大大超出了我的可用内存”时,“将整个 CSV 文件读取到内存的简单算法”再快不过了。
  • 没错,好点。您可以使用“如果我有足够的可用内存”或“用于一小部分数据”的条件来接受该陈述
  • 我看不出将假设场景的性能与真实场景的性能进行比较有什么意义。此外,您没有命名“幼稚的实现”,此外,您没有显示任何有关性能的数字。因此,您的问题是基于一个空洞的说法,即未指定的实现将比您在不适用的假设场景中所做的更快。
  • @Holger 我删除了这句话,因为它与问题无关。如果您想提供答案并且需要确定您的解决方案与我给出的解决方案相比的性能,您可以生成一些 CSV 文件并在您自己的本地计算机上运行它们。很遗憾,我无法提供我实际过滤的任何 CSV 文件。
  • 这与“问题无关”。完全不清楚为什么您认为必须有比您已有的解决方案更快的解决方案。以及仅要求工具或库或 SO 题外话的问题。

标签: csv functional-programming java-8 java-stream lazy-evaluation


【解决方案1】:

这对于仅 2.3GB 的数据来说太可怕了,我可以建议您尝试使用 uniVocity-parsers 以获得更好的性能吗?试试这个:

CsvParserSettings settings = new CsvParserSettings();
settings.setHeaderExtractionEnabled(true); // grabs headers from input

//select the fieds you are interested in. The filtered ones get in front to make things easier
settings.selectFields("API_Call", "Remove"/*, ... and everything else you are interested in*/);

//defines a processor to filter the rows you want
settings.setProcessor(new AbstractRowProcessor() {
    @Override
    public void rowProcessed(String[] row, ParsingContext context) {
        if (row[0].equals("Updates") && row[1].isEmpty()) {
            System.out.println(Arrays.toString(row));
        }
    }
});

// create the parser
CsvParser parser = new CsvParser(settings);

//parses everything. All rows will be sent to the processor defined above
parser.parse(file, "UTF-8"); 

我知道它不起作用,但处理我创建的一个 4 GB 文件来测试它需要 20 秒,同时消耗 不到 75mb 整个时间的记忆。从您的图形来看,您当前的方法似乎需要 1 分钟来处理较小的文件,并且需要 10 倍的内存。

试试这个例子,相信会有很大帮助。

免责声明,我是这个库的作者,它是开源免费的(Apache 2.0 许可)

【讨论】:

  • 太棒了,谢谢!但是,示例中使用的大量内存和时间可能是由于流中的对象实例化所致。我不只是在做我展示的那种简单的过滤。我会尝试使用你的库,看看它是如何工作的。
  • 顺便问一下,您介意分享您创建的 4GB 文件吗?我想尝试在我的机器上同时运行你的代码和 java 流代码来比较苹果和苹果。
  • 很高兴为您提供帮助。我使用的文件没什么特别的。我得到了这个maxmind.com/download/worldcities/worldcitiespop.txt.gz 并复制了它的内容 30 次。我还选择了“国家”和“城市”列。您可以只使用原始文件(不扩展它)并运行相同的代码几次以获得一个不错的基准。从尝试过的整体表现来看是一样的。
  • 将此标记为答案,因为它是提供的最快的解决方案。
猜你喜欢
  • 2011-06-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-25
  • 2013-07-13
  • 1970-01-01
相关资源
最近更新 更多