Skip to content
wangguanquanPublic

About

A fast and lower memory excel write/read tool.一个非POI底层,支持流式处理的高效且超低内存的Excel读写工具

Topics

Resources

Stars

341 stars

Watchers

15 watching

Forks

Latest commit

 

History

2,816 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

EEC介绍

Release License

EEC(Excel Export Core)是一款轻量且高效的Excel读写工具,它具有包体小、接入代码量少和运行时消耗资源少等优点

EEC的设计初衷是为了解决Apache POI内存高、速度慢且API臃肿的诟病。EEC的底层并不依赖POI包,所有的底层代码均自己实现,事实上EEC仅依赖dom4j和slf4j,前者用于小文件xml读取,后者统一日志接口。

EEC在JVM参数-Xmx10m -Xms10m下读写100w行x29列内存使用截图,下载 eec-benchmark 项目进行性能测试

write_read 100w

使用场景

EEC是线程不安全的它不支持多线程读写,同时其为流式设计且只能顺序向后,这意味着不能通过指定行列坐标来随机读写,通常可以使用EEC来做一些日常的导入/导出功能,推荐在大数据量或性能/内存要求较高的场景或者没有随机读写的场景下使用。

目前已实现worksheet类型有以下七种,也可以继承已有Worksheet来实现自定义数据源

主要功能

  1. 支持大数据量导出行数无上限,超过单个Sheet上限会自动分页
  2. 超低内存,无论是xlsx还是xls格式,大部分情况下可以在10MB以内完成十万级甚至百万级行数据读写
  3. 支持动态样式,如导出库存时将低于预警阈值的行背景标黄显示
  4. 支持一键设置斑马线,利于阅读
  5. 自适应列宽对中文更精准
  6. 采用Stream流读文件,按需加载不会将整个文件读入到内存
  7. 支持Iterator和Stream+Lambda读文件,你可以像操作集合类一样操作Excel
  8. 支持csv与excel格式相互转换

WIKI

阅读WIKI 了解更多用法

Gitee

国内用户可访问Gitee, 在Gitee提issue开发者也同样会及时回复

使用方法

pom.xml添加

<dependency>
    <groupId>org.ttzero</groupId>
    <artifactId>eec</artifactId>
    <version>${eec.version}</version>
</dependency>

示例

1. 简单导出

使用SimpleSheet简单工作表导出Excel可以不必定义对象或者Map,直接添加单元格的值即可导出。

// 准备导出数据
List<Object> rows = new ArrayList<>();
rows.add(new String[] {"列1", "列2", "列3"});
rows.add(new int[] {1, 2, 3, 4});
rows.add(new Object[] {5, new Date(), 7, null, "字母", 9, 10.1243});

new Workbook()
    .addSheet(new SimpleSheet<>(rows)) // 添加一个简单工作表
    .writeTo(Paths.get("F:/excel")); // 导出到F:/excel目录下

simple_sheet

2. 对象导出

对象数组是使用最广泛的工作表,导出时需要在对象的属性上添加注解@ExcelColumn("列名")来标识当前属性可被导出,默认情况下导出的列顺序与字段在对象中的定义顺序一致,可以通过指定colIndex来重置列顺序。

// 定义导出对象
public class Student {
    @ExcelColumn("学号")
    private int id;

    @ExcelColumn("姓名")
    private String name;
}

// 创建一个名为"一年级学生表"的excel文件
new Workbook("一年级学生表")

    // 添加"工作表"并指定导出数据,可以通过addSheet添加多个worksheet
    .addSheet(new ListSheet<>("学生信息", students))

    // 指定输出位置,如果做文件导出可以直接输出到`respone.getOutputStream()`
    .writeTo(Paths.get("F:/excel"));

3. 分批写数据

数据量较大时一次取出可能造成OOM,此时就需要分批写数据以降低内存消耗,EEC支持PULL和PUSH两种模式写数据

  • PULL模式

PULL模式由WorksheetWriter触发more方法取数,写入数据超过单个Worksheet上限将自动分裂一个新的Worksheet并将数据写到新的Worksheet中

new Workbook()
    .addSheet(new ListSheet<Item>() {
        @Override
        public List<Item> more() {
            return service.scrollQuery(); // <- 滚动查询数据
        }
    })
    .writeTo(Paths.get("F:/excel/item-export.xlsx"));
  • PUSH模式

PUSH模式由外部主动向Worksheet推数据,实现IPushModelSheet接口的Worksheet支持直接写数据

Workbook workbook = new Workbook();
// 如果要设置额外的属性则需要在写数据前设置,否则不生效
workbook.setAutoSize(true);
workbook.setWatermark(Watermark.of("Hello EEC"));
// 实例化一个ListSheet
ListSheet<Item> sheet = new ListSheet<>();
// 添加进workbook后才能写数据
workbook.addSheet(sheet);

ItemQueryVO queryVO = new ItemQueryVO();
// 每页100条
queryVO.setPageSize(100);
for (int i = 0; ;) {
    queryVO.setPage(++i);
    // 分页查询
    List<Item> sub = service.pageQuery(queryVO);
    if (sub != null && !sub.isEmpty()) sheet.writeData(sub); // <- 直接写数据
    else break; // EOF
}
// 指定Excel输出位置
workbook.writeTo(Paths.get("F:/excel/item-export.xlsx"));

4. 动态样式

动态样式和数据转换都是使用@FunctionalInterface实现,通常用于突出或高亮显示一些重要的单元格或行,下面展示如何将低下60分的成绩输出为"不合格"并将整行标为橙色

new Workbook("2021小五班期未考试成绩")
    .addSheet(new ListSheet<>("期末成绩", students
         , new Column("学号", "id", int.class)
         , new Column("姓名", "name", String.class)
         , new Column("成绩", "score", int.class, n -> (int) n < 60 ? "不合格" : n)
    ).setStyleProcessor((o, style, sst) -> 
            o.getScore() < 60 ? sst.modifyFill(style, new Fill(PatternType.solid, Color.orange)) : style)
    ).writeTo(Paths.get("F:/excel"));

期未成绩

5. 支持模板导出

TemplateSheet工作表支持xls和xlsx模板格式,使用模板工作表可以合并多个Excel文件也可以和其它工作表混用,关于模板工作表请参考3-模板导出

new Workbook()
    
    // 复制[企业名片.xls]文件的[封面]工作表
    .addSheet(new TemplateSheet(Paths.get("./template/企业名片.xls", "封面"))
    
    // 复制[商品导入模板]的第一个工作表,并添加导出数据
    .addSheet(new TemplateSheet(Paths.get("./template/商品导入模板.xlsx"))
        .setData(Entity.mock()) // 设置对象 对应占位符${*}
        // 分片拉取数据 对应占位符${list.*}
        .setData("list", (i,lastOne) -> scrollQuery(i > 0 ? ((Product)lastOne).getId() : 0))
    ).writeTo(Paths.get("F:/excel"));

6. 自适应列宽更精准

// 测试类
public static class WidthTestItem {
    @ExcelColumn(value = "整型", format = "#,##0_);[Red]-#,##0_);0_)")
    private Integer nv;
    @ExcelColumn("字符串(en)")
    private String sen;
    @ExcelColumn("字符串(中文)")
    private String scn;
    @ExcelColumn(value = "日期时间", format = "yyyy-mm-dd hh:mm:ss")
    private Timestamp iv;
}

new Workbook("Auto Width Test")
    .setAutoSize(true) // <- 自适应列宽
    .addSheet(new ListSheet<>(randomTestData()))
    .writeTo(Paths.get("F:/excel"));

自动列宽

7. 支持多级表头

EEC使用多个ExcelColumn注解来实现多级表头,名称一样的行或列将自动合并

 public static class RepeatableEntry {
    @ExcelColumn("运单号")
    private String orderNo;
    @ExcelColumn("收件地址")
    @ExcelColumn("省")
    private String rProvince;
    @ExcelColumn("收件地址")
    @ExcelColumn("市")
    private String rCity;
    @ExcelColumn("收件地址")
    @ExcelColumn("详细地址")
    private String rDetail;
    @ExcelColumn("收件人")
    private String recipient;
    @ExcelColumn("寄件地址")
    @ExcelColumn("省")
    private String sProvince;
    @ExcelColumn("寄件地址")
    @ExcelColumn("市")
    private String sCity;
    @ExcelColumn("寄件地址")
    @ExcelColumn("详细地址")
    private String sDetail;
    @ExcelColumn("寄件人")
    private String sender;
}

多行表头

8. 报表轻松制作

现在使用普通的ListSheet就可以导出漂亮的报表。示例请跳转到 WIKI

报表1 报表2

9. 支持28种预设图片样式

导出图片时添加内置样式使其更美观,关于图片样式请参考1-导出Excel#导出图片

effect

读取示例

EEC使用ExcelReader#read静态方法读文件,其支持标准Stream所以可以直接使用map、filter和collect等JDK内置函数,读取Excel就像操作集合类一样简单,极大降低学习成本。

1. 使用Stream

try (ExcelReader reader = ExcelReader.read(Paths.get("./User.xlsx"))) {
    // 读取所有worksheet并输出
    reader.sheets().flatMap(Sheet::rows).forEach(System.out::println);
} catch (IOException e) {
    e.printStackTrace();
}

2. 读入到数组或List中

try (ExcelReader reader = ExcelReader.read(Paths.get("./User.xlsx"))) {
    List<User> users = reader.sheet(0) // 读取第1个Sheet页
        .header(6)                     // 指定第6行为表头
        .rows()                        // 读取数据行
        .map(row -> row.to(User.class))// 将每行数据转换为User对象
        .collect(Collectors.toList()); // 收集数据进行后续处理
} catch (IOException e) {
    e.printStackTrace();
}

3. 过滤和聚合

EEC支持Stream的大部分功能,以下代码使用filter来演示如何过滤平台为"iOS"的注册用户

reader.sheet(0).header(6)
    .rows()
    // 过滤平台为"iOS"的用户
    .filter(row -> "iOS".equals(row.getString("platform")))
    .map(row -> row.to(User.class))
    .collect(Collectors.toList());

4. 多级表头读取

多级表头可以使用header方法来指定表头所在的多个行号,多级表头将使用:拼接多个行单元格来组成一个聚合头

reader.sheet(0)
    .header(1, 2)    // <- 指定第1、2行均为表头
    .rows()
    .map(Row::toMap) // <- Row 转 Map
    .forEach(System.out::println);

# 输出如下(数据均随机生成所以输出与示例不一致)
运单号 | 收件地址:省 | 收件地址:市 | 收件地址:详细地址 | 收件人 | 寄件地址:省 | 寄件地址:市 | 寄件地址:详细地址 | 寄件人
921674764 | 湖北省 | 宜昌市 | xx街4号 | 王** | 江苏省 | 南京市 | xx街5号 | 周**
1512518776 | 广东省 | 佛山市 | xx街6号 | 王** | 广东省 | 广州市 | xx街7号 | 周**
1473338301 | 浙江省 | 杭州市 | xx街4号 | 王** | 湖北省 | 黄冈市 | xx街7号 | 周**
1484573956 | 湖北省 | 武汉市 | xx街4号 | 王** | 江苏省 | 南京市 | xx街9号 | 周**
1409795643 | 湖北省 | 黄冈市 | xx街3号 | 王** | 江苏省 | 南京市 | xx街1号 | 周**

多级表头将以A1:A2:A3这种格式进行纵向拼接,读取第6个示例中的运单数据读取结果将以运单号,收件地址:省,收件地址:市呈现,这样就可以解决出现两个省、市导致错乱的问题

更多关于多表头使用方法可以参考 WIKI

xls格式支持

pom.xml添加如下依赖,添加好后即完成了xls的兼容,是的!你不需要为xls格式添加任何一行代码。

<dependency>
    <groupId>org.ttzero</groupId>
    <artifactId>eec-e3-support</artifactId>
    <version>${eec-e3-support.version}</version>
</dependency>

读取xls的方法与xlsx完全一样,外部不需要判断是哪种格式,EEC为其提供了完全一样的接口,内部会根据文件头去判断具体类型,这种方式比简单判断文件后缀准确得多。

两个工具的兼容性 参考此表

CSV与Excel格式互转

  • CSV => Excel:向Workbook中添加一个CSVSheet工作表
  • Excel => CSV:读Excel时调用saveAsCSV另存为csv格式

代码示例

// 直接保存为csv生成测试文件,对于数据量较多的场合也可以使用#more方法分批获取数据
new Workbook()
    .addSheet(createTestData())
    .saveAsCSV() // 指定输出格式为csv
    .writeTo(Paths.get("F:/excel/abc.csv"));

// CSV转Excel
new Workbook()
    .addSheet(new CSVSheet(Paths.get("d:\\abc.csv"))) // 添加CSVSheet并指定csv路径
    .writeTo(Paths.get("F:/excel/abc.xlsx"));
    
// Excel转CSV
try (ExcelReader reader = ExcelReader.read(Paths.get("F:/excel/abc.xlsx"))) {
    // 读取Excel使用saveAsCSV保存为CSV格式
    reader.sheet(0).saveAsCSV(Paths.get("./"));
} catch (IOException e) {
    e.printStackTrace();
}

CHANGELOG

Version 0.5.30 (2026-09-16)

  • 增加单个Sheet页超4G的支持(#478)
  • 修复静态共享可变对象导致的线程安全问题(#479)
  • 修复文件超过2G导出内容有概率不完整的问题
  • 修复读科学计数法的检测问题

Version 0.5.29 (2026-07-03)

  • 修复静态共享可变对象导致的线程安全问题(#375,#473)

Version 0.5.28 (2026-04-26)

  • 修复FullSheet部分情况下读取公式时出现越界问题
  • 提供更丰富方法修改表头样式

Version 0.5.27 (2025-10-28)

  • 支持级联下拉框(#444)
  • 优化Validation内容超过255时打开文件异常的问题
  • TemplateSheet支持复制Validation

更多...

About

A fast and lower memory excel write/read tool.一个非POI底层,支持流式处理的高效且超低内存的Excel读写工具

Topics

Resources

Stars

341 stars

Watchers

15 watching

Forks

Releases

Packages

Used by

Contributors

Languages