巨大CSV文件的Java内存问题

巨大CSV文件的Java内存问题,java,Java,我正在开发一个系统,加载一个巨大的CSV文件(超过100万行)并保存到数据库中。而且每一行都有一千多个字段。CSV文件被视为一个批处理,每一行被视为其子对象。在添加对象的过程中,每个对象都将保存在单个批处理的列表中,并且在某个时候我的内存不足,因为该列表将添加超过100万个对象。我无法将文件拆分为N个数字,因为非串行顺序的行之间存在依赖关系(任何行都可以依赖于其他行) 以下是一般逻辑: Batch batch = new Batch(); while (csvLine !=null ){

我正在开发一个系统,加载一个巨大的CSV文件(超过100万行)并保存到数据库中。而且每一行都有一千多个字段。CSV文件被视为一个批处理,每一行被视为其子对象。在添加对象的过程中,每个对象都将保存在单个批处理的列表中,并且在某个时候我的内存不足,因为该列表将添加超过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();

在服务器内存不足的情况下,有没有办法处理这种情况?为CSV导入指定一个单独的数据库表。可能需要为您提到的交叉引用添加其他字段

如果需要在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;
    }
}
公共类共享字符串{
私有映射sharedStrings=新HashMap();
公共字符串共享(字符串s){

如果(s.length()在过去,我们用很少的内存和磁盘来处理顺序磁带上存储的大量数据,但这需要很长时间

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

这里可能还有另一个问题,因为您希望在一个批中存储所有记录。该批需要足够的内存来存储所有记录,因此这里同样存在耗尽内存的风险。但您可以再次使用好的旧方法,并保存许多较小的批

如果要确保所有内容都将完全插入数据库或被拒绝,只需使用事务:

  • 在作业开始时声明事务
  • 将所有批次保存在此单个事务中
  • 完成所有操作后提交事务

专业级数据库(MySQL、PostgreSQL、Oracle等)可以使用磁盘上的回滚段,以便在不耗尽内存的情况下处理一个事务。当然,它比内存中的操作慢得多(如果出于任何原因必须回滚这样的事务,则不必说!)但至少它可以工作,除非您耗尽可用的物理磁盘…

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

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

因此此行将在内存中保留原始字符串:

String a = "...very long and comma separated";
String[] split = a.split(",");
String b = split[1];
a = null;
因此,如果您没有使用csvLine的所有数据,您应该将每个值条目包装成一个新字符串,即在上面的示例中,您可以这样做

String b = new String(split[1]);
否则gc无法释放字符串a


我在提取包含数百万行的csv文件的一列时遇到了这个问题。

您是将每行直接上传到数据库中,还是真的保存了一百万行,然后将其全部添加到数据库中?@Blobonat我正在批量添加一百万条记录,然后立即将其全部添加到数据库中。所有行是相互依赖的,还是相互依赖的组?如果是后者,是否可以通过在内存中只保存每条线的一部分信息来解析组?@Theodoroschatziganakis并非所有线都相互依赖,通常有5条记录由一个字段(txn_编号)标识。而复杂的部分是,这些依赖行不是以串行顺序排列的。@SushilPaudel如果没有其他方法,则将所有数据保存在内存中,您必须购买更多内存来解决此问题。或者您更改CSV文件的结构,以便一次只能处理部分行。