Browse Source

优化读写逻辑

bugfix
zhuangjiaju 5 years ago
parent
commit
fe35e570c2
  1. 136
      README.md
  2. 13
      pom.xml
  3. 11
      quickstart.md
  4. 17
      src/test/java/com/alibaba/easyexcel/test/demo/read/DemoData.java
  5. 49
      src/test/java/com/alibaba/easyexcel/test/demo/read/DemoDataListener.java
  6. 28
      src/test/java/com/alibaba/easyexcel/test/demo/read/IndexOrNameData.java
  7. 49
      src/test/java/com/alibaba/easyexcel/test/demo/read/IndexOrNameDataListener.java
  8. 51
      src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java
  9. 12
      src/test/java/com/alibaba/easyexcel/test/demo/web/EasyexcelApplication.java
  10. 29
      src/test/java/com/alibaba/easyexcel/test/demo/write/DemoData.java
  11. 44
      src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java
  12. BIN
      src/test/resources/demo/demo.xlsx
  13. 60
      update.md

136
README.md

@ -13,7 +13,7 @@
---------------------------------- ----------------------------------
# JAVA解析Excel工具easyexcel # JAVA解析Excel工具easyexcel
Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存,poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题,但POI还是有一些缺陷,比如07版Excel解压缩以及解压后存储都是在内存中完成的,内存消耗依然很大。easyexcel重写了poi对07版Excel的解析,能够原本一个3M的excel用POI sax依然需要100M左右内存降低到KB级别,并且再大的excel不会出现内存溢出,03版依赖POI的sax模式。在上层做了模型转换的封装,让使用者更加简单方便 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模式。在上层做了模型转换的封装,让使用者更加简单方便
## 相关文档 ## 相关文档
* [关于软件](/abouteasyexcel.md) * [关于软件](/abouteasyexcel.md)
* [快速使用](/quickstart.md) * [快速使用](/quickstart.md)
@ -21,118 +21,50 @@ Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都
* [更新记事](/update.md) * [更新记事](/update.md)
* [English-README](/easyexcel_en.md) * [English-README](/easyexcel_en.md)
## 二方包 ## 二方包
```xml
<dependency> <dependency>
<groupId>com.alibaba</groupId> <groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId> <artifactId>easyexcel</artifactId>
<version>{latestVersion}</version> <version>2.0.0</version>
</dependency> </dependency>
```
## 最新版本:1.1.2-beta4 ## 最新版本:2.0.0
## 维护者 ## 维护者
姬朋飞(玉霄) 姬朋飞(玉霄)
## 快速开始 ## 快速开始
### 读Excel ### 读Excel
测试代码地址:[https://github.com/alibaba/easyexcel/blob/master/src/test/java/com/alibaba/easyexcel/test/ReadTest.java](/src/test/java/com/alibaba/easyexcel/test/ReadTest.java) DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/src/test/java/com/alibaba/easyexcel/demo/read/ReadTest.java](/src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java)
读07版小于1000行数据返回List<List<String>> ```java
``` /**
List<Object> data = EasyExcelFactory.read(inputStream, new Sheet(1, 0)); * 最简单的读
``` * <li>1. 创建excel对应的实体对象 参照{@link DemoData}
读07版小于1000行数据返回List<? extend BaseRowModel> * <li>2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
``` * <li>3. 直接读即可
List<Object> data = EasyExcelFactory.read(inputStream, new Sheet(2, 1,JavaModel.class)); */
``` @Test
读07版大于1000行数据返回List<List<String>> public void simpleRead() {
``` String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
ExcelListener excelListener = new ExcelListener(); // 这里 需要指定读用哪个class去读,然后读取第一个sheet 然后千万别忘记 finish
EasyExcelFactory.readBySax(inputStream, new Sheet(1, 1), excelListener); EasyExcelFactory.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead().finish();
}
``` ```
读07版大于1000行数据返回List<? extend BaseRowModel>
```
ExcelListener excelListener = new ExcelListener();
EasyExcelFactory.readBySax(inputStream, new Sheet(2, 1,JavaModel.class), excelListener);
```
读03版方法同上
### 写Excel ### 写Excel
测试代码地址:[https://github.com/alibaba/easyexcel/blob/master/src/test/java/com/alibaba/easyexcel/test/WriteTest.java](/src/test/java/com/alibaba/easyexcel/test/WriteTest.java) DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java](/src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java)
没有模板 ```java
```OutputStream out = new FileOutputStream("/Users/jipengfei/2007.xlsx"); /**
ExcelWriter writer = EasyExcelFactory.getWriter(out); * 最简单的写
* <li>1. 创建excel对应的实体对象 参照{@link com.alibaba.easyexcel.test.demo.write.DemoData}
//写第一个sheet, sheet1 数据全是List<String> 无模型映射关系 * <li>2. 直接写即可
Sheet sheet1 = new Sheet(1, 3); */
sheet1.setSheetName("第一个sheet"); @Test
//设置列宽 设置每列的宽度 public void simpleWrite() {
Map columnWidth = new HashMap(); String fileName = TestFileUtil.getPath() + "write.xlsx";
columnWidth.put(0,10000);columnWidth.put(1,40000);columnWidth.put(2,10000);columnWidth.put(3,10000); // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后千万别忘记 finish
sheet1.setColumnWidthMap(columnWidth); // 如果这里想使用03 则 传入excelType参数即可
sheet1.setHead(createTestListStringHead()); EasyExcelFactory.write(fileName, DemoData.class).sheet("模板").doWrite(data()).finish();
//or 设置自适应宽度 }
//sheet1.setAutoWidth(Boolean.TRUE);
writer.write1(createTestListObject(), sheet1);
//写第二个sheet sheet2 模型上打有表头的注解,合并单元格
Sheet sheet2 = new Sheet(2, 3, JavaModel1.class, "第二个sheet", null);
sheet2.setTableStyle(createTableStyle());
writer.write(createTestListJavaMode(), sheet2);
//写第三个sheet包含多个table情况
Sheet sheet3 = new Sheet(3, 0);
sheet3.setSheetName("第三个sheet");
Table table1 = new Table(1);
table1.setHead(createTestListStringHead());
writer.write1(createTestListObject(), sheet3, table1);
//写sheet2 模型上打有表头的注解
Table table2 = new Table(2);
table2.setTableStyle(createTableStyle());
table2.setClazz(JavaModel1.class);
writer.write(createTestListJavaMode(), sheet3, table2);
//关闭资源
writer.finish();
out.close();
```
有模板
```InputStream inputStream = new BufferedInputStream(new FileInputStream("/Users/jipengfei/temp.xlsx"));
OutputStream out = new FileOutputStream("/Users/jipengfei/2007.xlsx");
ExcelWriter writer = EasyExcelFactory.getWriterWithTemp(inputStream,out,ExcelTypeEnum.XLSX,true);
//写第一个sheet, sheet1 数据全是List<String> 无模型映射关系
Sheet sheet1 = new Sheet(1, 3);
sheet1.setSheetName("第一个sheet");
//设置列宽 设置每列的宽度
Map columnWidth = new HashMap();
columnWidth.put(0,10000);columnWidth.put(1,40000);columnWidth.put(2,10000);columnWidth.put(3,10000);
sheet1.setColumnWidthMap(columnWidth);
sheet1.setHead(createTestListStringHead());
//or 设置自适应宽度
//sheet1.setAutoWidth(Boolean.TRUE);
writer.write1(createTestListObject(), sheet1);
//写第二个sheet sheet2 模型上打有表头的注解,合并单元格
Sheet sheet2 = new Sheet(2, 3, JavaModel1.class, "第二个sheet", null);
sheet2.setTableStyle(createTableStyle());
writer.write(createTestListJavaMode(), sheet2);
//写第三个sheet包含多个table情况
Sheet sheet3 = new Sheet(3, 0);
sheet3.setSheetName("第三个sheet");
Table table1 = new Table(1);
table1.setHead(createTestListStringHead());
writer.write1(createTestListObject(), sheet3, table1);
//写sheet2 模型上打有表头的注解
Table table2 = new Table(2);
table2.setTableStyle(createTableStyle());
table2.setClazz(JavaModel1.class);
writer.write(createTestListJavaMode(), sheet3, table2);
//关闭资源
writer.finish();
out.close();
``` ```
### web下载实例写法 ### web下载实例写法

13
pom.xml

@ -81,6 +81,7 @@
<artifactId>ehcache</artifactId> <artifactId>ehcache</artifactId>
<version>3.7.1</version> <version>3.7.1</version>
</dependency> </dependency>
<!--test-->
<dependency> <dependency>
<groupId>ch.qos.logback</groupId> <groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId> <artifactId>logback-classic</artifactId>
@ -99,6 +100,18 @@
<version>1.18.8</version> <version>1.18.8</version>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<version>1.5.21.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>1.5.21.RELEASE</version>
<scope>test</scope>
</dependency>
<dependency> <dependency>
<groupId>junit</groupId> <groupId>junit</groupId>
<artifactId>junit</artifactId> <artifactId>junit</artifactId>

11
quickstart.md

@ -12,17 +12,6 @@
## *写Excel时一个sheet可以写多个Table<br /> ## *写Excel时一个sheet可以写多个Table<br />
## *写Excel时候自定义是否需要写表头<br /> ## *写Excel时候自定义是否需要写表头<br />
## 二方包依赖
使用前最好咨询下最新版,或者到mvn仓库搜索先easyexcel的最新版
```
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>1.0.0-RELEASE</version>
</dependency>
```
## 读Excel ## 读Excel
使用easyexcel解析03、07版本的Excel只是ExcelTypeEnum不同,其他使用完全相同,使用者无需知道底层解析的差异。 使用easyexcel解析03、07版本的Excel只是ExcelTypeEnum不同,其他使用完全相同,使用者无需知道底层解析的差异。

17
src/test/java/com/alibaba/easyexcel/test/demo/read/DemoData.java

@ -0,0 +1,17 @@
package com.alibaba.easyexcel.test.demo.read;
import java.util.Date;
import lombok.Data;
/**
* 基础数据类.这里的排序和excel里面的排序一致
*
* @author zhuangjiaju
**/
@Data
public class DemoData {
private String string;
private Date date;
private Double doubleData;
}

49
src/test/java/com/alibaba/easyexcel/test/demo/read/DemoDataListener.java

@ -0,0 +1,49 @@
package com.alibaba.easyexcel.test.demo.read;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.fastjson.JSON;
/**
* 模板的读取类
*
* @author zhuangjiaju
*/
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>();
@Override
public void invoke(DemoData data, AnalysisContext context) {
LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data));
list.add(data);
if (list.size() >= BATCH_COUNT) {
saveData();
list.clear();
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
saveData();
LOGGER.info("所有数据解析完成!");
}
/**
* 加上存储数据库
*/
private void saveData() {
LOGGER.info("{}条数据,开始存储数据库!", list.size());
LOGGER.info("存储数据库成功!");
}
}

28
src/test/java/com/alibaba/easyexcel/test/demo/read/IndexOrNameData.java

@ -0,0 +1,28 @@
package com.alibaba.easyexcel.test.demo.read;
import java.util.Date;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
/**
* 基础数据类
*
* @author zhuangjiaju
**/
@Data
public class IndexOrNameData {
/**
* 强制读取第三个 这里不建议 index name 同时用要么一个对象只用index要么一个对象只用name去匹配
*/
@ExcelProperty(index = 2)
private Double doubleData;
/**
* 用名字去匹配这里需要注意如果名字重复会导致只有一个字段读取到数据
*/
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
}

49
src/test/java/com/alibaba/easyexcel/test/demo/read/IndexOrNameDataListener.java

@ -0,0 +1,49 @@
package com.alibaba.easyexcel.test.demo.read;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.fastjson.JSON;
/**
* 模板的读取类
*
* @author zhuangjiaju
*/
public class IndexOrNameDataListener extends AnalysisEventListener<IndexOrNameData> {
private static final Logger LOGGER = LoggerFactory.getLogger(IndexOrNameDataListener.class);
/**
* 每隔5条存储数据库实际使用中可以3000条然后清理list 方便内存回收
*/
private static final int BATCH_COUNT = 5;
List<IndexOrNameData> list = new ArrayList<IndexOrNameData>();
@Override
public void invoke(IndexOrNameData data, AnalysisContext context) {
LOGGER.info("解析到一条数据:{}", JSON.toJSONString(data));
list.add(data);
if (list.size() >= BATCH_COUNT) {
saveData();
list.clear();
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
saveData();
LOGGER.info("所有数据解析完成!");
}
/**
* 加上存储数据库
*/
private void saveData() {
LOGGER.info("{}条数据,开始存储数据库!", list.size());
LOGGER.info("存储数据库成功!");
}
}

51
src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java

@ -0,0 +1,51 @@
package com.alibaba.easyexcel.test.demo.read;
import java.io.File;
import org.junit.Ignore;
import org.junit.Test;
import com.alibaba.easyexcel.test.util.TestFileUtil;
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.read.metadata.ReadSheet;
/**
* 读的常见写法
*
* @author zhuangjiaju
*/
@Ignore
public class ReadTest {
/**
* 最简单的读
* <li>1. 创建excel对应的实体对象 参照{@link DemoData}
* <li>2. 由于默认异步读取excel所以需要创建excel一行一行的回调监听器参照{@link DemoDataListener}
* <li>3. 直接读即可
*/
@Test
public void simpleRead() {
// 写法1:
String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 然后千万别忘记 finish
EasyExcelFactory.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead().finish();
// 写法2:
fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
ExcelReader excelReader = EasyExcelFactory.read(fileName, DemoData.class, new DemoDataListener()).build();
ReadSheet readSheet = EasyExcelFactory.readSheet(0).build();
excelReader.read(readSheet);
excelReader.finish();
}
/**
* 指定列的下标或者列名
* <li>使用{@link com.alibaba.excel.annotation.ExcelProperty}注解即可
*/
@Test
public void indexOrNameRead() {
String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
// 这里默认读取第一个sheet 然后千万别忘记 finish
EasyExcelFactory.read(fileName, IndexOrNameData.class, new IndexOrNameDataListener()).sheet().doRead().finish();
}
}

12
src/test/java/com/alibaba/easyexcel/test/demo/web/EasyexcelApplication.java

@ -0,0 +1,12 @@
package com.alibaba.easyexcel.test.demo.web;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class EasyexcelApplication {
public static void main(String[] args) {
SpringApplication.run(EasyexcelApplication.class, args);
}
}

29
src/test/java/com/alibaba/easyexcel/test/demo/write/DemoData.java

@ -0,0 +1,29 @@
package com.alibaba.easyexcel.test.demo.write;
import java.util.Date;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
/**
* 基础数据类.行高 列宽可以忽略
*
* @author zhuangjiaju
**/
@Data
public class DemoData {
@ExcelProperty("字符串标题")
private String string;
/**
* 指定写到excel的格式
*/
@ExcelProperty("日期标题")
private Date date;
/**
* 数字转成百分比默认数字存到excel是数字不存在格式现在要变成百分比就变成文本了所以要指定转换器
*/
@ExcelProperty("数字标题")
private Double doubleData;
}

44
src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java

@ -0,0 +1,44 @@
package com.alibaba.easyexcel.test.demo.write;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.junit.Ignore;
import org.junit.Test;
import com.alibaba.easyexcel.test.util.TestFileUtil;
import com.alibaba.excel.EasyExcelFactory;
/**
* 写的常见写法
*
* @author zhuangjiaju
*/
@Ignore
public class WriteTest {
/**
* 最简单的写
* <li>1. 创建excel对应的实体对象 参照{@link com.alibaba.easyexcel.test.demo.write.DemoData}
* <li>2. 直接写即可
*/
@Test
public void simpleWrite() {
String fileName = TestFileUtil.getPath() + "write" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后千万别忘记 finish
// 如果这里想使用03 则 传入excelType参数即可
EasyExcelFactory.write(fileName, DemoData.class).sheet("模板").doWrite(data()).finish();
}
private List<DemoData> data() {
List<DemoData> list = new ArrayList<DemoData>();
for (int i = 0; i < 10; i++) {
DemoData data = new DemoData();
data.setString("字符串" + 0);
data.setDate(new Date());
data.setDoubleData(0.56);
list.add(data);
}
return list;
}
}

BIN
src/test/resources/demo/demo.xlsx

Binary file not shown.

60
update.md

@ -1,39 +1,25 @@
# 1.0.1 # 2.0.0
* 优化读写逻辑
完善测试用例,防止歧义,模型字段映射不上时候有抛异常,改为提醒。 * 优化读写对外接口
* 加入转换器,方便格式转换
# 1.0.2 * 极大优化读大文件的内存和效率
修复拿到一行数据后,存到list中,但最后处理时候变为空的bug。
# 1.0.3
修复无@ExcelProperty标注的多余字段时候报错。
# 1.0.4
修复日期类型转换时候数字问题。基础模型支持字段类型int,long,double,boolean,date,string
# 1.0.5
优化类型转换的性能。
# 1.0.6
增加@ExcelColumnNum,修复字符串前后空白,增加过滤功能。
# 1.0.8
如果整行excel数据全部为空,则不解析返回。完善多sheet的解析。
# 1.0.9
修复excel超过16列被覆盖的问题,修复数据只有一行时候无法透传的bug。
# 1.2.1
修复POI在大并发情况下创建临时目录失败的bug
# 1.2.4 # 1.2.4
修复read()方法存在的bug 修复read()方法存在的bug
# 1.2.1
修复POI在大并发情况下创建临时目录失败的bug
# 1.0.9
修复excel超过16列被覆盖的问题,修复数据只有一行时候无法透传的bug。
# 1.0.8
如果整行excel数据全部为空,则不解析返回。完善多sheet的解析。
# 1.0.6
增加@ExcelColumnNum,修复字符串前后空白,增加过滤功能。
# 1.0.5
优化类型转换的性能。
# 1.0.4
修复日期类型转换时候数字问题。基础模型支持字段类型int,long,double,boolean,date,string
# 1.0.3
修复无@ExcelProperty标注的多余字段时候报错。
# 1.0.2
修复拿到一行数据后,存到list中,但最后处理时候变为空的bug。
# 1.0.1
完善测试用例,防止歧义,模型字段映射不上时候有抛异常,改为提醒。
Loading…
Cancel
Save