调整COSWriter解决X-easypdf / PDFBOX生成大量数据时OOM问题

news2024/9/28 5:27:30

背景

业务需要生成一个15W数据左右的PDF交易报表。希望我们写在一个文件里,不拆分成多个PDF文件。

使用的技术组件

        <dependency>
            <groupId>wiki.xsx</groupId>
            <artifactId>x-easypdf-pdfbox</artifactId>
            <version>2.11.10</version>
        </dependency>
    

生成PDF方法

testPDF: 使用xeasypdf实现未做修改

testDynamicPdf: 使用了修改后的方法实现

package wiki.xsx.core.pdf.doc;

import org.junit.Test;
import wiki.xsx.core.pdf.component.table.XEasyPdfCell;
import wiki.xsx.core.pdf.component.table.XEasyPdfRow;
import wiki.xsx.core.pdf.component.table.XEasyPdfTable;
import wiki.xsx.core.pdf.component.text.XEasyPdfText;
import wiki.xsx.core.pdf.handler.XEasyPdfHandler;
import wiki.xsx.core.pdf.mark.XEasyPdfWatermark;

public class XEasyPdfDynamicTest {

    public static final int GENERATE_PAGE = 10000;

    @Test
    //原生办法,最好别执行,会内存溢出。
    public void testPdf() {
        // 定义pdf输出路径
        String outputPath = "D://out.pdf";

        XEasyPdfText titleText = XEasyPdfHandler.Text.build("明细");
        titleText.setHorizontalStyle(XEasyPdfPositionStyle.CENTER);
        titleText.setFontSize(32);
        titleText.setMarginTop(15);
        XEasyPdfWatermark watermark = XEasyPdfHandler.Watermark.build("账单");
        // 如果需要动态加Page,需要使用定制的对象;
        XEasyPdfDocument document = XEasyPdfHandler.Document.build();
        document.setGlobalHeader(XEasyPdfHandler.Header.build(titleText));
        document.setGlobalWatermark(watermark);

        int[] cellWidth = {130, 80, 80, 262};

        for (int current = 0; current < GENERATE_PAGE; current++) {
            XEasyPdfPage xEasyPdfPage = generatePage(current, cellWidth);
            document.addPage(xEasyPdfPage);
        }
        document.save(outputPath).close();
    }

    @Test
    public void testDynamicPdf() {
        // 定义pdf输出路径
        String outputPath = "D://out.pdf";

        XEasyPdfText titleText = XEasyPdfHandler.Text.build("明细");
        titleText.setHorizontalStyle(XEasyPdfPositionStyle.CENTER);
        titleText.setFontSize(32);
        titleText.setMarginTop(15);
        XEasyPdfWatermark watermark = XEasyPdfHandler.Watermark.build("账单");
        // 如果需要动态加Page,需要使用定制的对象;
        XEasyPdfDynamicPdfDocument document = new XEasyPdfDynamicPdfDocument();
        document.setGlobalHeader(XEasyPdfHandler.Header.build(titleText));
        document.setGlobalWatermark(watermark);

        int[] cellWidth = {130, 80, 80, 262};

        for (int current = 1; current <= GENERATE_PAGE; current++) {
            XEasyPdfPage xEasyPdfPage = generatePage(current, cellWidth);
            document.addPage(xEasyPdfPage);
            if (current % 100 == 0) {
                document.flush();
            }
        }
        document.dynamicSave(outputPath, new XEasyPdfDynamicPage(10000, document)).close();
    }

    public static XEasyPdfPage generatePage(long current, int[] cellWidth) {
        // 这里构建一下页数;
        XEasyPdfTable table = XEasyPdfHandler.Table.build();
        XEasyPdfPage page = XEasyPdfHandler.Page.build();

        table.setMarginTop(30);
        table.setMarginLeft(20);
        table.enableCenterStyle();

        XEasyPdfRow headRow = XEasyPdfHandler.Table.Row.build();
        XEasyPdfCell headCell1 = XEasyPdfHandler.Table.Row.Cell.build(cellWidth[0]);
        headCell1.addContent(XEasyPdfHandler.Text.build("卡号"));
        XEasyPdfCell headCell2 = XEasyPdfHandler.Table.Row.Cell.build(cellWidth[1]);
        headCell2.addContent(XEasyPdfHandler.Text.build("下标"));
        XEasyPdfCell headCell3 = XEasyPdfHandler.Table.Row.Cell.build(cellWidth[2]);
        headCell3.addContent(XEasyPdfHandler.Text.build("金额"));
        XEasyPdfCell headCell4 = XEasyPdfHandler.Table.Row.Cell.build(cellWidth[3]);
        headCell4.addContent(XEasyPdfHandler.Text.build("描述"));
        headRow.addCell(headCell1, headCell2, headCell3, headCell4);

        table.addRow(headRow);
        page.addComponent(table);
        for (int i = 0; i < 14; i++) {
            // 14行一页;
            XEasyPdfRow row = XEasyPdfHandler.Table.Row.build();
            row.setHeight(50);
            XEasyPdfCell cell1 = XEasyPdfHandler.Table.Row.Cell.build(cellWidth[0]);
            cell1.addContent(XEasyPdfHandler.Text.build("123456"));
            XEasyPdfCell cell2 = XEasyPdfHandler.Table.Row.Cell.build(cellWidth[1]);
            cell2.addContent(XEasyPdfHandler.Text.build("j-" + current + ":i-" + i));
            XEasyPdfCell cell3 = XEasyPdfHandler.Table.Row.Cell.build(cellWidth[2]);
            cell3.addContent(XEasyPdfHandler.Text.build("20.1"));
            XEasyPdfCell cell4 = XEasyPdfHandler.Table.Row.Cell.build(cellWidth[3]);
            cell4.addContent(XEasyPdfHandler.Text.build("说明"));
            row.addCell(cell1, cell2, cell3, cell4);
            table.addRow(row);
        }
        return page;
    }
}

testPdf执行情况

Exception in thread "RMI TCP Connection(idle)" java.lang.OutOfMemoryError: Java heap space
	at java.base/java.security.AccessController.wrapException(AccessController.java:828)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:716)
	at java.rmi/sun.rmi.transport.Transport.serviceCall(Transport.java:196)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:587)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:828)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(TCPTransport.java:705)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$176/0x000001bb3a9bd290.run(Unknown Source)
	at java.base/java.security.AccessController.executePrivileged(AccessController.java:776)
	at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
	at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:704)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:833)

java.lang.OutOfMemoryError: Java heap space
11月 16, 2023 4:15:07 下午 org.apache.pdfbox.cos.COSDocument finalize
警告: Warning: You did not close a PDF Document

Process finished with exit code -1

从JVM监控可以看出CPU与内存占用会随着PDF文件写入而逐渐增大。【很正常,因为他无法释放内存】

testDynamicPdf运行情况

源代码

基于源码fork的仓库地址【源码我没权限改,所以fork了一个】:

x-easypdf: 一个用搭积木的方式构建pdf的框架(基于pdfbox/fop)icon-default.png?t=N7T8https://gitee.com/crazyAsm/x-easypdf分支:FEATURE_Dynamic_Generate

OOM原因

超过1万页的数据,使用原版的COSWriter类会占用大量内存。

COSWriter在写文件时,会使用doWriterBody方法写入PDF的基础信息。如下:

protected void doWriteBody(COSDocument doc) throws IOException
    {
        COSDictionary trailer = doc.getTrailer();
        COSDictionary root = trailer.getCOSDictionary(COSName.ROOT);
        COSDictionary info = trailer.getCOSDictionary(COSName.INFO);
        COSDictionary encrypt = trailer.getCOSDictionary(COSName.ENCRYPT);
        if( root != null )
        {
            addObjectToWrite( root );
        }
        if( info != null )
        {
            addObjectToWrite( info );
        }

        doWriteObjects();
        willEncrypt = false;
        if( encrypt != null )
        {
            addObjectToWrite( encrypt );
        }

        doWriteObjects();
    }

可以看到会写入的信息有root、基础信息、与加密信息【因为这个不咋占内存,这里就不展开说明了】;然后会执行doWriteObjects();

 第一次写入时可以看出,写的是Type\Version\Page\MetaData这四个信息;

分别对应PDF文件内容的Type\Version\Page\MetaData:f

根据PDF的规则,实际Page栏的4 0 R 代表 第一页对应内容在4 0 obj 位置,有多少页Page就会有多少个引用键。4 0 obj 对应的是第一页的内容,内容又是由一堆引用键组成的。COSWriter的问题也就在这里,只要页数够大,内容够多,这里就会占用大量内存。

解决思路

既然内存占用原因是写入时在内存中存放了太多的内容,那么解决思路也就很容易得出来:一页一页写就行了。

因为我用的事X-EasyPdf 所以基于这个改造了一下。【源码自己看下git仓库吧】

XEasyPdfDynamicCOSWriter:基于COSWriter改造的类目的:在doWriteObjet时,动态加载Page并写入;
XEasyPdfDynamicPage:动态页的实现,结合XEasyPDFDocument的flush方法,借助临时文件增量写页内容。
XEasyPdfDynamicPdfDocument:增加了个实现,写文件改用XEasyPdfDynamicCOSWriter类。

参考文章

https://zxyle.github.io/PDF-Explained/resources/pdf_reference_1.7.pdf

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1223076.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于和声算法优化概率神经网络PNN的分类预测 - 附代码

基于和声算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于和声算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于和声优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络的光滑…

IPSecGRE

IPSec&GRE 手工方式建立IPSec隧道组网实验拓扑配置步骤第一步配置IP地址第二步配置静态路由第三步配置IPSec 抓包测试 GRE Over IPSec功能的配置组网实验拓扑配置命令 配置GRE使用静态路由组网图实验拓扑配置步骤1.配置RouterA2.配置RouterB3.配置RouterC4.验证配置结果 手…

【限时免费】20天拿下华为OD笔试之 【前缀和】2023B-最大子矩阵和【欧弟算法】全网注释最详细分类最全的华为OD真题题解

文章目录 题目描述与示例题目描述输入描述输出描述示例输入输出说明 解题思路如何表示一个子矩阵暴力解法二维前缀和优化二维前缀和矩阵的构建 代码解法一&#xff1a;二维前缀和PythonJavaC时空复杂度 解法二&#xff1a;暴力解法&#xff08;不推荐&#xff09;PythonJavaC时…

Pattern Recognition投稿经验

文章目录 ManuscriptTitle PageHighlightsAuthor BiographyDeclarationSubmit 合作推广&#xff0c;分享一个人工智能学习网站。计划系统性学习的同学可以了解下&#xff0c;点击助力博主脱贫( •̀ ω •́ )✧ 停更了大半年&#xff0c;近期终于完成了论文投稿&#xff0c;趁…

聚观早报 |联想集团Q2财季业绩;小鹏汽车Q3营收

【聚观365】11月17日消息 联想集团Q2财季业绩 小鹏汽车Q3营收 微软发布两款自研AI芯片 FAA批准SpaceX再次发射星际飞船 2023 OPPO开发者大会 联想集团Q2财季业绩 全球数字经济领导企业联想集团公布截至2023年9月30日的2023/24财年第二财季业绩&#xff1a;整体营收达到10…

闲聊从零开发一个2D数字人流程实战

.2D数字人技术 百度&#xff0c;腾讯&#xff0c;等大厂都有自己的数字平台制作&#xff08;套壳&#xff1a;api后台转发vue前端&#xff09;&#xff0c;国外也有出名的heygen&#xff08;非常厉害一个&#xff09;通过开源项目组合实现&#xff0c;再打通每个项目已api的形…

OpenAI 变天:Sam Altman 被踢出局,原 CTO 暂代临时 CEO

文章目录 灵魂人物 Sam Altman 离任 OpenAICEO 下台&#xff1a;OpenAI 也宫斗&#xff1f;个人简介 hello&#xff0c;大家好&#xff0c;我是 Lorin&#xff0c;一觉醒来科技圈发生了一件令人震惊的大事&#xff1a;Sam Altman 被踢出局&#xff0c;原 CTO 暂代临时 CEO。 灵…

Flask学习一:概述

搭建项目 安装框架 pip install Flask第一个程序 from flask import Flaskapp Flask(__name__)app.route(/) def hello_world():return "Hello World"if __name__ __main__:app.run()怎么说呢&#xff0c;感觉还不错的样子。 调试模式 if __name__ __main__:a…

搭建企业社区,如何激发员工互动?

本文是关于企业内部社区搭建后怎么运营&#xff0c;如何激发员工互动。 作为运营者&#xff0c;我们搭建企业内部员工的目的首先得明确下来&#xff0c;一般都是打造和宣扬企业内部文化&#xff0c;发布公司政策通知和行业动态、组织公司关键节点活动、以及员工经验分享资源分…

【数据结构】图的存储结构及实现(邻接表和十字链表)

一.邻接矩阵的空间复杂度 假设图G有n个顶点e条边&#xff0c;则存储该图需要O&#xff08;n^2) 不适用稀疏图的存储 二.邻接表 1.邻接表的存储思想&#xff1a; 对于图的每个顶点vi&#xff0c;将所有邻接于vi的顶点链成一个单链表&#xff0c;称为顶点vi的边表&#xff08…

基于蛾群算法优化概率神经网络PNN的分类预测 - 附代码

基于蛾群算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于蛾群算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于蛾群优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络的光滑…

2023.11.18 Hadoop之 YARN

1.简介 Apache Hadoop YARN &#xff08;Yet Another Resource Negotiator&#xff0c;另一种资源协调者&#xff09;是一种新的 Hadoop 资源管理器&#xff0c;它是一个通用资源管理系统和调度平台&#xff0c;可为上层应用提供统一的资源管理和调度。支持多个数据处理框架&…

【每周一测】Java阶段三阶段考试

目录 1、SpringBoot在整合RabbitMQ时需要导入的包是 2、下列关于RabbitMQ的confirm消息确认机制解释说明正确的是 3、关于SpringBoot的配置文件&#xff0c;以下说法正确的是&#xff08;&#xff09; 4、变量命名规范说法正确的是? 5、哪个关键字可以对对象加互斥锁&…

Adversarial Attacks on Neural Networks for Graph Data

Adversarial Attacks on Neural Networks for Graph Data----《针对图数据的神经网络的对抗攻击》 论文提出了两个问题&#xff1a; 1、属性图的深度学习模型容易受攻击吗&#xff1f; 2、他们的结果可靠吗&#xff1f; 回答这两个问题需要考虑到GNN的特性&#xff1a; ①关…

Spring IOC - 推断构造方法

一、前言 上文解析了Bean生命周期的实例化阶段&#xff0c;其中bean真正开始实例化的核心代码位于方法AbstractAutowireCapableBeanFactory#createBeanInstance中&#xff0c;这里也是spring推断构造方法的核心所在。 二、整体介绍 首先看下方法的源码及注释如下&#xff0c;下…

一文带你了解QT Model/View框架的设计思想和实现机制

目录 1、QT Model/View框架简介 1.1、QT Model/View是什么&#xff1f; 1.2、QT Model/View框架核心思想 1.3、Model/View框架工作机制 1.4、Model/View框架的类 2、Model 2.1模型简介 2.2、模型索引 2.3、数据角色 2.4、QStringListModel 2.5、QFileSystemModel 2…

Pycharm中添加Python库指南

一、介绍 Pycharm是一款为Python开发者提供的集成开发环境&#xff08;IDE&#xff09;&#xff0c;支持执行、调试Python代码&#xff0c;并提供了许多有用的工具和功能&#xff0c;其中之一就是在Pycharm中添加Python库。 添加Python库有许多好处&#xff0c;比如能够增加开…

xlua源码分析(三)C#访问lua的映射

xlua源码分析&#xff08;三&#xff09;C#访问lua的映射 上一节我们主要分析了lua call C#的无wrap实现。同时我们在第一节里提到过&#xff0c;C#使用LuaTable类持有lua层的table&#xff0c;以及使用Action委托持有lua层的function。而在xlua的官方文档中&#xff0c;推荐使…

算法通关村第十关-青铜挑战快速排序

大家好我是苏麟,今天带来快速排序 . 快速排序 单边快速排序(lomuto 洛穆托分区方案) 单边循环 (lomuto分区) 要点 : 选择最右侧元素作为基准点j 找比基准点小的&#xff0c;i 找比基准点大的&#xff0c;一旦找到&#xff0c;二者进行交换。 交换时机: 找到小的&#xff0c…

第四篇 《随机点名答题系统》——基础设置详解(类抽奖系统、在线答题系统、线上答题系统、在线点名系统、线上点名系统、在线考试系统、线上考试系统)

目录 1.功能需求 2.数据库设计 3.流程设计 4.关键代码 4.1.设置题库 4.1.1数据请求示意图 4.1.2选择题库&#xff08;index.php&#xff09;数据请求代码 4.1.3取消题库&#xff08;index.php&#xff09;数据请求代码 4.1.4业务处理Service&#xff08;xztk.p…