巨大的CSV文件

时间:2016-08-11 08:11:59

标签: java

我正在开发一个系统,它可以加载一个巨大的CSV文件(超过100万行)并保存到数据库中。每条线也有超过一千个场。 CSV文件被视为一个批处理,每行被视为其子对象。在添加对象的过程中,每个对象都将保存在单个批处理列表中,并且在某些时候我的内存不足,因为List将添加超过100万个对象。我不能将文件拆分为N个数字,因为不是按顺序排列的行之间存在依赖关系(任何行都可以依赖于其他行)。

以下是一般逻辑:

Batch batch = new Batch();

while (csvLine !=null ){
   {
      String[] values = csvLine.split( ",", -1 );       

      Transaction txn = new Transaction();
      txn.setType(values[0]);       
      txn.setAmount(values[1]);

      /*
        There are more than one thousand transaction fields in one line
      */


      batch.addTransaction (txn);
}

batch.save();

我们有什么方法可以在服务器内存不足的情况下处理这种情况吗?

3 个答案:

答案 0 :(得分:1)

在过去,我们过去常常处理存储在顺序磁带上的大量数据,而内存和磁盘很少。但是花了很多时间!

基本上,您构建的行缓冲区可以容纳在内存中,浏览所有文件以解析依赖关系并完全处理这些行。然后迭代下一个缓冲区,直到处理完所有文件。如果每个缓冲区需要完整读取文件,但允许节省内存。

此处可能存在其他问题,因为您希望将所有记录存储在一个批处理中。批处理将需要足够的内存来存储所有记录,因此再次存在耗尽内存的风险。但是你可以再次使用好的旧方法,并保存许多较小尺寸的批次。

如果您想确保所有内容都完全插入数据库或一切都将被拒绝,您只需使用交易:

  • 在工作开始时声明交易
  • 将所有批次保存在此单笔交易中
  • 在完成任务时提交交易

专业级数据库(MySQL,PostgreSQL,Oracle等)可以使用磁盘上的回滚段来处理一个事务而不会耗尽内存。当然,它比内存操作慢得多(如果出于任何原因你不得不回滚这样的事务!),但至少它可以工作,除非你耗尽可用的物理磁盘...

答案 1 :(得分:0)

专门为导入单独的数据库表。也许还有你提到的那些交叉引用的附加字段。

如果您需要分析java中的CSV字段,通过缓存来限制值实例的数量

public class SharedStrings {
    private Map<String, String> sharedStrings = new HashMap<>();

    public String share(String s) {
        if (s.length() <= 15) {
            String t = sharedStrings.putIfAbsent(s, s); // Since java 8
            if (t != null) {
                s = t;
            }
            /*
            // Older java:
            String t = sharedString.get(s);
            if (t == null) {
                sharedString.put(s, s);
            } else {
                s = t;
            }
            */
        }
        return s;
    }
}

在你的情况下,对于长记录,它甚至可以将读取行的 GZipOutputStream 作为字节,更短的字节数组。 但是数据库似乎更合乎逻辑。

答案 2 :(得分:0)

如果您正在使用csvLine的所有字段,则以下内容可能不适用。

String#split使用String #substring,而后者不会创建新的字符串,而是将原始字符串保留在内存中并引用相应的部分。

所以这一行会将原始字符串保留在内存中:

String a = "...very long and comma separated";
String[] split = a.split(",");
String b = split[1];
a = null;

因此,如果你没有使用csvLine的所有数据,你应该将每个值的条目包装在一个新的String中,即在上面的例子中你会这样做

String b = new String(split[1]);

否则gc无法释放字符串a。

我遇到了这个,而我正在提取一个包含数百万行的csv文件列。