2016-08-11 32 views
2

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

以下是一般的逻辑:

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(); 

有什么办法可以处理这种类型的具有低内存的服务器状态?

+0

你是直接将每行上传到数据库还是真的保存1 mio行,然后将它全部添加到数据库? – Blobonat

+0

@Blobonat我在批量中添加了1百万条记录,然后一次将它们全部添加到数据库中。 –

+0

*所有行*是相互依赖的,还是存在组?如果是后者,是否可以通过记忆每行信息的一部分来解决群体问题? –

回答

0

专用一个单独的数据库表,仅用于CSV导入。可能会为您提到的那些交叉引用添加其他字段。

如果需要analize CSV领域中的java,通过缓存抑制值实例的数量:

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; 
    } 
} 

在你的情况下,长记录,它甚至可能使SENCE到GZipOutputStream的将行作为字节读取到较短的字节数组中。 但是,数据库似乎更符合逻辑。

1

在过去,我们用来处理存储在连续磁带上的大量数据,而这些数据只有很少的内存和磁盘。但花了很长时间!

基本上,你建立的线条缓冲区不能放在你的内存中,浏览所有文件来解决依赖关系并完全处理这些线条。然后在下一个缓冲区上迭代,直到处理完所有文件。如果需要每个缓冲区完整读取文件,但允许节省内存。

这里可能还有另一个问题,因为您想要将所有记录存储在一个批次中。该批次将需要足够的内存来存储所有记录,因此这里再次存在耗尽内存的风险。但你可以再次使用古老的方法,并保存许多批量较小的尺寸。在你的工作

  • 的开始保存

    • 申报的交易:

      如果你想确保一切都将被要么完全数据库或一切插入会被拒绝,你可以简单地使用事务当everithing完成这单交易

    • 内所有批次提交事务

    专业级的数据库(MySQL和PostgreSQL或acle等)可以使用磁盘上的回滚段来处理一个事务而不耗尽内存。当然,它是慢得多比在内存操作(不说话,如果因为任何原因你必须回滚这样的交易!),但至少它的作品,除非你用尽可用的物理磁盘......

  • 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文件的一列。