杰瑞科技汇

Java xlsx转xlsx,如何高效实现?

最常用、最强大的库是 Apache POI,下面我将详细介绍如何使用 Apache POI 来完成这个任务,并提供几种不同的场景示例。

Java xlsx转xlsx,如何高效实现?-图1
(图片来源网络,侵删)

准备工作:添加 Apache POI 依赖

你需要在你的项目中添加 Apache POI 的依赖,如果你使用 Maven,请在 pom.xml 文件中添加以下内容:

<dependencies>
    <!-- Apache POI 核心库 -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi</artifactId>
        <version>5.2.5</version> <!-- 建议使用较新版本 -->
    </dependency>
    <!-- Apache POI OOXML 库,用于处理 .xlsx 格式 -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-ooxml</artifactId>
        <version>5.2.5</version>
    </dependency>
    <!-- 为了更好的性能,建议添加这个依赖 -->
    <dependency>
        <groupId>org.apache.poi</groupId>
        <artifactId>poi-scratchpad</artifactId>
        <version>5.2.5</version>
    </dependency>
</dependencies>

如果你使用 Gradle,则在 build.gradle 文件中添加:

implementation 'org.apache.poi:poi:5.2.5'
implementation 'org.apache.poi:poi-ooxml:5.2.5'
implementation 'org.apache.poi:poi-scratchpad:5.2.5'

核心步骤

无论进行何种操作,基本流程都遵循以下三步:

  1. 读取:使用 XSSFWorkbook 打开源 XLSX 文件。
  2. 处理:遍历工作表、行和单元格,读取数据,进行修改或计算。
  3. 写入:将修改后的 XSSFWorkbook 对象写入到一个新的 XLSX 文件中。

重要:请务必使用 try-with-resources 语句来管理文件流,确保资源被正确关闭。

Java xlsx转xlsx,如何高效实现?-图2
(图片来源网络,侵删)

示例 1:最简单的复制(不修改内容)

这个示例演示了如何将 source.xlsx 完整地复制到 target.xlsx,虽然简单,但它展示了基本框架。

import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class SimpleCopyXlsx {
    public static void main(String[] args) {
        String sourceFilePath = "path/to/your/source.xlsx";
        String targetFilePath = "path/to/your/target.xlsx";
        try (FileInputStream fis = new FileInputStream(sourceFilePath);
             XSSFWorkbook sourceWorkbook = new XSSFWorkbook(fis);
             FileOutputStream fos = new FileOutputStream(targetFilePath)) {
            // XSSFWorkbook 对象已经包含了源文件的所有内容
            // 直接将其写入到新的文件输出流中
            sourceWorkbook.write(fos);
            System.out.println("文件已成功从 " + sourceFilePath + " 复制到 " + targetFilePath);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

示例 2:修改内容后转换(常见场景)

这是最常见的用法,我们读取一个文件,修改一些数据,然后保存为新文件。

场景:将 students.xlsx 中所有学生的“数学”成绩加 10 分,然后保存为 updated_students.xlsx

假设 students.xlsx 内容如下:

Java xlsx转xlsx,如何高效实现?-图3
(图片来源网络,侵删)
姓名 数学 英语
张三 85 90
李四 72 88
王五 93 95

Java 代码实现:

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class ModifyAndConvertXlsx {
    public static void main(String[] args) {
        String sourcePath = "students.xlsx";
        String targetPath = "updated_students.xlsx";
        try (FileInputStream fis = new FileInputStream(sourcePath);
             XSSFWorkbook workbook = new XSSFWorkbook(fis);
             FileOutputStream fos = new FileOutputStream(targetPath)) {
            // 1. 获取第一个工作表
            Sheet sheet = workbook.getSheetAt(0);
            // 2. 遍历每一行(从第二行开始,假设第一行是标题)
            for (int i = 1; i <= sheet.getLastRowNum(); i++) {
                Row row = sheet.getRow(i);
                if (row == null) {
                    continue; // 如果某行是空的,则跳过
                }
                // 3. 获取“数学”成绩所在的单元格(假设它在第二列,索引为1)
                Cell mathCell = row.getCell(1); // 列索引从0开始
                // 4. 检查单元格是否为数字类型
                if (mathCell != null && mathCell.getCellType() == CellType.NUMERIC) {
                    double originalScore = mathCell.getNumericCellValue();
                    // 修改值:加10分
                    mathCell.setCellValue(originalScore + 10);
                }
            }
            // 5. 将修改后的工作簿写入新文件
            workbook.write(fos);
            System.out.println("文件已处理并成功保存到: " + targetPath);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

执行后的 updated_students.xlsx

姓名 数学 英语
张三 95 90
李四 82 88
王五 103 95

示例 3:更复杂的操作(添加公式、样式等)

Apache POI 功能非常强大,你还可以进行更复杂的操作。

场景:在 students.xlsx 的末尾添加一个“总分”列,并计算每个学生的总分,为总分高于 180 的学生所在行设置背景色为浅绿色。

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.xssf.usermodel.IndexedColors;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class ComplexOperationXlsx {
    public static void main(String[] args) {
        String sourcePath = "students.xlsx";
        String targetPath = "students_with_total.xlsx";
        try (FileInputStream fis = new FileInputStream(sourcePath);
             XSSFWorkbook workbook = new XSSFWorkbook(fis);
             FileOutputStream fos = new FileOutputStream(targetPath)) {
            Sheet sheet = workbook.getSheetAt(0);
            // 创建单元格样式
            CellStyle highScoreStyle = workbook.createCellStyle();
            highScoreStyle.setFillForegroundColor(IndexedColors.LIGHT_GREEN.getIndex());
            highScoreStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
            // 添加“总分”标题行
            Row titleRow = sheet.getRow(0);
            if (titleRow == null) {
                titleRow = sheet.createRow(0);
            }
            Cell totalTitleCell = titleRow.createCell(3); // 第四列
            totalTitleCell.setCellValue("总分");
            // 遍历数据行,计算总分并设置样式
            for (int i = 1; i <= sheet.getLastRowNum(); i++) {
                Row row = sheet.getRow(i);
                if (row == null) continue;
                Cell mathCell = row.getCell(1);
                Cell englishCell = row.getCell(2);
                double math = 0, english = 0;
                if (mathCell != null && mathCell.getCellType() == CellType.NUMERIC) {
                    math = mathCell.getNumericCellValue();
                }
                if (englishCell != null && englishCell.getCellType() == CellType.NUMERIC) {
                    english = englishCell.getNumericCellValue();
                }
                double total = math + english;
                Cell totalCell = row.createCell(3); // 在第四列创建总分单元格
                totalCell.setCellValue(total);
                // 如果总分大于180,应用样式
                if (total > 180) {
                    row.setRowStyle(highScoreStyle); // 为整行设置样式
                }
            }
            workbook.write(fos);
            System.out.println("复杂操作完成,文件已保存到: " + targetPath);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

性能优化建议

对于处理非常大的 XLSX 文件(包含数万行数据),XSSFWorkbook 会将整个文件加载到内存中,可能导致 OutOfMemoryError

为了解决这个问题,Apache POI 提供了 SXSSF (Streaming Usermodel API),它是一个基于事件的、低内存占用的 API,非常适合处理大数据量。

SXSSF 的核心思想

  • 它只保留一定数量的“滑动窗口”中的行在内存中。
  • 超出窗口的行会被临时写入到磁盘上的临时文件中。
  • 所有数据会合并成一个完整的 XLSX 文件。

使用 SXSSF 的示例(概念性):

import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFRow;
import org.apache.poi.xssf.streaming.SXSSFCell;
// ... (省略文件流操作)
// 使用 SXSSFWorkbook 而不是 XSSFWorkbook
// 参数 100 是指在内存中保留100行
try (SXSSFWorkbook workbook = new SXSSFWorkbook(100)) {
    SXSSFSheet sheet = workbook.createSheet("Sheet1");
    // 创建数据...
    for (int i = 0; i < 100000; i++) {
        SXSSFRow row = sheet.createRow(i);
        SXSSFCell cell = row.createCell(0);
        cell.setCellValue("Row " + i);
    }
    // 写入文件
    try (FileOutputStream out = new FileOutputStream("large_file.xlsx")) {
        workbook.write(out);
    }
    // 清理临时文件(非常重要!)
    ((SXSSFWorkbook) workbook).dispose();
}

注意:SXSSF 有一些限制,例如不能随机访问行(只能顺序写入),并且不能修改已写入磁盘的行,但对于从零开始创建或顺序转换大文件的场景,它是最佳选择。

需求场景 推荐库 特点
小/中型文件,需要读取、修改、再写入 XSSFWorkbook 功能最全,可以随机读写任何单元格,但内存占用高。
大型文件(10万+行),顺序写入或转换 SXSSFWorkbook 低内存占用,流式处理,速度快,但只能顺序写入,不能修改。
仅读取大型文件内容 XSSF + Event API 内存占用极低,但使用起来比 Usermodel API 复杂。

对于“xlsx 转 xlsx”这个通用问题,XSSFWorkbook 是最常用和最灵活的解决方案,如果你的文件特别大,再考虑使用 SXSSFWorkbook

分享:
扫描分享到社交APP
上一篇
下一篇