粮草机器人 Java操作PDF页面:删除单页、多页、空白页 工业级实现方案



结合你之前关注的Acl.Excel避坑、Apache PDFBox开源库使用、工业级文件处理稳定性的相关背景,

这套方案完全基于Apache PDFBox 2.0.x稳定版本实现,覆盖删除指定单页、批量删除多页、智能识

别删除空白页三类核心场景,同时补充了生产环境高频踩坑点,避免出现内存泄漏、PDF损坏等线上问题。


前置依赖配置


首先在Maven项目中引入PDFBox官方稳定依赖,避免使用版本过旧的快照版,防止出现已知的页面解析漏洞:


xml

<dependency>

    <groupId>org.apache.pdfbox</groupId>

    <artifactId>pdfbox</artifactId>

    <version>2.0.33</version>

</dependency>


三类删除场景完整实现

1. 删除指定单页


直接调用PDDocument的removePage方法,注意PDFBox的页面下标从0开始计数,业务层传入的“第N页”

需要先减1再传入,避免出现删错页面的问题:


java

public void removeSinglePage(String inputPath, String outputPath, int targetPageNum) throws IOException {

    // 使用try-with-resources自动关闭资源,避免内存泄漏

    try (PDDocument document = PDDocument.load(new File(inputPath))) {

        // 业务传入的页码从1开始,转换为PDFBox的0基下标

        int pageIndex = targetPageNum - 1;

        if (pageIndex < 0 || pageIndex >= document.getNumberOfPages()) {

            throw new IllegalArgumentException("指定页码超出PDF总页数范围");

        }

        document.removePage(pageIndex);

        document.save(outputPath);

    }

}


2. 批量删除指定多页


先把需要删除的页码下标按从大到小排序,从后往前删除页面,避免从前往后删除时,页面下标发生偏移导致后续页面删错:


java

public void removeMultiPages(String inputPath, String outputPath, List<Integer> targetPageNums) throws IOException {

    try (PDDocument document = PDDocument.load(new File(inputPath))) {

        // 转换为0基下标,去重后按从大到小排序

        List<Integer> indexList = targetPageNums.stream()

                .map(num -> num - 1)

                .filter(idx -> idx >= 0 && idx < document.getNumberOfPages())

                .distinct()

                .sorted(Comparator.reverseOrder())

                .collect(Collectors.toList());

        for (Integer idx : indexList) {

            document.removePage(idx);

        }

        document.save(outputPath);

    }

}


3. 智能识别删除空白页


不能只靠判断页面资源是否为空来识别空白页,要同时校验页面文本、绘制元素,避免把只有图片、只有线条的非空白页误判为空白页:


java

public void removeBlankPages(String inputPath, String outputPath) throws IOException {

    try (PDDocument document = PDDocument.load(new File(inputPath))) {

        int pageIdx = 0;

        while (pageIdx < document.getNumberOfPages()) {

            PDPage page = document.getPage(pageIdx);

            if (isRealBlankPage(page)) {

                document.removePage(pageIdx);

            } else {

                pageIdx++;

            }

        }

        document.save(outputPath);

    }

}


// 精准判断页面是否为真空白页

private boolean isRealBlankPage(PDPage page) throws IOException {

    // 提取页面所有文本,文本长度大于0直接判定非空白

    String pageText = new PDFTextStripper().getText(new PDDocument(Collections.singletonList(page)));

    if (pageText.trim().length() > 0) {

        return false;

    }

    // 校验页面资源中的图片、图形元素,存在元素直接判定非空白

    if (page.getResources() == null) {

        return true;

    }

    return page.getResources().getXObjects().isEmpty()

            && page.getResources().getFonts().isEmpty()

            && page.getResources().getPatterns().isEmpty();

}


生产环境避坑要点

所有PDDocument实例必须用try-with-resources包裹,自动关闭资源,避免大体积PDF处理后文件句柄泄漏,

导致后续无法再次读取文件。

处理加密PDF时,要先调用document.decrypt(密码)完成解密,再执行页面删除操作,否则会抛出加密权限异常。

大体积PDF处理时,开启MemoryUsageSetting.setupTempFileOnly()配置,把临时数据写入磁盘,避免OOM内存溢出。

处理完成后不要直接覆盖原文件,先写入临时文件,校验成功后再替换原文件,避免中途程序崩溃导致原PDF损坏。


需要我为你补充‌大体积PDF批量删除页面的内存优化版本代码‌,适配GB级大PDF处理场景,完全避免OOM问题吗?