【问题标题】:How to Compare two large CSV file in java如何在java中比较两个大的CSV文件
【发布时间】:2021-09-28 07:38:04
【问题描述】:

我需要比较两个大的 csv 文件并找出差异。

第一个 CSV 文件如下:

c71f55b6c18248b8915d8a26
64b7d2d4eab74d7999a967c0
ceb792ad21054fe0a27ec410
95319566f9424c57ba2145f9
682a4fe26c154050b8f5c6f1
88e0209e2af74049ad9bf2bd
5c462b42763d41d7bb67029f
0ee74c227fc84e39a9ecc1da
66f7ab6f56374ba08d2fb92d
3ed793e35f9441b58562c9ba
baad81ac8ba54188afe63fb8
...

每一行只有一个 id,总行数约为 5 百万。 第二个 CSV 文件将类似于第一个,总行数为 3 百万。

我需要从第一个 csv 中删除第二个 csv 的 id 并将它们放入 MongoDb。当我将所有行放入内存然后比较两个 CSV 文件时,出现内存不足错误。我有 512Mb 的内存空间,每天至少会收到 30 个请求。 CSV 的行数正在变化 100 万至 1000 万。我可以同时收到两个请求,同时做同样的事情。

还有其他办法吗?

谢谢。

【问题讨论】:

  • 不能直接在数据库中完成工作吗?
  • csv 文件中的数据是否已排序?那将是非常有益的。
  • 我没有看到任何逗号,你为什么称它为 CSV 文件?
  • 基本上,您需要为 ID 建立索引以便快速搜索,并且只在内存中保留可管理的子集。数据库已经在这样做了。 10M 正确索引的行不是那么多。许多数据库甚至提供原生 CSV 导入(甚至是 MongoDB)。

标签: java csv file


【解决方案1】:

如果您需要在 java 中管理数据,您可以使用 Set 作为基本数据结构来保存您的数据:

不包含重复元素的集合

特别是在您的情况下,最好使用 HashSet 字符串,因为:

此类为基本操作提供恒定时间性能(添加、删除、包含和大小)

这意味着从HashSet 添加和删除项目不依赖于HashSet 中存在的项目数量。 保存 10.000.000 或 24 个字符的字符串可以用大约半 GB 的内存来完成,因此您可以将所有内容保存在内存中,但如果您受到半 GB 内存的限制,请考虑 10.000.000 是您的上限。

代码可以是这样的

Set<String> items = new HashSet<>();

...
// For each item in the first file (may be a loop or stream)
items.add(item);
...
// For each item in the second file (may be a loop or stream)
items.remove(item);
...
// Here the set contains all items of the first csv without items present also 
// in the second csv

【讨论】:

  • 所陈述的问题是 HashSet 不适合内存。但是在当今的计算机中应该可以管理 1000 万个 ID
  • 10.000.000(项目数)* 24(字符数)* 2(java 中每个字符的字节数)= 480.000.000 字节,因此您非常接近内存的限制。目前的HashSet在内存使用方面是相当小的,你有什么问题?您是否将完整文件保存在内存中?如果是这样,则所需的内存大小会增加一倍,因为您同时在内存中拥有文件和 HashSet
【解决方案2】:

出于性能原因,您应该在内存中保留第二个文件的表示,这样您就可以遍历第一个文件,检查该条目是否包含在第二个文件中,如果不包含,则将该条目插入 MongoDB。

第二个文件的表示应该:

  • 紧凑,不要消耗太多内存,
  • 允许快速“包含”检查。

您的数据条目似乎都由 24 个十六进制数字组成。如果这是真的,您可以将它们表示为 96 位数字而不是字符串。最直接的方法是

String entry = ...
BigInteger value = new BigInteger(entry,16);

然后,您使用Set&lt;BigInteger&gt; 而不是Set&lt;String&gt;,内存消耗大大降低。我会尝试HashSetTreeSet,但我担心它们的每个条目的内存开销可能仍然太多。

因此,可能有必要创建自己的数据结构,例如使用第一个(最高)16 位作为大小为 65536 数组的索引,其中每个元素是文件的一个 List,两个 BigIntegers 以该 16 位值开头。这应该会产生较低的内存开销、不错的contains() 性能,并且最多需要 50 行代码。

【讨论】:

    【解决方案3】:

    您需要从第一个 CSV 文件中删除也存在于第二个 CSV 文件中的数据。由于两个 CSV 都非常大,它们不能完全加载到内存中。 Java 会生成一段很长的代码来执行此操作。

    在开源 Java 包 SPL 中完成这项工作相当简单。一行代码就足够了:

    A
    1 =file("result.csv").export([file("csv1.csv").cursor@i().sortx(~),file("csv2.csv").cursor@i().sortx(~)].mergex@d())

     

    SPL 提供 JDBC 驱动程序供 Java 调用。只需将上述 SPL 脚本存储为 diff.splx 并在调用存储过程时在 Java 中调用它:

    …    
    Class.forName("com.esproc.jdbc.InternalDriver");    
    con= DriverManager.getConnection("jdbc:esproc:local://");    
    st=con.prepareCall("call diff()");    
    st.execute();    
    …
    

    或者在我们执行 SQL 语句时在 Java 程序中执行 SPL 字符串:

    …
    st = con.prepareStatement("==file(\"result.csv\").     
         export([file(\"csv1.csv\").cursor@i().sortx(~),file(\"csv2.csv\")
        .cursor@i().sortx(~)].mergex@d ())");
    
    st.execute();
    …
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多