当前位置:
首页
文章
后端
详情

Java 使用 Jackson编写大型 JSON 文件

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

与任何大型数据集一样,您不能将其全部放入内存并将其写入文件。这需要一段时间,它从数据库中读取大量条目,您需要小心不要使此类导出使整个系统过载或耗尽内存。

幸运的是,在 ​JacksonSequenceWriter​和可选的管道流的帮助下,这样做相当简单。这是它的样子:

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);
                }
                if (batch.isEmpty()) {
                    // if there are no more batches, stop.
                    break;
                }
            }
 
            // wait for storing to complete
            storageFuture.get();
 
            // send the customer a notification and a download link
            notifyCustomer(customerId);
        } 
 
        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 中表示一个数组。
  • 使用​PipedOutputStream​和​PipedInputStream​将​SequenceWriter​链接到​InputStream​然后传递给存储服务的 ​an&nbsp;​。如果我们明确地处理文件,就没有必要了——只需传递 ​aFileOutputStream​就可以了。但是,您可能希望以不同的方式存储文件,例如在 Amazon S3 中,并且 ​putObject ​调用需要一个 ​InputStream​,从中读取数据并将其存储在 ​S3 ​中。因此,实际上,您正在写入直接写入 ​InputStream ​的 ​OutputStream​,当尝试从中读取时,会将所有内容写入另一个 ​OutputStream
  • 存储文件是在单独的线程中调用的,这样写入文件不会阻塞当前线程,其目的是从数据库中读取。同样,如果使用简单的 ​FileOutputStream​,则不需要这样做。
  • 整个方法被标记为​@Async (spring) ​以便它不会阻塞执行——它在准备好时被调用并完成(使用具有有限线程池的内部 Spring 执行程序服务)
  • 数据库批量读取代码这里没有显示,因为它因数据库而异。关键是,您应该批量获取数据,而不是 ​SELECT * FROM X​。
  • OutputStream ​被包裹在 ​GZIPOutputStream ​中,因为像 JSON 这样带有重复元素的文本文件可以从压缩中显着受益

主要工作是由 Jackson 的 SequenceWriter 完成的,需要清楚的点是 - 不要假设您的数据会适合内存。它几乎从不这样做,因此以批处理和增量写入的方式进行所有操作。


免责申明:本站发布的内容(图片、视频和文字)以转载和分享为主,文章观点不代表本站立场,如涉及侵权请联系站长邮箱:xbc-online@qq.com进行反馈,一经查实,将立刻删除涉嫌侵权内容。

同类热门文章

深入了解C++中的new操作符:使用具体实例学习

C++中的new操作符是动态分配内存的主要手段之一。在程序运行时,我们可能需要动态地创建和销毁对象,而new就是为此提供了便利。但是,使用new也常常会引发一些问题,如内存泄漏、空指针等等。因此,本文将通过具体的示例,深入介绍C++中的new操作符,帮助读者更好地掌握其使用。


深入了解C++中的new操作符:使用具体实例学习

怎么用Java反射获取包下所有类? 详细代码实例操作

Java的反射机制就是在运行状态下,对于任何一个类,它能知道这个类的所有属性和方法;对于任何一个对象,都能调用这个对象的任意一个方法。本篇文章将通过具体的代码示例,展示如何通过Java反射来获取包下的所有类。


怎么用Java反射获取包下所有类? 详细代码实例操作

员工线上学习考试系统

有点播,直播,在线支付,三级分销等功能,可以对学员学习情况的监督监控,有源码,可二次开发。支持外网和局域网私有化部署,经过测试源码完整可用!1、视频点播:视频播放,图文资料,课件下载,章节试学,限时免

员工线上学习考试系统

了解Java中的volati关键字的作用 以及具体使用方法

本篇文章将和大家分享一下Java当中的volatile关键字,下面将为各位小伙伴讲述volatile关键字的作用以及它的具体使用方法。


了解Java中的volati关键字的作用 以及具体使用方法

Java Map 所有的值转为String类型

可以使用 Java 8 中的 Map.replaceAll() 方法将所有的值转为 String 类型: 上面的代码会将 map 中所有的值都转为 String 类型。 HashMap 是 Java

Java Map 所有的值转为String类型