【问题标题】:OutOfMemory when parsing a CSV file of 7M lines into Java Object将 7M 行的 CSV 文件解析为 Java 对象时的 OutOfMemory
【发布时间】:2018-12-14 12:12:08
【问题描述】:

我正在逐行提取一个 CSV 文件,其中包含超过 7M 行,占用磁盘空间超过 1Gig。

List<String> 的读取操作很好,并且发生在不到 2 分钟的时间内。 但问题是当我尝试在这个列表上循环并将每一行映射到一个对象Balance 然后我创建了一个OuyOfMemoryException

01:00:30.664 [restartedMain] ERROR org.springframework.batch.core.step.AbstractStep - Encountered an error executing step readInputStep in job readCsvJob
java.lang.OutOfMemoryError: GC overhead limit exceeded
    at java.lang.AbstractStringBuilder.<init>(AbstractStringBuilder.java:68) ~[?:1.8.0_172]
    at java.lang.StringBuffer.<init>(StringBuffer.java:128) ~[?:1.8.0_172]
    at java.text.DigitList.getStringBuffer(DigitList.java:804) ~[?:1.8.0_172]
    at java.text.DigitList.getDouble(DigitList.java:164) ~[?:1.8.0_172]
    at java.text.DecimalFormat.parse(DecimalFormat.java:2089) ~[?:1.8.0_172]
    at java.text.NumberFormat.parse(NumberFormat.java:383) ~[?:1.8.0_172]
    at fr.payet.flad.batch.mapper.BalanceLineMapper.parseToDouble(BalanceLineMapper.java:56) ~[classes/:?]
    at fr.payet.flad.batch.mapper.BalanceLineMapper.toBalance(BalanceLineMapper.java:40) ~[classes/:?]
    at fr.payet.flad.batch.tasklet.ReadInputTasklet.execute(ReadInputTasklet.java:56) ~[classes/:?]

这是我的 BalanceLineMapper 代码:

@Component
@Slf4j
public class BalanceLineMapper {

    public Balance toBalance(String[] ligneCsv, int cursorIndex) {
        try {
            return Balance.builder()
                    .index(cursorIndex)
                    .exer(ligneCsv[0])
                    .ident(ligneCsv[1])
                    .nDept(ligneCsv[2])
                    .lBudg(ligneCsv[3])
                    .insee(ligneCsv[4])
                    .siren(ligneCsv[5])
                    .cRegi(ligneCsv[6])
                    .nomen(ligneCsv[7])
                    .cType(ligneCsv[8])
                    .cstyp(ligneCsv[9])
                    .cActi(ligneCsv[10])
                    .finess(ligneCsv[11])
                    .secteur(ligneCsv[12])
                    .cBudg(ligneCsv[13])
                    .codBud1(ligneCsv[14])
                    .compte(ligneCsv[15])
                    .BEDeb(ligneCsv[16])
                    .BECre(parseToDouble(ligneCsv[17]))
                    .OBNetDeb(parseToDouble(ligneCsv[18]))
                    .OBNetCre(parseToDouble(ligneCsv[19]))
                    .ONBDeb(parseToDouble(ligneCsv[20]))
                    .ONBCre(parseToDouble(ligneCsv[21]))
                    .OOBDeb(parseToDouble(ligneCsv[22]))
                    .OOBCre(parseToDouble(ligneCsv[23]))
                    .sd(parseToDouble(ligneCsv[24]))
                    .sc(parseToDouble(ligneCsv[25]))
                    .build();
        } catch (NumberFormatException e) {
            log.debug("Erreur lors de du casting");
        }
        return null;
    }

    private Double parseToDouble(String number){
        NumberFormat format = NumberFormat.getInstance(Locale.FRANCE);
        try {
             return format.parse(number).doubleValue();
        }catch (ParseException e){
            log.error("Erreur de parsing de {} en Java Double", number, e.getMessage(), e);
        }
        log.error("parseToDouble retourne la valeur NULL");
        return null;
    }

}

和 ReadInputTasklet 代码:

@Slf4j
@Component
public class ReadInputTasklet implements Tasklet, StepExecutionListener {

    @Autowired
    BalanceLineMapper balanceLineMapper;

    @Override
    public RepeatStatus execute(StepContribution stepContribution, ChunkContext chunkContext) throws Exception {
        List<Balance> balances = Lists.newArrayList();
        List<String> balancesList = Lists.newArrayList();
        try {
            CSVReader reader = new CSVReader(new FileReader("/Users/ghassen/Desktop/FLAD/Balance_Commune_2016.csv"), '\n');
            String[] nextLine;
            int cursorIndex = 0;
            while ((nextLine = reader.readNext()) != null) {
                if (cursorIndex != 0){
                    balancesList.add(nextLine[0]);
                    log.debug("{} balance(s) ajoutée(s) dans la liste ...", balancesList.size());
                }
                cursorIndex++;
            }
            log.debug("Lecture de toutes les lignes terminé");

            log.debug("Parsing de toutes les lignes");
            for (String line : balancesList){
                String[] lineSeperated = StringUtils.splitByWholeSeparatorPreserveAllTokens(line,";");
                balances.add(balanceLineMapper.toBalance(lineSeperated, cursorIndex));
            }
            log.debug("Job terminé");
        } catch (IOException e) {
            log.error("File not found", e);
        }
        return RepeatStatus.FINISHED;
    }

    @Override
    public void beforeStep(StepExecution stepExecution) {

    }

    @Override
    public ExitStatus afterStep(StepExecution stepExecution) {
        return null;
    }
}

【问题讨论】:

  • 我不知道 Spring,但你可能需要增加它的堆设置。 -Xmx 将是 vanilla JVM 的相关命令行参数,例如-Xmx6G 将上限设置为 6 GB。也许这样:stackoverflow.com/questions/23072187/…

标签: java csv out-of-memory


【解决方案1】:

您在短时间内创建了大量实例(包括稍后要解析的字符串),而垃圾收集器无法跟上。我建议您以流式设计构建整个系统,并仅解析您实际需要的系统。

【讨论】:

  • 我之前的解决方案是直接解析读取的行并添加到List&lt;Balance&gt;。我需要所有这些,因为之后我会将它们保存在数据库中。
【解决方案2】:

我同意@AUser。但是,让我更具体一点。你可以用标准的 Double.valueOf() 替换你的 parseToDouble 函数。它应该更有效率。

【讨论】:

  • 你是对的..我删除了方法Double parseToDouble(String number),现在没问题了:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多