代码之家  ›  专栏  ›  技术社区  ›  Ghassen

将7M行的csv文件解析为java对象时的outofmemory

  •  0
  • Ghassen  · 技术社区  · 6 年前

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

    将读取操作转换为 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;
        }
    
    }
    

    并重新输入密码:

    @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;
        }
    }
    
    2 回复  |  直到 6 年前
        1
  •  1
  •   David Medinets doug    6 年前

    我同意“奥瑟”。不过,让我更具体一点。您可以用标准的double.valueOf()替换parseToDouble函数。它应该更有效率。

        2
  •  1
  •   AUser    6 年前

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