为有中文需求的客户提供多渠道中文技术支持.

Tue Nov 12, 2024 3:51 am

public static void extRactAndReplaceText(String src, PnBatteryRegister pnBatteryRegister) {
logger.info("开始提取和替换文本。");
Map<String, String> replacements = generateReplacements(pnBatteryRegister);
String batterySpecification = safeGetString(pnBatteryRegister.getBatterySpecification());
String[] specs = batterySpecification.split(",");
Set<String> seenSpecs = new HashSet<>();
List<String> uniqueSpecs = new ArrayList<>();
for (String spec : specs) {
String trimmedSpec = spec.trim();
if (!trimmedSpec.isEmpty() && seenSpecs.add(trimmedSpec)) {
uniqueSpecs.add(trimmedSpec);
}
}
logger.info("处理电池规格: {}", uniqueSpecs);
processBatterySpecifications(uniqueSpecs, replacements);
String path = AwsUtil.getSubPath("8", "en");
String filename = generateFilenameWithDate();
String s3Key = path + '/' + filename;
String fileUrl = Constants.RESOURCE_PREFIX + '/' + path + '/' + filename;
String tempFile = PaiNengConfig.getDownloadPath() + "temp.pdf";
logger.info("准备下载 PDF,源 URL: {}", src);
PdfDocument pdf = null;
try {
pdf = new PdfDocument();
InputStream pdfInputStream = new UrlResource(src).getInputStream();
pdf.loadFromStream(pdfInputStream);
pdfInputStream.close();
logger.info("PDF 成功加载。");
// 添加附录页
if (uniqueSpecs.size() > 3) {
logger.info("添加附录页,因为规格数量超过 3。");
// 2. 设置页面边距
PdfMargins margin = new PdfMargins(90);
PdfPageBase page = pdf.getPages().add(PdfPageSize.A4, margin);
// 3. 设置字体
PdfTrueTypeFont titleFont = new PdfTrueTypeFont(new Font("Hind", Font.BOLD, 12));
// 3. 绘制标题
PdfBrush brush = PdfBrushes.getBlack();
String title = RegisterConstants.KEY_TITLE;
page.getCanvas().drawString(title, titleFont, brush, 10, 20);
// 4. 绘制项目列表
float y = 40;
PdfOrderedMarker marker = new PdfOrderedMarker(PdfNumberStyle.Numeric, titleFont);
PdfSortedList list = new PdfSortedList(String.join("\n", uniqueSpecs)); // 使用 uniqueSpecs 数据
list.setFont(titleFont);
list.setIndent(2);
list.setTextIndent(4);
list.setBrush(brush);
list.setMarker(marker);
list.draw(page, 10, y);
}
// 替换文本
logger.info("开始替换 PDF 中的文本。");
// 确保 PDF 至少有一页
if (pdf.getPages().getCount() > 0) {
PdfPageBase pdfPage = pdf.getPages().get(0); // 获取第一页
PdfTextReplacer textReplacer = new PdfTextReplacer(pdfPage);
// 遍历替换项,进行批量替换
for (Map.Entry<String, String> entry : replacements.entrySet()) {
textReplacer.replaceAllText(entry.getKey(), entry.getValue());
}
}
// 保存 PDF 到本地文件
pdf.saveToFile(tempFile);
logger.info("PDF 成功保存到本地文件。");
logger.info("开始上传 PDF 到 S3,目标 key: {}", s3Key);
uploadToS3WithTransferManager(new File(tempFile), s3Key);
// 设置文件 URL
pnBatteryRegister.setFile(fileUrl);
logger.info("设置文件 URL: {}", fileUrl);
} catch (Exception e) {
logger.error("处理过程中发生错误: {}", e.getMessage(), e);
throw new ServiceException("处理过程中发生错误");
} finally {
// 确保在最后关闭 PdfTrueTypeFont 对象(如果它不为 null)
if (pdf != null) {
// 记录释放前的内存
logMemoryUsage("释放 PDF 资源前");
pdf.dispose(); // 手动释放资源
logger.info("PDF 资源已释放。");
// 记录释放后的内存
logMemoryUsage("释放 PDF 资源后");
}
}
}

song_zhi_jie
 
Posts: 1
Joined: Tue Nov 12, 2024 3:49 am

Tue Nov 12, 2024 7:32 am

您好,

感谢您的留言。
请问您是使用我们Spire.PDF 10.10.7最新商业版测试的吗?如果不是的话,由于最新版包含的修复更多,建议从下面链接下载最新版测试一下。
如果新版本测试之后还是有问题,您能详细描述一下您的问题吗,请问jvm无法回收具体是指什么?据我们所了解,jvm的内存回收机制是由jvm自动管理的,dispose()等方法只能加速其回收,但并不能让jvm立即回收对象。
如果您是遇到了内存占用过多或者内存溢出等问题,请提供以下信息供我们进一步调查。提前感谢您的协助!
1. 您的测试文件。
2. JDK版本(如JDK8.0)。
3.您的测试环境(如Windows11)

https://www.e-iceblue.cn/Downloads/Spire-PDF-JAVA.html

Sincerely,
Doris
E-iceblue support team
User avatar

Doris.Liu
 
Posts: 460
Joined: Mon Nov 07, 2022 8:10 am

Mon May 18, 2026 10:17 am

相同的问题,我是使用 free版本,在使用文本替换时,内存无法回收
<dependency>
<groupId>e-iceblue</groupId>
<artifactId>spire.pdf.free</artifactId>
<version>9.13.0</version>
</dependency>
关键代码:

public static ByteArrayOutputStream drawMosaicTextAndGet(byte[] originalPdf, Collection<String> sensitiveTextList) {
// 创建一个PdfDocument对象
com.spire.pdf.PdfDocument doc = null;
// 预分配缓冲区大小,避免频繁扩容。原PDF大小的1.5倍作为输出缓冲区的预估大小
int estimatedSize = originalPdf.length > 0 ? (int) (originalPdf.length * 1.5) : 8192;
ByteArrayOutputStream outputStream = new ByteArrayOutputStream(estimatedSize);
try {
doc = new com.spire.pdf.PdfDocument(originalPdf);
// 创建一个PdfTextReplaceOptions对象
PdfTextReplaceOptions textReplaceOptions = new PdfTextReplaceOptions();
// 指定文本替换的选项
textReplaceOptions.setReplaceType(EnumSet.of(ReplaceActionType.WholeWord));
// 遍历PDF文档所有页面,获取页面下标与页面内容的键值对
SortedMap<Integer, String> pdfPageTextMap = getPdfPageTextMapByDoc(originalPdf, doc);
for (Map.Entry<Integer, String> indexTextMap : pdfPageTextMap.entrySet()) {
int index = indexTextMap.getKey();
if (index >= 10) {
// 只处理前10页
break;
}
PdfPageBase page = doc.getPages().get(index);
// 如果没有文本就跳过
String text = indexTextMap.getValue();
if (StringUtils.isBlank(text)) {
continue;
}
// 调用 ai 接口识别敏感信息
// 遍历敏感信息,替换文本
if (CollectionUtils.isEmpty(sensitiveTextList)) {
continue;
}
// 每个页面只创建一个PdfTextReplacer实例,避免重复创建导致的内存浪费
PdfTextReplacer textReplacer = new PdfTextReplacer(page);
textReplacer.setOptions(textReplaceOptions);
for (String sensitiveText : sensitiveTextList) {
// 将所有目标文本实例替换为新文本
textReplacer.replaceAllText(sensitiveText, "*************");
}
}
// 将文档保存到不同的PDF文件
doc.saveToStream(outputStream);
return outputStream;
} catch (Exception e) {
log.error("spire打码失败: ", e);
return null;
}finally {
// 释放资源
if (doc != null) {
doc.dispose();
}
}
}

public static SortedMap<Integer, String> getPdfPageTextMapByDoc(byte[] fileByte, PdfDocument spirePdfDoc) {
SortedMap<Integer, String> textMap = new TreeMap<>();
for (int i = 0; i < spirePdfDoc.getPages().getCount(); i++) {
PdfPageBase page = spirePdfDoc.getPages().get(i);
// 创建PdfTextExtractor 对象
PdfTextExtractor textExtractor = new PdfTextExtractor(page);
// 创建PdfTextExtractOptions 对象
PdfTextExtractOptions extractOptions = new PdfTextExtractOptions();
// 从页面中提取文本
String text = textExtractor.extract(extractOptions);
if (StringUtils.isNotBlank(text)) {
textMap.put(i, text);
}
}
return textMap;
}

chenggecong
 
Posts: 2
Joined: Mon May 26, 2025 3:20 am

Tue May 19, 2026 6:29 am

您好,

感谢您对我们产品的关注。
针对您关注的内存占用问题,请注意在商业版中,我们针对 PDF 文本替换及提取接口进行了优化,显著降低了其对内存的消耗。 建议您优先升级至最新商业版进行验证。
Code: Select all
<dependency>
    <groupId>e-iceblue</groupId>
    <artifactId>spire.pdf</artifactId>
    <version>12.4.4</version>
</dependency>


结合您提供的测试代码,我们在本地环境复现了 50 次循环调用场景,未发现内存泄漏现象。请注意: 调用 doc.dispose()方法的目的是立即释放非托管资源(如文件句柄、Native 层缓存、GDI+ 对象等)并断开 Java 对象的强引用。这会将对象标记为“可回收状态”,但并不会强制 JVM 立即执行垃圾回收(GC)。

Java 的内存回收是由 JVM 垃圾收集器自动管理的。在实际运行中,JVM 会倾向于让堆内存使用量逐步上升,直到接近 -Xmx 设定的阈值才会进行大规模的内存清理。因此,在程序运行过程我们会观察到内存有个显著升高的过程。我建议您在测试的时候将JVM配置较小内存,比如-Xms256m -Xmx512m, 这样可以强制 JVM 更频繁地触发 GC,从而观察内存的变化。

若在执行上述调优后,您依然观察到内存呈单调递增且无法回落的趋势,请提供以下信息,我们将为您做进一步的定向分析:

1. 您的测试PDF文档以及您需要替换的敏感词汇
2. 您的JDK 版本及操作系统环境
3. 您那边观察到的内存泄漏详情

下面是我的测试代码供您参考:
Code: Select all
public static void main(String[] args) throws IOException {
        MemoryMXBean memoryMxBean = ManagementFactory.getMemoryMXBean();
        //获取内存使用情况
        MemoryUsage heapMemoryUsage = memoryMxBean.getHeapMemoryUsage();
        MemoryUsage nonHeapMemoryUsage = memoryMxBean.getNonHeapMemoryUsage();

        //打印内存使用情况
        System.out.println("Heap Memory Usage: " + heapMemoryUsage);
        System.out.println("Non-Heap Memory Usage: " + nonHeapMemoryUsage);
        byte[] originalPdf = Files.readAllBytes(Paths.get("test.pdf"));
        Collection<String> sensitiveTextList = Arrays.asList("敏感词1", "敏感词2", "敏感词3");
        for (int i = 0; i < 50; i++) {
            try {
                heapMemoryUsage = memoryMxBean.getHeapMemoryUsage();
                System.out.println("Start Heap Memory Usage: " + heapMemoryUsage);

                ByteArrayOutputStream outputStream = drawMosaicTextAndGet(originalPdf,sensitiveTextList);

                heapMemoryUsage = memoryMxBean.getHeapMemoryUsage();
                System.out.println("End Heap Memory Usage: " + heapMemoryUsage);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
        public static ByteArrayOutputStream drawMosaicTextAndGet(byte[] originalPdf,Collection<String> sensitiveTextList) {
            com.spire.pdf.PdfDocument doc = null;
            // 预分配缓冲区大小,避免频繁扩容。原PDF大小的1.5倍作为输出缓冲区的预估大小
            int estimatedSize = originalPdf.length > 0 ? (int) (originalPdf.length * 1.5) : 8192;
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream(estimatedSize);

            try {
                doc = new com.spire.pdf.PdfDocument(originalPdf);

                // 创建一个PdfTextReplaceOptions对象
                PdfTextReplaceOptions textReplaceOptions = new PdfTextReplaceOptions();
                // 指定文本替换的选项
                textReplaceOptions.setReplaceType(EnumSet.of(ReplaceActionType.WholeWord));

                // 遍历PDF文档所有页面,获取页面下标与页面内容的键值对
                SortedMap<Integer, String> pdfPageTextMap = getPdfPageTextMapByDoc(originalPdf, doc);

                for (Map.Entry<Integer, String> indexTextMap : pdfPageTextMap.entrySet()) {
                    int index = indexTextMap.getKey();
                    if (index >= 10) {
                        // 只处理前10页
                        break;
                    }
                    PdfPageBase page = doc.getPages().get(index);

                    // 每个页面只创建一个PdfTextReplacer实例,避免重复创建导致的内存浪费
                    PdfTextReplacer textReplacer = new PdfTextReplacer(page);
                    textReplacer.setOptions(textReplaceOptions);
                    for (String sensitiveText : sensitiveTextList) {
                        // 将所有目标文本实例替换为新文本
                        textReplacer.replaceAllText(sensitiveText, "*************");
                    }
                }

                // 将文档保存到不同的PDF文件
                doc.saveToStream(outputStream);
                return outputStream;

            } catch (Exception e) {
                System.out.printf("spire打码失败: ", e);
                return null;
            } finally {
                // 释放资源
                if (doc != null) {
                    doc.dispose();
                }
            }
        }

        /**
         * 获取PDF页面文本映射
         * @param fileByte PDF字节数组
         * @param spirePdfDoc PDF文档对象
         * @return 页面索引和文本的映射
         */
        public static SortedMap<Integer, String> getPdfPageTextMapByDoc(byte[] fileByte, PdfDocument spirePdfDoc) {
            SortedMap<Integer, String> textMap = new TreeMap<>();
            for (int i = 0; i < spirePdfDoc.getPages().getCount(); i++) {
                PdfPageBase page = spirePdfDoc.getPages().get(i);
                // 创建PdfTextExtractor 对象
                PdfTextExtractor textExtractor = new PdfTextExtractor(page);
                // 创建PdfTextExtractOptions 对象
                PdfTextExtractOptions extractOptions = new PdfTextExtractOptions();
                // 从页面中提取文本
                String text = textExtractor.extract(extractOptions);
                if (text!="") {
                    textMap.put(i, text);
                }
            }
            return textMap;
        }
    }

Sincerely,
Nina
E-iceblue support team
User avatar

Nina.Tang
 
Posts: 1385
Joined: Tue Sep 27, 2016 1:06 am

Return to 中文技术支持