用Jackson编写大型JSON文件

有时您需要将大量数据导出到JSON到文件中。 可能是“将所有数据导出到JSON”,或者是GDPR“可移植性权利”,您实际上需要这样做。

与任何大型数据集一样,您不能只将其全部容纳在内存中并将其写入文件。 它需要一段时间,它会从数据库中读取大量条目,并且您需要注意不要使此类导出使整个系统超载或耗尽内存。

幸运的是,借助Jackson的SequenceWriter和可选的管道流,这样做非常简单。 看起来像这样:

private ObjectMapper jsonMapper = new ObjectMapper();
    private ExecutorService executorService = Executors.newFixedThreadPool(5);

    @Async
    public ListenableFuture<Boolean> export(UUID customerId) {
        try (PipedInputStream in = new PipedInputStream();
                PipedOutputStream pipedOut = new PipedOutputStream(in);
                GZIPOutputStream out = new GZIPOutputStream(pipedOut)) {
        
            Stopwatch stopwatch = Stopwatch.createStarted();

            ObjectWriter writer = jsonMapper.writer().withDefaultPrettyPrinter();

            try(SequenceWriter sequenceWriter = writer.writeValues(out)) {
                sequenceWriter.init(true);
            
                Future<?> storageFuture = executorService.submit(() ->
                       storageProvider.storeFile(getFilePath(customerId), in));

                int batchCounter = 0;
                while (true) {
                    List<Record> batch = readDatabaseBatch(batchCounter++);
                    for (Record record : batch) {
                        sequenceWriter.write(entry);
                    }
                }

                // wait for storing to complete
                storageFuture.get();
            }  

            logger.info("Exporting took {} seconds", stopwatch.stop().elapsed(TimeUnit.SECONDS));

            return AsyncResult.forValue(true);
        } catch (Exception ex) {
            logger.error("Failed to export data", ex);
            return AsyncResult.forValue(false);
        }
    }

该代码可以做一些事情:

  • 使用SequenceWriter连续写入记录。 它使用OutputStream初始化,所有内容均写入其中。 这可以是简单的FileOutputStream,也可以是如下所述的管道流。 注意,这里的命名有点误导– writeValues(out)听起来像是您指示作者现在写东西; 而是将其配置为以后使用特定的流。
  • SequenceWriter初始化为true ,表示“包装在数组中”。 您正在编写许多相同的记录,因此它们应在最终JSON中表示一个数组。
  • 使用PipedOutputStreamPipedInputStreamSequenceWriter链接到InputStream ,然后将InputStream传递到存储服务。 如果我们明确地处理文件,则不需要这样做-只需传递FileOutputStream就可以。 但是,您可能希望以不同的方式存储文件,例如在Amazon S3中,并且putObject调用需要一个InputStream,从该InputStream可以读取数据并将其存储在S3中。 因此,实际上,您正在写入一个OutputStream,而该OutputStream会直接写入InputStream,当被输入以读取该输入流时,会将所有内容写入另一个OutputStream
  • 存储文件是在单独的线程中调用的,因此写入文件不会阻塞当前线程,当前线程的目的是从数据库中读取数据。 同样,如果使用简单的FileOutputStream,则不需要这样做。
  • 整个方法被标记为@Async(spring),因此它不会阻止执行-它被调用并在准备就绪时完成(使用内部Spring executor服务和有限的线程池)
  • 这里未显示数据库批处理读取代码,因为它随数据库的不同而不同。 关键是,您应该分批提取数据,而不是SELECT * FROMX。
  • OutputStream包装在GZIPOutputStream中,因为带有重复元素的JSON之类的文本文件可从压缩中显着受益

主要工作是由Jackson的SequenceWriter完成的,(显而易见的)一点是–不要假设您的数据可以容纳在内存中。 它几乎永远不会做,所以批量处理和增量写入都是如此。

翻译自: https://www.javacodegeeks.com/2018/08/writing-big-json-files-jackson.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值