Java处理大型 Excel 文件(超过 100 万行)难题

您将获得一个包含 100 个电子表格的工作簿,每个电子表格都有 100 万条记录,至少有 100 列。

以下是每个 Excel 版本可以支持的行数供您参考:

  • 1,048,576 行 -- Excel 365、2013、2010、2007

  • 65,536 行 -- Excel 2003 及更早版本

背景分析

在开发过程中,处理大型 Excel 文件是不少开发者都会遇到的"拦路虎"。像这里提到的包含 100 个电子表格,每个表格有 100 万条记录且至少 100 列的工作簿,处理起来更是难上加难,"内存不足"错误就像幽灵一样常常出现。而且不同 Excel 版本支持的行数不同,再加上成本和托管服务器的限制(第三方许可证购买不可行,只能用开源方案),部署环境也有各种各样的情况,这无疑给开发工作增加了不少难度。

开源解决方案探讨

在当前的数字市场中,开源解决方案是我们的"救命稻草"。很多人推荐使用 Apache POI 流媒体库,它确实是处理 Excel 文件的一把好手。Apache POI 是一个用于处理各种 Microsoft Office 格式文件的 Java 库,其流媒体库可以在处理大型 Excel 文件时减少内存的使用。它通过逐行读取和处理数据,而不是一次性将整个文件加载到内存中,从而避免了"内存不足"的错误。

Apache POI 进行实际实现

为了获取大型数据集 (Excel) 文件,可以从这些地方下载个大型数据集验证,例如 awesome-public-datasetsGoogle Dataset SearchWorld Bank Data

让我们将容器数据集下载为 1.9GB CSV 文件,然后将其保存为 Excel 文件 (.XSLX),该文件变成了大约 600 MB 的 XLSX 文件。

该表包括 87 列和 1048576 行。

在运行解决方案之前,让我捕获笔记本电脑的资源使用情况,以便以后进行比较。

资源使用情况

  • 中央处理器:3%

  • 内存:54%

CPU: 3%, memory: 54%

IDE

我使用的是 Eclipse IDE (v: 4.36.0)。

Apache POI

我正在使用最新的 Apache POI 5.2.5 和 Apache POI 提供的其他依赖项 JARS。

源代码

在这里,我只是尝试从工作簿中读取工作表名称,而不是行。

java 复制代码
public static void main(String[] args) throws Exception {        String filePath = "自己去上面的网站下个.xlsx";        ReadExcelbyApachePOI(filePath);    }​​    static void ReadExcelbyApachePOI(String filePath) throws Exception {        try (OPCPackage opcPackage = OPCPackage.open(new File(filePath), PackageAccess.READ)) {            XSSFWorkbook workbook = new XSSFWorkbook(opcPackage);            XSSFReader xssfReader = new XSSFReader(opcPackage);            StylesTable styles = xssfReader.getStylesTable();            XSSFReader.SheetIterator iter = (XSSFReader.SheetIterator) xssfReader                    .getSheetsData();            while (iter.hasNext()) {                InputStream stream = iter.next();                String sheetName = iter.getSheetName();                System.out.println("Sheetname: " + sheetName);            }​        } catch (IOException e) {            e.printStackTrace();        }    }

结果

遇到"Java 堆空间 - 内存不足错误"。

爪哇岛

arduino 复制代码
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

资源使用情况

  • 中央处理器:58%

  • 内存:94%

CPU: 58%, memory: 94%

使用 sjxlsx 开源 API实现

这是一个google开源的 Java API 源代码;下载不下来可以从下面百度云下载

通过网盘分享的文件:sjxlsx-master.zip

链接: pan.baidu.com/s/11lAh6kK2... 提取码: q8mv

这个项目最初是在 Google Code 上发布的,它似乎没有维护。在 GitHub 中,任何人都可以根据自己的需要下载和更新更改。

"内存"和"速度"是该 API 的主要目标。

它提供了两种模式,即"经典"和"流"。

  1. Classic -- 将加载工作表的所有记录。

  2. Stream -- 一次读取一条记录。

Microsoft XLSX 使用 XML+zip (OOXML) 来存储数据。因此,为了快速起见,"sjxlsx"使用 STAX 进行 XML 输入和输出。

源代码

java 复制代码
public static void main(String[] args) throws Exception {        String filePath = "自己去上面的网站下个.xlsx";        SimpleXLSXWorkbook workbook = newWorkbook(filePath);        testLoadALL(workbook);    }private static SimpleXLSXWorkbook newWorkbook(String filePath) {        return new SimpleXLSXWorkbook(new File(filePath));    }    /*Read Each Row*/    private static void printRow(int rowPos, com.incesoft.tools.excel.xlsx.Cell[] row) {        int cellPos = 0;        for (com.incesoft.tools.excel.xlsx.Cell cell : row) {            System.out.println(com.incesoft.tools.excel.xlsx.Sheet.getCellId(rowPos, cellPos) + "=" + cell.getValue());            cellPos++;        }    }​    /*Load & Read workbook     * false => Read each row     * true  => Load all rows     */    public static void testLoadALL(SimpleXLSXWorkbook workbook) {        com.incesoft.tools.excel.xlsx.Sheet sheetToRead = workbook.getSheet(0,false);        SheetRowReader rowreader = sheetToRead.newReader();        int rowPos = 0;        while (rowreader != null) {            com.incesoft.tools.excel.xlsx.Cell[] row = rowreader.readRow();            printRow(rowPos, row);            rowPos++;​        }​    }

资源使用情况

  • CPU:3%(无变化)

  • 内存:61%(7% 使用率:1 GB 使用率)

CPU: 3%, RAM: 61%

输出

ini 复制代码
BN1048519=40298BO1048519=0BP1048519=0BQ1048519=0BR1048519=0BS1048519=610BT1048519=0BU1048519=1BV1048519=64240BW1048519=923BX1048519=158BY1048519=32BZ1048519=0CA1048519=0CB1048519=0CC1048519=0CD1048519=0CE1048519=0CF1048519=0CG1048519=0CH1048519=10000206CI1048519=0A1048520=100.64.0.2-10.16.0.9-35919-8080-6B1048520=100.64.0.2C1048520=35919D1048520=10.16.0.9E1048520=8080F1048520=6G1048520=45266.83932053241H1048520=41626I1048520=6J1048520=5K1048520=515L1048520=357M1048520=515N1048520=0O1048520=85.8333333333333P1048520=210.24786958888899Q1048520=357R1048520=0S1048520=71.400000000000006T1048520=159.65525359348399U1048520=20948.445682986501V1048520=264.25791572574798W1048520=4162.5999999999904X1048520=12728.124713056101Y1048520=40374Z1048520=9AA1048520=41626AB1048520=8325.2000000000007AC1048520=17922.528077813098AD1048520=40374AE1048520=29AF1048520=41594AG1048520=10398.5AH1048520=20011.5685292282AI1048520=40406AJ1048520=26AK1048520=1AL1048520=1AM1048520=0AN1048520=0AO1048520=0AP1048520=0AQ1048520=200AR1048520=168AS1048520=144.14068130495301AT1048520=120.11723442079401AU1048520=0AV1048520=515AW1048520=79.272727272727295AX1048520=179.87445116474399AY1048520=32354.8181818181AZ1048520=2BA1048520=2BB1048520=0BC1048520=2BD1048520=10BE1048520=0BF1048520=0BG1048520=0BH1048520=0.83333333333333304BI1048520=79.272727272727195BJ1048520=85.8333333333333BK1048520=71.400000000000006BL1048520=0BM1048520=0BN1048520=0BO1048520=0BP1048520=0BQ1048520=0BR1048520=0BS1048520=46BT1048520=0BU1048520=32BV1048520=64240BW1048520=502BX1048520=1BY1048520=32BZ1048520=0CA1048520=0CB1048520=0CC1048520=0CD1048520=0CE1048520=0CF1048520=0CG1048520=0CH1048520=41626CI1048520=0A1048521=100.64.0.2-10.16.0.9-9479-8080-6B1048521=100.64.0.2C1048521=9479D1048521=10.16.0.9E1048521=8080F1048521=6G1048521=45266.835683206016H1048521=111205I1048521=6J1048521=5K1048521=537L1048521=357

性能结果

根据上面的性能结果对比可以看出,获胜者是"sjxlsx"。事实证明,与 Apache POI 的更高使用率相比,该库消耗的内存不到 1 GB。

它是一个出色的开源 Java API,用于读取大型 Excel 数据集。

还有其他的的功能,没有用过

除此之外,它还支持编写 Excel 并在工作表中提供丰富的数据格式。

sql 复制代码
public static void main(String[] args) throws Exception {        // WRITE - we take WRITE as a special kind of MODIFY         SimpleXLSXWorkbook workbook = newWorkbook();        OutputStream output = ExcelOutput("write");        WriteExcel(workbook, output);        output.close();    } private static SimpleXLSXWorkbook newWorkbook() {         return new SimpleXLSXWorkbook(new File("/sample.xlsx")); }     private static OutputStream ExcelOutput(String suffix) throws Exception {         return new BufferedOutputStream(new FileOutputStream("/sample_"                         + suffix + ".xlsx")); }​    public static void WriteExcel(SimpleXLSXWorkbook workbook,            OutputStream outputStream) throws Exception {        com.incesoft.tools.excel.xlsx.Sheet sheet = workbook.getSheet(0);        WriteRecords(workbook, sheet, 0);    workbook.commit(outputStream);}     static public void WriteRecords(SimpleXLSXWorkbook wb, com.incesoft.tools.excel.xlsx.Sheet sheet,             int rowOffset) {     int columnCount = 10;     int rowCount = 10;     int offset = rowOffset;     for (int r = offset; r < offset + rowCount; r++) {             int modfiedRowLength = sheet.getModfiedRowLength();             for (int c = 0; c < columnCount; c++) {                     sheet.modify(modfiedRowLength, c, r + "," + c, null);             }     }}

自定义行风格

scss 复制代码
public static void WriteRichStyleRow(SimpleXLSXWorkbook wb, com.incesoft.tools.excel.xlsx.Sheet sheet)              throws Exception {      Font font = wb.createFont();      font.setColor("FFFF0000");      Fill fill = wb.createFill();      fill.setFgColor("FF00FF00");      CellStyle style = wb.createStyle(font, fill);​      RichText richText = wb.createRichText();      richText.setText("test_text");      Font font2 = wb.createFont();      font2.setColor("FFFF0000");      richText.applyFont(font2, 1, 2);      sheet.modify(0, 0, (String) null, style);      sheet.modify(1, 0, richText, null);}

总结

最终,"sjxlsx"提供了一种高效、轻量级的方式来读取大型 Excel 文件,而无需解决基础设施问题。

相关推荐
老K的Java兵器库4 小时前
Collections 工具类 15 个常用方法源码:sort、binarySearch、reverse、shuffle、unmodifiableXxx
java·开发语言·哈希算法
武子康4 小时前
Java-153 深入浅出 MongoDB 全面的适用场景分析与选型指南 场景应用指南
java·开发语言·数据库·mongodb·性能优化·系统架构·nosql
救救孩子把4 小时前
从 JDK 8 到 JDK 23:HotSpot 垃圾回收器全景演进与深度剖析
java·开发语言·jvm·jdk
ha20428941945 小时前
Linux操作系统学习之---线程控制
java·linux·学习
Knight_AL5 小时前
Spring AOP 中@annotation的两种写法详解
java·spring
某空m5 小时前
【Android】BottomNavigationView实现底部导航栏
android·java
顾漂亮5 小时前
Spring AOP 实战案例+避坑指南
java·后端·spring
SimonKing5 小时前
Mybatis-Plus的竞争对手来了,试试 MyBatis-Flex
java·后端·程序员
光军oi5 小时前
JAVA全栈JVM篇————初识JVM
java·开发语言·jvm