Apache poi
官网https://poi.apache.org/
Apache poi是Apache软件基金会的开放源码函式库,poi提供API给Java程序对Microsoft Office格式档案读和写的功能。
基本功能:
HSSF - 提供读写Microsoft Excel格式档案的功能。
XSSF - 提供读写Microsoft Excel OOXML格式档案的功能。
HWPF - 提供读写Microsoft Word格式档案的功能。
HSLF - 提供读写Microsoft PowerPoint格式档案的功能。
HDGF - 提供读写Microsoft Visio格式档案的功能。
easyExcel
官网https://github.com/alibaba/easyexcel/
Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel重写了poi对07版Excel的解析,能够原本一个3M的excel用POI sax依然需要100M左右内存降低到几M,并且再大的excel不会出现内存溢出,03版依赖POI的sax模式。在上层做了模型转换的封装,让使用者更加简单方便。
64M内存1分钟内读取75M(46W行25列)的Excel
当然还有急速模式能更快,但是内存占用会在100M多一点 。
内存问题:
POI:会将数据先加载到内存(数据量大会导致OOM问题),再将数据写入到文件中去。
excel是有03版和07版本之分,03版本后缀名是xls,限制65536行,07版本后缀名是xlsx,没有限制行数。
poi测试
pom.xml导入依赖
<!-- xls03版 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.0.0</version>
</dependency>
<!-- xlsx07版 -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.0.0</version>
</dependency>
<!-- 日期格式化工具 -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10.4</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.7.1</version>
<scope>test</scope>
</dependency>
03版本写入的测试方法,用的类是HSSFWorkbook,文件后缀名是xls。
@Test
public void test() throws IOException {
//03版本excel
//创建一个工作簿
Workbook workbook = new HSSFWorkbook();
//创建工作表
Sheet sheet = workbook.createSheet("测试工作表");
//创建行,默认是从0开始
Row row1 = sheet.createRow(0);
//创建一个单元格,跟行结合是第一行第一列
Cell cell1 = row1.createCell(0);
//插入数据
cell1.setCellValue("测试数据");
Cell cell2 = row1.createCell(1);
cell2.setCellValue(new DateTime().toString("yyyy-MM-dd HH:mm:ss"));
//生成一张表
FileOutputStream outputStream = new FileOutputStream("C:\\Users\\wdn\\Desktop\\测试.xls");
workbook.write(outputStream);
//关闭流
outputStream.close();
}
07版本测试写入方法,用的类是XSSFWorkbook,文件后缀名是xlsx。
@Test
public void test() throws IOException {
//07版本excel
//创建一个工作簿
Workbook workbook = new XSSFWorkbook();
//创建工作表
Sheet sheet = workbook.createSheet("测试工作表");
//创建行,默认是从0开始
Row row1 = sheet.createRow(0);
//创建一个单元格,跟行结合是第一行第一列
Cell cell1 = row1.createCell(0);
//插入数据
cell1.setCellValue("测试数据");
Cell cell2 = row1.createCell(1);
cell2.setCellValue(new DateTime().toString("yyyy-MM-dd HH:mm:ss"));
//生成一张表
FileOutputStream outputStream = new FileOutputStream("C:\\Users\\wdn\\Desktop\\测试.xlsx");
workbook.write(outputStream);
//关闭流
outputStream.close();
}
大文件写HSSF
缺点:最多只能处理65536,否则会抛出异常。
优点:过程中写入缓存,不操作磁盘,最后一次性写入磁盘,速度快。
大文件写XSSF
缺点:写数据时速度非常慢,非常耗内存,也会发生内存溢出,如100万条。
优点:可以写较大的数据量,如20万条。
大文件写SXSSF
优点:可以写非常大的数据量,如100万条甚至更多条,写数据速度快,占用更少的内存。
注意:过程中会产生临时文件,需要清理临时文件;默认由100条记录被保存在内存中,如果超过这数量,则最前面的数据被写入临时文件;如果想自定义内存中的数据的数量,可以使用new SXSSFWorkbook(数量)。
03版本读取的测试方法
//文件路径
static String path = "C:\\Users\\wdn\\Desktop\\测试.xls";
@Test
public void test() throws IOException {
FileInputStream inputStream = new FileInputStream(path);
//创建工作簿
Workbook workbook = new HSSFWorkbook(inputStream);
//获取表,每一个工作簿至少有一个工作表
Sheet sheetAt = workbook.getSheetAt(0);
//获取行数
int rowCount = sheetAt.getPhysicalNumberOfRows();
for (int i = 0; i < rowCount; i++) {
//获取每一行对象
Row row = sheetAt.getRow(i);
if(row != null){
//获取列数
int cellCount = row.getPhysicalNumberOfCells();
for (int j = 0; j < cellCount; j++) {
//获取列对象
Cell cell = row.getCell(j);
if(cell != null){
switch (cell.getCellType()){
case STRING: //字符串
System.out.println(cell.getStringCellValue());
break;
case NUMERIC: //数字(日期和普通数字)
if(DateUtil.isCellDateFormatted(cell)){//判断该列是否是日期
System.out.println(new DateTime(cell.getDateCellValue()).toString("yyy-MM-dd HH:mm:ss"));
}else{
System.out.println(cell.getNumericCellValue());
}
break;
case BLANK: //空
System.out.println("");
break;
}
}
}
}
}
}
07版本读取的测试方法跟03版本差不多,这里就不写了,主要是要把文件路径改成.xlsx后缀名的文件,以及类HSSFWorkbook改成XSSFWorkbook
easyExcel
pom.xml依赖导入
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.7</version>
</dependency>
官网的例子:
先创建个基础数据类
/**
* 基础数据类
**/
@Data
public class DemoData {
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
@ExcelProperty("数字标题")
private Double doubleData;
/**
* 忽略这个字段
*/
@ExcelIgnore
private String ignore;
}
EasyExcel写入测试方法
//路径
static String path = "C:\\Users\\wdn\\Desktop\\";
@Test
public void simpleWrite() {
// 写法1
String fileName = path + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
// 如果这里想使用03 则 传入excelType参数即可
EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
// 写法2
fileName = path + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写
ExcelWriter excelWriter = null;
try {
excelWriter = EasyExcel.write(fileName, DemoData.class).build();
WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
excelWriter.write(data(), writeSheet);
} finally {
// 千万别忘记finish 会帮忙关闭流
if (excelWriter != null) {
excelWriter.finish();
}
}
}
public List<DemoData> data(){
List<DemoData> list = new ArrayList<>();
DemoData demoData = new DemoData();
demoData.setString("你好,小young");
demoData.setDate(new Date());
demoData.setDoubleData(18.18);
demoData.setIgnore("忽略");
list.add(demoData);
return list;
}
EasyExcel读取测试方法
首先创建监听器类,里面的监听方法可以根据自己需求,具体参考官网模板
/**
* 模板的读取类
*/
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
public class DemoDataListener extends AnalysisEventListener<DemoData> {
private static final Logger LOGGER = LoggerFactory.getLogger(DemoDataListener.class);
/**
* 每隔5条存储数据库,实际使用中可以3000条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 5;
List<DemoData> list = new ArrayList<DemoData>();
/**
* 这个每一条数据解析都会来调用
*
* @param data
* one row value. Is is same as {@link AnalysisContext#readRowHolder()}
* @param context
*/
@Override
public void invoke(DemoData data, AnalysisContext context) {
LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data));
list.add(data);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (list.size() >= BATCH_COUNT) {
list.stream().forEach(item->{
System.out.println(item.toString());
});
// 存储完成清理 list
list.clear();
}
}
/**
* 所有数据解析完成了 都会来调用
*
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
LOGGER.info("所有数据解析完成!");
}
}
具体测试方法
static String path = "C:\\Users\\wdn\\Desktop\\";
@Test
public void simpleRead() {
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
// 写法1:
String fileName = path + "demo.xlsx";
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
// 写法2:
//fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
ExcelReader excelReader = null;
try {
excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build();
ReadSheet readSheet = EasyExcel.readSheet(0).build();
excelReader.read(readSheet);
} finally {
if (excelReader != null) {
// 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
excelReader.finish();
}
}
}
总结
此篇有参考b站狂神说的poi和easyExcel视频,大家也可以去了解了解,具体easyExcel的用法大家还是要参考官网的源码,在此祝愿大家编程路上bug越写越少,只要你够强,bug就追不上你(开玩笑)。