Browse Source

优化读写逻辑

pull/507/head
zhuangjiaju 5 years ago
parent
commit
22dc33d4da
  1. 4
      README.md
  2. BIN
      img/readme/quickstart/read/demo.png
  3. 565
      quickstart.md
  4. 16
      src/main/java/com/alibaba/excel/context/WriteContextImpl.java
  5. 132
      src/main/java/com/alibaba/excel/converters/DefaultConverterLoader.java
  6. 12
      src/main/java/com/alibaba/excel/enums/WriteLastRowType.java
  7. 13
      src/main/java/com/alibaba/excel/util/WorkBookUtil.java
  8. 18
      src/main/java/com/alibaba/excel/write/ExcelBuilderImpl.java
  9. 17
      src/main/java/com/alibaba/excel/write/merge/LoopMergeStrategy.java
  10. 35
      src/main/java/com/alibaba/excel/write/metadata/holder/WriteSheetHolder.java
  11. 14
      src/main/java/com/alibaba/excel/write/metadata/holder/WriteWorkbookHolder.java
  12. 14
      src/main/java/com/alibaba/excel/write/property/ExcelWriteHeadProperty.java
  13. 4
      src/test/java/com/alibaba/easyexcel/test/core/template/TemplateDataTest.java
  14. 54
      src/test/java/com/alibaba/easyexcel/test/demo/poi/PoiTest.java
  15. 14
      src/test/java/com/alibaba/easyexcel/test/demo/read/ReadTest.java
  16. 2
      src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java
  17. 22
      src/test/java/com/alibaba/easyexcel/test/demo/write/ComplexHeadData.java
  18. 35
      src/test/java/com/alibaba/easyexcel/test/demo/write/ConverterData.java
  19. 59
      src/test/java/com/alibaba/easyexcel/test/demo/write/CustomStringStringConverter.java
  20. 25
      src/test/java/com/alibaba/easyexcel/test/demo/write/IndexData.java
  21. 32
      src/test/java/com/alibaba/easyexcel/test/demo/write/WidthAndHeightData.java
  22. 185
      src/test/java/com/alibaba/easyexcel/test/demo/write/WriteTest.java
  23. BIN
      src/test/resources/demo/demo.xlsx
  24. BIN
      src/test/resources/template/template03.xls
  25. BIN
      src/test/resources/template/template07.xlsx
  26. 2
      update.md

4
README.md

@ -15,8 +15,8 @@
# JAVA解析Excel工具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模式。在上层做了模型转换的封装,让使用者更加简单方便
## 相关文档
* [关于软件](/abouteasyexcel.md)
* [快速使用](/quickstart.md)
* [关于软件](/abouteasyexcel.md)
* [常见问题](/problem.md)
* [更新记事](/update.md)
* [English-README](/easyexcel_en.md)
@ -74,7 +74,7 @@ DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/src/test/ja
* 文件下载
* <li>1. 创建excel对应的实体对象 参照{@link DownloadData}
* <li>2. 设置返回的 参数
* <li>3. 直接写,这里注意,finish的时候会自动关闭OutputStream,当然你外面再关闭异常问题不大
* <li>3. 直接写,这里注意,finish的时候会自动关闭OutputStream,当然你外面再关闭问题不大
*/
@GetMapping("download")
public void download(HttpServletResponse response) throws IOException {

BIN
img/readme/quickstart/read/demo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

565
quickstart.md

@ -1,4 +1,10 @@
# easyexcel核心功能
## 目录
### 读
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)
* [最简单的读](#simpleRead)
### 写
## *读任意大小的03、07版Excel不会OO]<br />
## *读Excel自动通过注解,把结果映射为java模型<br />
@ -12,343 +18,304 @@
## *写Excel时一个sheet可以写多个Table<br />
## *写Excel时候自定义是否需要写表头<br />
## 读Excel
使用easyexcel解析03、07版本的Excel只是ExcelTypeEnum不同,其他使用完全相同,使用者无需知道底层解析的差异。
### 无java模型直接把excel解析的每行结果以List&lt;String&gt;返回 在ExcelListener获取解析结果
读excel代码示例如下:
## 读excel样例
### 最简单的读<span id="simpleRead" />
#### excel示例<span id="simpleReadExcel" />
![img](img/readme/quickstart/read/demo.png)
#### 对象<span id="simpleReadObject" />
```java
@Data
public class DemoData {
private String string;
private Date date;
private Double doubleData;
}
```
@Test
public void testExcel2003NoModel() {
InputStream inputStream = getInputStream("loan1.xls");
try {
// 解析每行结果在listener中处理
ExcelListener listener = new ExcelListener();
ExcelReader excelReader = new ExcelReader(inputStream, ExcelTypeEnum.XLS, null, listener);
excelReader.read();
} catch (Exception e) {
} finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
#### 监听器<span id="simpleReadListener" />
```java
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();
}
}
```
ExcelListener示例代码如下:
```
/* 解析监听器,
* 每解析一行会回调invoke()方法。
* 整个excel解析结束会执行doAfterAllAnalysed()方法
*
* 下面只是我写的一个样例而已,可以根据自己的逻辑修改该类。
* @author jipengfei
* @date 2017/03/14
*/
public class ExcelListener extends AnalysisEventListener {
//自定义用于暂时存储data。
//可以通过实例获取该值
private List<Object> datas = new ArrayList<Object>();
public void invoke(Object object, AnalysisContext context) {
System.out.println("当前行:"+context.getCurrentRowNum());
System.out.println(object);
datas.add(object);//数据存储到list,供批量处理,或后续自己业务逻辑处理。
doSomething(object);//根据自己业务做处理
}
private void doSomething(Object object) {
//1、入库调用接口
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// datas.clear();//解析结束销毁不用的资源
}
public List<Object> getDatas() {
return datas;
saveData();
LOGGER.info("所有数据解析完成!");
}
public void setDatas(List<Object> datas) {
this.datas = datas;
/**
* 加上存储数据库
*/
private void saveData() {
LOGGER.info("{}条数据,开始存储数据库!", list.size());
LOGGER.info("存储数据库成功!");
}
}
```
### 有java模型映射
java模型写法如下:
#### 代码
```java
/**
* 最简单的读
* <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 文件流会自动关闭
EasyExcelFactory.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
// 写法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();
}
```
public class LoanInfo extends BaseRowModel {
@ExcelProperty(index = 0)
private String bankLoanId;
@ExcelProperty(index = 1)
private Long customerId;
@ExcelProperty(index = 2,format = "yyyy/MM/dd")
private Date loanDate;
@ExcelProperty(index = 3)
private BigDecimal quota;
@ExcelProperty(index = 4)
private String bankInterestRate;
@ExcelProperty(index = 5)
private Integer loanTerm;
@ExcelProperty(index = 6,format = "yyyy/MM/dd")
private Date loanEndDate;
@ExcelProperty(index = 7)
private BigDecimal interestPerMonth;
@ExcelProperty(value = {"一级表头","二级表头"})
private BigDecimal sax;
### 指定列的下标或者列名<span id="indexOrNameRead" />
#### excel示例
参照:[excel示例](#simpleReadExcel)
#### 对象
```java
@Data
public class IndexOrNameData {
/**
* 强制读取第三个 这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去匹配
*/
@ExcelProperty(index = 2)
private Double doubleData;
/**
* 用名字去匹配,这里需要注意,如果名字重复,会导致只有一个字段读取到数据
*/
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
}
```
@ExcelProperty(index = 3)数字代表该字段与excel对应列号做映射,也可以采用 @ExcelProperty(value = {"一级表头","二级表头"})用于解决不确切知道excel第几列和该字段映射,位置不固定,但表头的内容知道的情况。
```
#### 监听器
参照:[监听器](#simpleReadListener) 只是泛型变了而已
#### 代码
```java
/**
* 指定列的下标或者列名
*
* <li>1. 创建excel对应的实体对象,并使用{@link ExcelProperty}注解. 参照{@link IndexOrNameData}
* <li>2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link IndexOrNameDataListener}
* <li>3. 直接读即可
*/
@Test
public void testExcel2003WithReflectModel() {
InputStream inputStream = getInputStream("loan1.xls");
try {
// 解析每行结果在listener中处理
AnalysisEventListener listener = new ExcelListener();
ExcelReader excelReader = new ExcelReader(inputStream, ExcelTypeEnum.XLS, null, listener);
excelReader.read(new Sheet(1, 2, LoanInfo.class));
} catch (Exception e) {
} finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public void indexOrNameRead() {
String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
// 这里默认读取第一个sheet
EasyExcelFactory.read(fileName, IndexOrNameData.class, new IndexOrNameDataListener()).sheet().doRead();
}
```
带模型解析与不带模型解析主要在构造new Sheet(1, 2, LoanInfo.class)时候包含class。Class需要继承BaseRowModel暂时BaseRowModel没有任何内容,后面升级可能会增加一些默认的数据。
## 写Excel
### 每行数据是List&lt;String&gt;无表头
### 读多个sheet<span id="repeatedRead" />
#### excel示例
参照:[excel示例](#simpleReadExcel)
#### 对象
参照:[对象](#simpleReadObject)
#### 监听器
参照:[监听器](#simpleReadListener)
#### 代码
```java
/**
* 读多个sheet,这里注意一个sheet不能读取多次,一定要多次需要重新读取文件
* <li>1. 创建excel对应的实体对象 参照{@link DemoData}
* <li>2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
* <li>3. 直接读即可
*/
@Test
public void repeatedRead() {
String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
ExcelReader excelReader = EasyExcelFactory.read(fileName, DemoData.class, new DemoDataListener()).build();
ReadSheet readSheet1 = EasyExcelFactory.readSheet(0).build();
ReadSheet readSheet2 = EasyExcelFactory.readSheet(1).build();
excelReader.read(readSheet1);
excelReader.read(readSheet2);
// 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
excelReader.finish();
}
```
OutputStream out = new FileOutputStream("/Users/jipengfei/77.xlsx");
try {
ExcelWriter writer = new ExcelWriter(out, ExcelTypeEnum.XLSX,false);
//写第一个sheet, sheet1 数据全是List<String> 无模型映射关系
Sheet sheet1 = new Sheet(1, 0);
sheet1.setSheetName("第一个sheet");
writer.write(getListString(), sheet1);
writer.finish();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
```
### 每行数据是一个java模型有表头----表头层级为一
生成Excel格式如下图
![屏幕快照 2017-06-02 上午9.49.39.png](http://ata2-img.cn-hangzhou.img-pub.aliyun-inc.com/dfcb44d05380e2e26bce93f850d9fc99.png)
模型写法如下:
```
public class ExcelPropertyIndexModel extends BaseRowModel {
@ExcelProperty(value = "姓名" ,index = 0)
private String name;
@ExcelProperty(value = "年龄",index = 1)
private String age;
@ExcelProperty(value = "邮箱",index = 2)
private String email;
@ExcelProperty(value = "地址",index = 3)
private String address;
@ExcelProperty(value = "性别",index = 4)
private String sax;
@ExcelProperty(value = "高度",index = 5)
private String heigh;
@ExcelProperty(value = "备注",index = 6)
private String last;
### 日期、数字或者自定义格式转换<span id="converterRead" />
#### excel示例
参照:[excel示例](#simpleReadExcel)
#### 对象
```java
@Data
public class ConverterData {
/**
* 我自定义 转换器,不管数据库传过来什么 。我给他加上“自定义:”
*/
@ExcelProperty(converter = CustomStringStringConverter.class)
private String string;
/**
* 这里用string 去接日期才能格式化。我想接收年月日格式
*/
@DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
private String date;
/**
* 我想接收百分比的数字
*/
@NumberFormat("#.##%")
private String doubleData;
}
```
@ExcelProperty(value = "姓名",index = 0) value是表头数据,默认会写在excel的表头位置,index代表第几列。
```
@Test
public void test1() throws FileNotFoundException {
OutputStream out = new FileOutputStream("/Users/jipengfei/78.xlsx");
try {
ExcelWriter writer = new ExcelWriter(out, ExcelTypeEnum.XLSX);
//写第一个sheet, sheet1 数据全是List<String> 无模型映射关系
Sheet sheet1 = new Sheet(1, 0,ExcelPropertyIndexModel.class);
writer.write(getData(), sheet1);
writer.finish();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
#### 监听器
参照:[监听器](#simpleReadListener) 只是泛型变了
#### 自定义转换器
````java
public class CustomStringStringConverter implements Converter<String> {
@Override
public Class supportJavaTypeKey() {
return String.class;
}
```
### 每行数据是一个java模型有表头----表头层级为多层级
生成Excel格式如下图:
![屏幕快照 2017-06-02 上午9.53.07.png](http://ata2-img.cn-hangzhou.img-pub.aliyun-inc.com/0cdb1673665e7940cd670871afb4b3d7.png)
java模型写法如下:
```
public class MultiLineHeadExcelModel extends BaseRowModel {
@ExcelProperty(value = {"表头1","表头1","表头31"},index = 0)
private String p1;
@ExcelProperty(value = {"表头1","表头1","表头32"},index = 1)
private String p2;
@ExcelProperty(value = {"表头3","表头3","表头3"},index = 2)
private int p3;
@ExcelProperty(value = {"表头4","表头4","表头4"},index = 3)
private long p4;
@ExcelProperty(value = {"表头5","表头51","表头52"},index = 4)
private String p5;
@ExcelProperty(value = {"表头6","表头61","表头611"},index = 5)
private String p6;
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
@ExcelProperty(value = {"表头6","表头61","表头612"},index = 6)
private String p7;
/**
* 这里读的时候会调用
*
* @param cellData
* NotNull
* @param contentProperty
* Nullable
* @param globalConfiguration
* NotNull
* @return
*/
@Override
public String convertToJavaData(CellData cellData, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
return "自定义:" + cellData.getStringValue();
}
@ExcelProperty(value = {"表头6","表头62","表头621"},index = 7)
private String p8;
/**
* 这里是写的时候会调用 不用管
*
* @param value
* NotNull
* @param contentProperty
* Nullable
* @param globalConfiguration
* NotNull
* @return
*/
@Override
public CellData convertToExcelData(String value, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
return new CellData(value);
}
@ExcelProperty(value = {"表头6","表头62","表头622"},index = 8)
private String p9;
}
```
写Excel写法同上,只需将ExcelPropertyIndexModel.class改为MultiLineHeadExcelModel.class
### 一个Excel多个sheet写法
```
@Test
public void test1() throws FileNotFoundException {
OutputStream out = new FileOutputStream("/Users/jipengfei/77.xlsx");
try {
ExcelWriter writer = new ExcelWriter(out, ExcelTypeEnum.XLSX,false);
//写第一个sheet, sheet1 数据全是List<String> 无模型映射关系
Sheet sheet1 = new Sheet(1, 0);
sheet1.setSheetName("第一个sheet");
writer.write(getListString(), sheet1);
//写第二个sheet sheet2 模型上打有表头的注解,合并单元格
Sheet sheet2 = new Sheet(2, 3, MultiLineHeadExcelModel.class, "第二个sheet", null);
sheet2.setTableStyle(getTableStyle1());
writer.write(getModeldatas(), sheet2);
//写sheet3 模型上没有注解,表头数据动态传入
List<List<String>> head = new ArrayList<List<String>>();
List<String> headCoulumn1 = new ArrayList<String>();
List<String> headCoulumn2 = new ArrayList<String>();
List<String> headCoulumn3 = new ArrayList<String>();
headCoulumn1.add("第一列");
headCoulumn2.add("第二列");
headCoulumn3.add("第三列");
head.add(headCoulumn1);
head.add(headCoulumn2);
head.add(headCoulumn3);
Sheet sheet3 = new Sheet(3, 1, NoAnnModel.class, "第三个sheet", head);
writer.write(getNoAnnModels(), sheet3);
writer.finish();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
````
#### 代码
```java
/**
* 日期、数字或者自定义格式转换
* <p>
* 默认读的转换器{@link DefaultConverterLoader#loadDefaultReadConverter()}
* <li>1. 创建excel对应的实体对象 参照{@link ConverterData}.里面可以使用注解{@link DateTimeFormat}、{@link NumberFormat}或者自定义注解
* <li>2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link ConverterDataListener}
* <li>3. 直接读即可
*/
@Test
public void converterRead() {
String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 然后千万别忘记 finish
EasyExcelFactory.read(fileName, ConverterData.class, new ConverterDataListener())
// 这里注意 我们也可以registerConverter来指定自定义转换器, 但是这个转换变成全局了, 所有java为string,excel为string的都会用这个转换器。
// 如果就想单个字段使用请使用@ExcelProperty 指定converter
// .registerConverter(new CustomStringStringConverter())
// 读取sheet
.sheet().doRead();
}
```
### 一个sheet中有多个表格
### 多行头<span id="complexHeaderRead" />
#### excel示例
参照:[excel示例](#simpleReadExcel)
#### 对象
参照:[对象](#simpleReadObject)
#### 监听器
参照:[监听器](#simpleReadListener)
#### 代码
```java
/**
* 多行头
*
* <li>1. 创建excel对应的实体对象 参照{@link DemoData}
* <li>2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
* <li>3. 设置headRowNumber参数,然后读。 这里要注意headRowNumber如果不指定, 会根据你传入的class的{@link ExcelProperty#value()}里面的表头的数量来决定行数,
* 如果不传入class则默认为1.当然你指定了headRowNumber不管是否传入class都是以你传入的为准。
*/
@Test
public void complexHeaderRead() {
String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 然后千万别忘记 finish
EasyExcelFactory.read(fileName, DemoData.class, new DemoDataListener()).sheet()
// 这里可以设置1,因为头就是一行。如果多行头,可以设置其他值。不传入也可以,因为默认会根据DemoData 来解析,他没有指定头,也就是默认1行
.headRowNumber(1).doRead();
}
```
@Test
public void test2() throws FileNotFoundException {
OutputStream out = new FileOutputStream("/Users/jipengfei/77.xlsx");
try {
ExcelWriter writer = new ExcelWriter(out, ExcelTypeEnum.XLSX,false);
//写sheet1 数据全是List<String> 无模型映射关系
Sheet sheet1 = new Sheet(1, 0);
sheet1.setSheetName("第一个sheet");
Table table1 = new Table(1);
writer.write(getListString(), sheet1, table1);
writer.write(getListString(), sheet1, table1);
//写sheet2 模型上打有表头的注解
Table table2 = new Table(2);
table2.setTableStyle(getTableStyle1());
table2.setClazz(MultiLineHeadExcelModel.class);
writer.write(getModeldatas(), sheet1, table2);
//写sheet3 模型上没有注解,表头数据动态传入,此情况下模型field顺序与excel现实顺序一致
List<List<String>> head = new ArrayList<List<String>>();
List<String> headCoulumn1 = new ArrayList<String>();
List<String> headCoulumn2 = new ArrayList<String>();
List<String> headCoulumn3 = new ArrayList<String>();
headCoulumn1.add("第一列");
headCoulumn2.add("第二列");
headCoulumn3.add("第三列");
head.add(headCoulumn1);
head.add(headCoulumn2);
head.add(headCoulumn3);
Table table3 = new Table(3);
table3.setHead(head);
table3.setClazz(NoAnnModel.class);
table3.setTableStyle(getTableStyle2());
writer.write(getNoAnnModels(), sheet1, table3);
writer.write(getNoAnnModels(), sheet1, table3);
### 同步的返回<span id="synchronousRead" />
#### excel示例
参照:[excel示例](#simpleReadExcel)
#### 对象
参照:[对象](#simpleReadObject)
#### 代码
```java
/**
* 同步的返回,不推荐使用,如果数据量大会把数据放到内存里面
*/
@Test
public void synchronousRead() {
String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 同步读取会自动finish
List<Object> list = EasyExcelFactory.read(fileName).head(DemoData.class).sheet().doReadSync();
for (Object obj : list) {
DemoData data = (DemoData)obj;
LOGGER.info("读取到数据:{}", JSON.toJSONString(data));
}
writer.finish();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
// 这里 也可以不指定class,返回一个list,然后读取第一个sheet 同步读取会自动finish
list = EasyExcelFactory.read(fileName).sheet().doReadSync();
for (Object obj : list) {
// 返回每条数据的键值对 表示所在的列 和所在列的值
Map<Integer, String> data = (Map<Integer, String>)obj;
LOGGER.info("读取到数据:{}", JSON.toJSONString(data));
}
}
```
## 测试数据分析
## 测试数据分析
![POI usermodel PK easyexcel(Excel 2003).png](http://ata2-img.cn-hangzhou.img-pub.aliyun-inc.com/02c4bfbbab99a649788523d04f84a42f.png)
![POI usermodel PK easyexcel(Excel 2007).png](http://ata2-img.cn-hangzhou.img-pub.aliyun-inc.com/f6a8a19ec959f0eb564e652de523fc9e.png)
![POI usermodel PK easyexcel(Excel 2003) (1).png](http://ata2-img.cn-hangzhou.img-pub.aliyun-inc.com/26888f7ea1cb8dc56db494926544edf7.png)

16
src/main/java/com/alibaba/excel/context/WriteContextImpl.java

@ -13,7 +13,6 @@ import org.apache.poi.ss.util.CellRangeAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.excel.enums.WriteLastRowType;
import com.alibaba.excel.exception.ExcelGenerateException;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.util.WorkBookUtil;
@ -198,18 +197,13 @@ public class WriteContextImpl implements WriteContext {
if (!currentWriteHolder.needHead() || !currentWriteHolder.excelWriteHeadProperty().hasHead()) {
return;
}
int lastRowNum = writeSheetHolder.getSheet().getLastRowNum();
// 'lastRowNum' doesn't matter if it has one or zero,is's zero
if (WriteLastRowType.HAVE_DATA == writeSheetHolder.getWriteLastRowType()) {
lastRowNum++;
}
writeSheetHolder.setWriteLastRowType(WriteLastRowType.HAVE_DATA);
int rowIndex = lastRowNum + currentWriteHolder.relativeHeadRowIndex();
int newRowIndex = writeSheetHolder.getNewRowIndexAndStartDoWrite();
newRowIndex += currentWriteHolder.relativeHeadRowIndex();
// Combined head
addMergedRegionToCurrentSheet(excelWriteHeadProperty, rowIndex);
for (int relativeRowIndex = 0, i = rowIndex; i < excelWriteHeadProperty.getHeadRowNumber() + rowIndex;
addMergedRegionToCurrentSheet(excelWriteHeadProperty, newRowIndex);
for (int relativeRowIndex = 0, i = newRowIndex; i < excelWriteHeadProperty.getHeadRowNumber() + newRowIndex;
i++, relativeRowIndex++) {
beforeRowCreate(rowIndex, relativeRowIndex);
beforeRowCreate(newRowIndex, relativeRowIndex);
Row row = WorkBookUtil.createRow(writeSheetHolder.getSheet(), i);
afterRowCreate(row, relativeRowIndex);
addOneRowOfHeadDataToExcel(row, excelWriteHeadProperty.getHeadMap(), relativeRowIndex);

132
src/main/java/com/alibaba/excel/converters/DefaultConverterLoader.java

@ -40,28 +40,34 @@ import com.alibaba.excel.converters.string.StringStringConverter;
* @author Jiaju Zhuang
*/
public class DefaultConverterLoader {
private static Map<String, Converter> defaultWriteConverter;
private static Map<String, Converter> allConverter;
/**
* Load default write converter
*
* @return
*/
public static Map<String, Converter> loadDefaultWriteConverter() {
Map<String, Converter> converterMap = new HashMap<String, Converter>(16);
putWriteConverter(converterMap, new BigDecimalNumberConverter());
putWriteConverter(converterMap, new BooleanBooleanConverter());
putWriteConverter(converterMap, new ByteNumberConverter());
putWriteConverter(converterMap, new DateStringConverter());
putWriteConverter(converterMap, new DoubleNumberConverter());
putWriteConverter(converterMap, new FloatNumberConverter());
putWriteConverter(converterMap, new IntegerNumberConverter());
putWriteConverter(converterMap, new LongNumberConverter());
putWriteConverter(converterMap, new ShortNumberConverter());
putWriteConverter(converterMap, new StringStringConverter());
return converterMap;
if (defaultWriteConverter != null) {
return defaultWriteConverter;
}
defaultWriteConverter = new HashMap<String, Converter>(16);
putWriteConverter(new BigDecimalNumberConverter());
putWriteConverter(new BooleanBooleanConverter());
putWriteConverter(new ByteNumberConverter());
putWriteConverter(new DateStringConverter());
putWriteConverter(new DoubleNumberConverter());
putWriteConverter(new FloatNumberConverter());
putWriteConverter(new IntegerNumberConverter());
putWriteConverter(new LongNumberConverter());
putWriteConverter(new ShortNumberConverter());
putWriteConverter(new StringStringConverter());
return defaultWriteConverter;
}
private static void putWriteConverter(Map<String, Converter> converterMap, Converter converter) {
converterMap.put(ConverterKeyBuild.buildKey(converter.supportJavaTypeKey()), converter);
private static void putWriteConverter(Converter converter) {
defaultWriteConverter.put(ConverterKeyBuild.buildKey(converter.supportJavaTypeKey()), converter);
}
/**
@ -70,51 +76,63 @@ public class DefaultConverterLoader {
* @return
*/
public static Map<String, Converter> loadDefaultReadConverter() {
Map<String, Converter> converterMap = new HashMap<String, Converter>(64);
putReadConverter(converterMap, new BigDecimalBooleanConverter());
putReadConverter(converterMap, new BigDecimalNumberConverter());
putReadConverter(converterMap, new BigDecimalStringConverter());
putReadConverter(converterMap, new BooleanBooleanConverter());
putReadConverter(converterMap, new BooleanNumberConverter());
putReadConverter(converterMap, new BooleanStringConverter());
putReadConverter(converterMap, new ByteBooleanConverter());
putReadConverter(converterMap, new ByteNumberConverter());
putReadConverter(converterMap, new ByteStringConverter());
putReadConverter(converterMap, new DateNumberConverter());
putReadConverter(converterMap, new DateStringConverter());
putReadConverter(converterMap, new DoubleBooleanConverter());
putReadConverter(converterMap, new DoubleNumberConverter());
putReadConverter(converterMap, new DoubleStringConverter());
putReadConverter(converterMap, new FloatBooleanConverter());
putReadConverter(converterMap, new FloatNumberConverter());
putReadConverter(converterMap, new FloatStringConverter());
putReadConverter(converterMap, new IntegerBooleanConverter());
putReadConverter(converterMap, new IntegerNumberConverter());
putReadConverter(converterMap, new IntegerStringConverter());
putReadConverter(converterMap, new LongBooleanConverter());
putReadConverter(converterMap, new LongNumberConverter());
putReadConverter(converterMap, new LongStringConverter());
putReadConverter(converterMap, new ShortBooleanConverter());
putReadConverter(converterMap, new ShortNumberConverter());
putReadConverter(converterMap, new ShortStringConverter());
putReadConverter(converterMap, new StringBooleanConverter());
putReadConverter(converterMap, new StringNumberConverter());
putReadConverter(converterMap, new StringStringConverter());
putReadConverter(converterMap, new StringErrorConverter());
return converterMap;
return loadAllConverter();
}
/**
* Load all converter
*
* @return
*/
public static Map<String, Converter> loadAllConverter() {
if (allConverter != null) {
return allConverter;
}
allConverter = new HashMap<String, Converter>(64);
putAllConverter(new BigDecimalBooleanConverter());
putAllConverter(new BigDecimalNumberConverter());
putAllConverter(new BigDecimalStringConverter());
putAllConverter(new BooleanBooleanConverter());
putAllConverter(new BooleanNumberConverter());
putAllConverter(new BooleanStringConverter());
putAllConverter(new ByteBooleanConverter());
putAllConverter(new ByteNumberConverter());
putAllConverter(new ByteStringConverter());
putAllConverter(new DateNumberConverter());
putAllConverter(new DateStringConverter());
putAllConverter(new DoubleBooleanConverter());
putAllConverter(new DoubleNumberConverter());
putAllConverter(new DoubleStringConverter());
putAllConverter(new FloatBooleanConverter());
putAllConverter(new FloatNumberConverter());
putAllConverter(new FloatStringConverter());
putAllConverter(new IntegerBooleanConverter());
putAllConverter(new IntegerNumberConverter());
putAllConverter(new IntegerStringConverter());
putAllConverter(new LongBooleanConverter());
putAllConverter(new LongNumberConverter());
putAllConverter(new LongStringConverter());
putAllConverter(new ShortBooleanConverter());
putAllConverter(new ShortNumberConverter());
putAllConverter(new ShortStringConverter());
putAllConverter(new StringBooleanConverter());
putAllConverter(new StringNumberConverter());
putAllConverter(new StringStringConverter());
putAllConverter(new StringErrorConverter());
return allConverter;
}
private static void putReadConverter(Map<String, Converter> converterMap, Converter converter) {
converterMap.put(ConverterKeyBuild.buildKey(converter.supportJavaTypeKey(), converter.supportExcelTypeKey()),
private static void putAllConverter(Converter converter) {
allConverter.put(ConverterKeyBuild.buildKey(converter.supportJavaTypeKey(), converter.supportExcelTypeKey()),
converter);
}
}

12
src/main/java/com/alibaba/excel/enums/WriteLastRowType.java

@ -7,11 +7,15 @@ package com.alibaba.excel.enums;
**/
public enum WriteLastRowType {
/**
* Tables are created without templates ,And any data has been written;
* Excel are created without templates ,And any data has been written;
*/
EMPTY,
COMMON_EMPTY,
/**
* It's supposed to have data in it.Tables are created with templates ,or any data has been written;
* Excel are created with templates ,And any data has been written;
*/
HAVE_DATA,;
TEMPLATE_EMPTY,
/**
* Any data has been written;
*/
HAS_DATA,;
}

13
src/main/java/com/alibaba/excel/util/WorkBookUtil.java

@ -25,11 +25,20 @@ public class WorkBookUtil {
public static Workbook createWorkBook(WriteWorkbookHolder writeWorkbookHolder)
throws IOException, InvalidFormatException {
if (ExcelTypeEnum.XLSX.equals(writeWorkbookHolder.getExcelType())) {
XSSFWorkbook xssfWorkbook = null;
if (writeWorkbookHolder.getTemplateFile() != null) {
return new SXSSFWorkbook(new XSSFWorkbook(writeWorkbookHolder.getTemplateFile()));
xssfWorkbook = new XSSFWorkbook(writeWorkbookHolder.getTemplateFile());
}
if (writeWorkbookHolder.getTemplateInputStream() != null) {
return new SXSSFWorkbook(new XSSFWorkbook(writeWorkbookHolder.getTemplateInputStream()));
xssfWorkbook = new XSSFWorkbook(writeWorkbookHolder.getTemplateInputStream());
}
// When using SXSSFWorkbook, you can't get the actual last line.But we need to read the last line when we
// are using the template, so we cache it
if (xssfWorkbook != null) {
for (int i = 0; i < xssfWorkbook.getNumberOfSheets(); i++) {
writeWorkbookHolder.getTemplateLastRowMap().put(i, xssfWorkbook.getSheetAt(i).getLastRowNum());
}
return new SXSSFWorkbook(xssfWorkbook);
}
return new SXSSFWorkbook(500);
}

18
src/main/java/com/alibaba/excel/write/ExcelBuilderImpl.java

@ -10,14 +10,12 @@ import java.util.Set;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;
import com.alibaba.excel.context.WriteContext;
import com.alibaba.excel.context.WriteContextImpl;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.converters.ConverterKeyBuild;
import com.alibaba.excel.enums.WriteLastRowType;
import com.alibaba.excel.exception.ExcelDataConvertException;
import com.alibaba.excel.exception.ExcelGenerateException;
import com.alibaba.excel.metadata.BaseRowModel;
@ -64,22 +62,14 @@ public class ExcelBuilderImpl implements ExcelBuilder {
return;
}
WriteSheetHolder writeSheetHolder = context.writeSheetHolder();
Sheet currentSheet = writeSheetHolder.getSheet();
int lastRowNum = currentSheet.getLastRowNum();
// 'lastRowNum' doesn't matter if it has one or zero,is's zero
if (lastRowNum == 0 && WriteLastRowType.EMPTY == writeSheetHolder.getWriteLastRowType()) {
lastRowNum--;
}
if (!data.isEmpty()) {
writeSheetHolder.setWriteLastRowType(WriteLastRowType.HAVE_DATA);
}
int newRowIndex = writeSheetHolder.getNewRowIndexAndStartDoWrite();
if (writeSheetHolder.isNew() && !writeSheetHolder.getExcelWriteHeadProperty().hasHead()) {
lastRowNum += context.currentWriteHolder().relativeHeadRowIndex();
newRowIndex += context.currentWriteHolder().relativeHeadRowIndex();
}
// Beanmap is out of order,so use fieldList
// BeanMap is out of order,so use fieldList
List<Field> fieldList = new ArrayList<Field>();
for (int relativeRowIndex = 0; relativeRowIndex < data.size(); relativeRowIndex++) {
int n = relativeRowIndex + lastRowNum + 1;
int n = relativeRowIndex + newRowIndex;
addOneRowOfDataToExcel(data.get(relativeRowIndex), n, relativeRowIndex, fieldList);
}
}

17
src/main/java/com/alibaba/excel/write/merge/LoopMergeStrategy.java

@ -13,21 +13,24 @@ import com.alibaba.excel.metadata.Head;
*/
public class LoopMergeStrategy extends AbstractMergeStrategy {
private int eachRow;
private int eachColumn;
private int columnIndex;
public LoopMergeStrategy(int eachRow, int eachColumn) {
if (eachRow < 1 || eachColumn < 1) {
throw new IllegalArgumentException("All parameters must be greater than 1");
public LoopMergeStrategy(int eachRow, int columnIndex) {
if (eachRow < 1) {
throw new IllegalArgumentException("EachRows must be greater than 1");
}
if (columnIndex < 0) {
throw new IllegalArgumentException("ColumnIndex must be greater than 0");
}
this.eachRow = eachRow;
this.eachColumn = eachColumn;
this.columnIndex = columnIndex;
}
@Override
protected void merge(Sheet sheet, Cell cell, Head head, int relativeRowIndex) {
if (relativeRowIndex % eachRow == 0 && head.getColumnIndex() % eachColumn == 0) {
if (head.getColumnIndex() == columnIndex && relativeRowIndex % eachRow == 0) {
CellRangeAddress cellRangeAddress = new CellRangeAddress(cell.getRowIndex(),
cell.getRowIndex() + eachRow - 1, cell.getColumnIndex(), cell.getColumnIndex() + eachColumn - 1);
cell.getRowIndex() + eachRow - 1, cell.getColumnIndex(), cell.getColumnIndex());
sheet.addMergedRegion(cellRangeAddress);
}
}

35
src/main/java/com/alibaba/excel/write/metadata/holder/WriteSheetHolder.java

@ -7,6 +7,7 @@ import org.apache.poi.ss.usermodel.Sheet;
import com.alibaba.excel.enums.HolderEnum;
import com.alibaba.excel.enums.WriteLastRowType;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.alibaba.excel.write.metadata.WriteSheet;
/**
@ -60,9 +61,9 @@ public class WriteSheetHolder extends AbstractWriteHolder {
this.parentWriteWorkbookHolder = writeWorkbookHolder;
this.hasBeenInitializedTable = new HashMap<Integer, WriteTableHolder>();
if (writeWorkbookHolder.getTemplateInputStream() == null && writeWorkbookHolder.getTemplateFile() == null) {
writeLastRowType = WriteLastRowType.EMPTY;
writeLastRowType = WriteLastRowType.COMMON_EMPTY;
} else {
writeLastRowType = WriteLastRowType.HAVE_DATA;
writeLastRowType = WriteLastRowType.TEMPLATE_EMPTY;
}
}
@ -122,6 +123,36 @@ public class WriteSheetHolder extends AbstractWriteHolder {
this.writeLastRowType = writeLastRowType;
}
/**
* Get the last line of index,you have to make sure that the data is written next
*
* @return
*/
public int getNewRowIndexAndStartDoWrite() {
// 'getLastRowNum' doesn't matter if it has one or zero,is's zero
int newRowIndex = 0;
switch (writeLastRowType) {
case TEMPLATE_EMPTY:
if (parentWriteWorkbookHolder.getExcelType() == ExcelTypeEnum.XLSX) {
if (parentWriteWorkbookHolder.getTemplateLastRowMap().containsKey(sheetNo)) {
newRowIndex = parentWriteWorkbookHolder.getTemplateLastRowMap().get(sheetNo);
}
} else {
newRowIndex = sheet.getLastRowNum();
}
newRowIndex++;
break;
case HAS_DATA:
newRowIndex = sheet.getLastRowNum();
newRowIndex++;
break;
default:
break;
}
writeLastRowType = WriteLastRowType.HAS_DATA;
return newRowIndex;
}
@Override
public HolderEnum holderType() {
return HolderEnum.SHEET;

14
src/main/java/com/alibaba/excel/write/metadata/holder/WriteWorkbookHolder.java

@ -72,6 +72,11 @@ public class WriteWorkbookHolder extends AbstractWriteHolder {
* prevent duplicate creation of sheet objects
*/
private Map<Integer, WriteSheetHolder> hasBeenInitializedSheet;
/**
* When using SXSSFWorkbook, you can't get the actual last line.But we need to read the last line when we are using
* the template, so we cache it
*/
private Map<Integer, Integer> templateLastRowMap;
public WriteWorkbookHolder(WriteWorkbook writeWorkbook) {
super(writeWorkbook, null, writeWorkbook.getConvertAllFiled());
@ -114,6 +119,7 @@ public class WriteWorkbookHolder extends AbstractWriteHolder {
this.mandatoryUseInputStream = writeWorkbook.getMandatoryUseInputStream();
}
this.hasBeenInitializedSheet = new HashMap<Integer, WriteSheetHolder>();
this.templateLastRowMap = new HashMap<Integer, Integer>(8);
}
public Workbook getWorkbook() {
@ -196,6 +202,14 @@ public class WriteWorkbookHolder extends AbstractWriteHolder {
this.mandatoryUseInputStream = mandatoryUseInputStream;
}
public Map<Integer, Integer> getTemplateLastRowMap() {
return templateLastRowMap;
}
public void setTemplateLastRowMap(Map<Integer, Integer> templateLastRowMap) {
this.templateLastRowMap = templateLastRowMap;
}
@Override
public HolderEnum holderType() {
return HolderEnum.WORKBOOK;

14
src/main/java/com/alibaba/excel/write/property/ExcelWriteHeadProperty.java

@ -7,9 +7,13 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import com.alibaba.excel.annotation.format.NumberFormat;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import com.alibaba.excel.converters.ConverterKeyBuild;
import com.alibaba.excel.converters.DefaultConverterLoader;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.enums.HeadKindEnum;
import com.alibaba.excel.metadata.CellRange;
import com.alibaba.excel.metadata.Head;
@ -48,8 +52,16 @@ public class ExcelWriteHeadProperty extends ExcelHeadProperty {
columnWidth = parentColumnWidth;
}
headData.setColumnWidthProperty(ColumnWidthProperty.build(columnWidth));
}
// If have @NumberFormat, 'NumberStringConverter' is specified by default
if (excelContentPropertyData.getConverter() == null) {
NumberFormat numberFormat = field.getAnnotation(NumberFormat.class);
if (numberFormat != null) {
excelContentPropertyData.setConverter(DefaultConverterLoader.loadAllConverter()
.get(ConverterKeyBuild.buildKey(field.getType(), CellDataTypeEnum.STRING)));
}
}
}
}
public RowHeightProperty getHeadRowHeightProperty() {

4
src/test/java/com/alibaba/easyexcel/test/core/template/TemplateDataTest.java

@ -42,14 +42,14 @@ public class TemplateDataTest {
EasyExcel.write(file, TemplateData.class)
.withTemplate(TestFileUtil.readFile("template" + File.separator + "template07.xlsx")).sheet()
.doWrite(data());
EasyExcel.read(file, TemplateData.class, new TemplateDataListener()).headRowNumber(2).sheet().doRead();
EasyExcel.read(file, TemplateData.class, new TemplateDataListener()).headRowNumber(3).sheet().doRead();
}
private void readAndWrite03(File file) {
EasyExcel.write(file, TemplateData.class)
.withTemplate(TestFileUtil.readFile("template" + File.separator + "template03.xls")).sheet()
.doWrite(data());
EasyExcel.read(file, TemplateData.class, new TemplateDataListener()).headRowNumber(2).sheet().doRead();
EasyExcel.read(file, TemplateData.class, new TemplateDataListener()).headRowNumber(3).sheet().doRead();
}
private List<TemplateData> data() {

54
src/test/java/com/alibaba/easyexcel/test/demo/poi/PoiTest.java

@ -0,0 +1,54 @@
package com.alibaba.easyexcel.test.demo.poi;
import java.io.File;
import java.io.IOException;
import org.apache.poi.xssf.streaming.SXSSFRow;
import org.apache.poi.xssf.streaming.SXSSFSheet;
import org.apache.poi.xssf.streaming.SXSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.junit.Ignore;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.alibaba.easyexcel.test.util.TestFileUtil;
/**
* 测试poi
*
* @author Jiaju Zhuang
**/
@Ignore
public class PoiTest {
private static final Logger LOGGER = LoggerFactory.getLogger(PoiTest.class);
@Test
public void lastRowNum() throws IOException {
String file = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
SXSSFWorkbook xssfWorkbook = new SXSSFWorkbook(new XSSFWorkbook(file));
SXSSFSheet xssfSheet = xssfWorkbook.getSheetAt(0);
LOGGER.info("一共行数:{}", xssfSheet.getLastRowNum());
SXSSFRow row = xssfSheet.getRow(0);
LOGGER.info("第一行数据:{}", row);
xssfSheet.createRow(20);
LOGGER.info("一共行数:{}", xssfSheet.getLastRowNum());
}
@Test
public void lastRowNumXSSF() throws IOException {
String file = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
XSSFWorkbook xssfWorkbook = new XSSFWorkbook(file);
LOGGER.info("一共:{}个sheet", xssfWorkbook.getNumberOfSheets());
XSSFSheet xssfSheet = xssfWorkbook.getSheetAt(0);
LOGGER.info("一共行数:{}", xssfSheet.getLastRowNum());
XSSFRow row = xssfSheet.getRow(0);
LOGGER.info("第一行数据:{}", row);
xssfSheet.createRow(20);
LOGGER.info("一共行数:{}", xssfSheet.getLastRowNum());
}
}

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

@ -13,6 +13,8 @@ import com.alibaba.easyexcel.test.util.TestFileUtil;
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.alibaba.excel.annotation.format.NumberFormat;
import com.alibaba.excel.converters.DefaultConverterLoader;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.alibaba.fastjson.JSON;
@ -50,7 +52,10 @@ public class ReadTest {
/**
* 指定列的下标或者列名
* <li>使用{@link com.alibaba.excel.annotation.ExcelProperty}注解即可
*
* <li>1. 创建excel对应的实体对象,并使用{@link ExcelProperty}注解. 参照{@link IndexOrNameData}
* <li>2. 由于默认异步读取excel所以需要创建excel一行一行的回调监听器参照{@link IndexOrNameDataListener}
* <li>3. 直接读即可
*/
@Test
public void indexOrNameRead() {
@ -78,10 +83,10 @@ public class ReadTest {
}
/**
* 日期数字或者自定义格式转换
* 日期数字或者自定义格式转换
* <p>
* 默认读的转换器{@link DefaultConverterLoader#loadDefaultReadConverter()}
* <li>1. 创建excel对应的实体对象 参照{@link ConverterData}.里面可以使用注解.
* <li>1. 创建excel对应的实体对象 参照{@link ConverterData}.里面可以使用注解{@link DateTimeFormat}{@link NumberFormat}或者自定义注解
* <li>2. 由于默认异步读取excel所以需要创建excel一行一行的回调监听器参照{@link ConverterDataListener}
* <li>3. 直接读即可
*/
@ -98,7 +103,7 @@ public class ReadTest {
}
/**
* 多行头.
* 多行头
*
* <li>1. 创建excel对应的实体对象 参照{@link DemoData}
* <li>2. 由于默认异步读取excel所以需要创建excel一行一行的回调监听器参照{@link DemoDataListener}
@ -134,7 +139,6 @@ public class ReadTest {
Map<Integer, String> data = (Map<Integer, String>)obj;
LOGGER.info("读取到数据:{}", JSON.toJSONString(data));
}
}
}

2
src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java

@ -26,7 +26,7 @@ public class WebTest {
* 文件下载
* <li>1. 创建excel对应的实体对象 参照{@link DownloadData}
* <li>2. 设置返回的 参数
* <li>3. 直接写这里注意finish的时候会自动关闭OutputStream,当然你外面再关闭异常问题不大
* <li>3. 直接写这里注意finish的时候会自动关闭OutputStream,当然你外面再关闭问题不大
*/
@GetMapping("download")
public void download(HttpServletResponse response) throws IOException {

22
src/test/java/com/alibaba/easyexcel/test/demo/write/ComplexHeadData.java

@ -0,0 +1,22 @@
package com.alibaba.easyexcel.test.demo.write;
import java.util.Date;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
/**
* 复杂头数据.这里最终效果是第一行就一个主标题第二行分类
*
* @author Jiaju Zhuang
**/
@Data
public class ComplexHeadData {
@ExcelProperty({"主标题", "字符串标题"})
private String string;
@ExcelProperty({"主标题", "日期标题"})
private Date date;
@ExcelProperty({"主标题", "数字标题"})
private Double doubleData;
}

35
src/test/java/com/alibaba/easyexcel/test/demo/write/ConverterData.java

@ -0,0 +1,35 @@
package com.alibaba.easyexcel.test.demo.write;
import java.util.Date;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.alibaba.excel.annotation.format.NumberFormat;
import lombok.Data;
/**
* 基础数据类.这里的排序和excel里面的排序一致
*
* @author Jiaju Zhuang
**/
@Data
public class ConverterData {
/**
* 我想所有的 字符串起前面加上"自定义:"三个字
*/
@ExcelProperty(value = "字符串标题", converter = CustomStringStringConverter.class)
private String string;
/**
* 我想写到excel 用年月日的格式
*/
@DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")
@ExcelProperty("日期标题")
private Date date;
/**
* 我想写到excel 用百分比表示
*/
@NumberFormat("#.##%")
@ExcelProperty(value = "数字标题")
private Double doubleData;
}

59
src/test/java/com/alibaba/easyexcel/test/demo/write/CustomStringStringConverter.java

@ -0,0 +1,59 @@
package com.alibaba.easyexcel.test.demo.write;
import com.alibaba.excel.converters.Converter;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.CellData;
import com.alibaba.excel.metadata.GlobalConfiguration;
import com.alibaba.excel.metadata.property.ExcelContentProperty;
/**
* String and string converter
*
* @author Jiaju Zhuang
*/
public class CustomStringStringConverter implements Converter<String> {
@Override
public Class supportJavaTypeKey() {
return String.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
/**
* 这里是读的时候会调用 不用管
*
* @param cellData
* NotNull
* @param contentProperty
* Nullable
* @param globalConfiguration
* NotNull
* @return
*/
@Override
public String convertToJavaData(CellData cellData, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
return cellData.getStringValue();
}
/**
* 这里是写的时候会调用 不用管
*
* @param value
* NotNull
* @param contentProperty
* Nullable
* @param globalConfiguration
* NotNull
* @return
*/
@Override
public CellData convertToExcelData(String value, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
return new CellData("自定义:" + value);
}
}

25
src/test/java/com/alibaba/easyexcel/test/demo/write/IndexData.java

@ -0,0 +1,25 @@
package com.alibaba.easyexcel.test.demo.write;
import java.util.Date;
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
/**
* 基础数据类
*
* @author Jiaju Zhuang
**/
@Data
public class IndexData {
@ExcelProperty(value = "字符串标题", index = 0)
private String string;
@ExcelProperty(value = "日期标题", index = 1)
private Date date;
/**
* 这里设置3 会导致第二列空的
*/
@ExcelProperty(value = "数字标题", index = 3)
private Double doubleData;
}

32
src/test/java/com/alibaba/easyexcel/test/demo/write/WidthAndHeightData.java

@ -0,0 +1,32 @@
package com.alibaba.easyexcel.test.demo.write;
import java.util.Date;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import lombok.Data;
/**
* 基础数据类
*
* @author Jiaju Zhuang
**/
@Data
@ContentRowHeight(10)
@HeadRowHeight(20)
@ColumnWidth(25)
public class WidthAndHeightData {
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
/**
* 宽度为50
*/
@ColumnWidth(50)
@ExcelProperty("数字标题")
private Double doubleData;
}

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

@ -1,16 +1,30 @@
package com.alibaba.easyexcel.test.demo.write;
import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.apache.poi.ss.usermodel.FillPatternType;
import org.apache.poi.ss.usermodel.IndexedColors;
import org.junit.Ignore;
import org.junit.Test;
import com.alibaba.easyexcel.test.util.TestFileUtil;
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.alibaba.excel.annotation.format.NumberFormat;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import com.alibaba.excel.write.merge.LoopMergeStrategy;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.WriteTable;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
/**
* 写的常见写法
@ -21,19 +35,19 @@ import com.alibaba.excel.write.metadata.WriteSheet;
public class WriteTest {
/**
* 最简单的写
* <li>1. 创建excel对应的实体对象 参照{@link com.alibaba.easyexcel.test.demo.write.DemoData}
* <li>1. 创建excel对应的实体对象 参照{@link DemoData}
* <li>2. 直接写即可
*/
@Test
public void simpleWrite() {
// 写法1
String fileName = TestFileUtil.getPath() + "write" + System.currentTimeMillis() + ".xlsx";
String fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
// 如果这里想使用03 则 传入excelType参数即可
EasyExcelFactory.write(fileName, DemoData.class).sheet("模板").doWrite(data());
// 写法2
fileName = TestFileUtil.getPath() + "write" + System.currentTimeMillis() + ".xlsx";
fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去读
ExcelWriter excelWriter = EasyExcelFactory.write(fileName, DemoData.class).build();
WriteSheet writeSheet = EasyExcelFactory.writerSheet("模板").build();
@ -42,12 +56,175 @@ public class WriteTest {
excelWriter.finish();
}
/**
* 指定写入的列
* <li>1. 创建excel对应的实体对象 参照{@link IndexData}
* <li>2. 使用{@link ExcelProperty}注解指定写入的列
* <li>3. 直接写即可
*/
@Test
public void indexWrite() {
String fileName = TestFileUtil.getPath() + "indexWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
EasyExcelFactory.write(fileName, IndexData.class).sheet("模板").doWrite(data());
}
/**
* 复杂头写入 指定写入的列
* <li>1. 创建excel对应的实体对象 参照{@link ComplexHeadData}
* <li>2. 使用{@link ExcelProperty}注解指定复杂的头
* <li>3. 直接写即可
*/
@Test
public void complexHeadWrite() {
String fileName = TestFileUtil.getPath() + "complexHeadWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
EasyExcelFactory.write(fileName, ComplexHeadData.class).sheet("模板").doWrite(data());
}
/**
* 重复多次写入
* <li>1. 创建excel对应的实体对象 参照{@link ComplexHeadData}
* <li>2. 使用{@link ExcelProperty}注解指定复杂的头
* <li>3. 直接调用二次写入即可
*/
@Test
public void repeatedWrite() {
String fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去读
ExcelWriter excelWriter = EasyExcelFactory.write(fileName, DemoData.class).build();
WriteSheet writeSheet = EasyExcelFactory.writerSheet("模板").build();
// 第一次写入会创建头
excelWriter.write(data(), writeSheet);
// 第二次写入会在上一次写入的最后一行后面写入
excelWriter.write(data(), writeSheet);
/// 千万别忘记finish 会帮忙关闭流
excelWriter.finish();
}
/**
* 日期数字或者自定义格式转换
* <li>1. 创建excel对应的实体对象 参照{@link ConverterData}
* <li>2. 使用{@link ExcelProperty}配合使用注解{@link DateTimeFormat}{@link NumberFormat}或者自定义注解
* <li>3. 直接写即可
*/
@Test
public void converterWrite() {
String fileName = TestFileUtil.getPath() + "converterWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
EasyExcelFactory.write(fileName, ConverterData.class).sheet("模板").doWrite(data());
}
/**
* 指定写入的列
* <li>1. 创建excel对应的实体对象 参照{@link IndexData}
* <li>2. 使用{@link ExcelProperty}注解指定写入的列
* <li>3. 使用withTemplate 读取模板
* <li>4. 直接写即可
*/
@Test
public void templateWrite() {
String templateFileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
String fileName = TestFileUtil.getPath() + "templateWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
EasyExcelFactory.write(fileName, DemoData.class).withTemplate(templateFileName).sheet().doWrite(data());
}
/**
* 列宽行高
* <li>1. 创建excel对应的实体对象 参照{@link WidthAndHeightData}
* <li>2. 使用注解{@link ColumnWidth}{@link HeadRowHeight}{@link ContentRowHeight}指定宽度或高度
* <li>3. 直接写即可
*/
@Test
public void widthAndHeightWrite() {
String fileName = TestFileUtil.getPath() + "widthAndHeightWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
EasyExcelFactory.write(fileName, WidthAndHeightData.class).sheet("模板").doWrite(data());
}
/**
* 自定义样式
* <li>1. 创建excel对应的实体对象 参照{@link DemoData}
* <li>2. 创建一个style策略 并注册
* <li>3. 直接写即可
*/
@Test
public void styleWrite() {
String fileName = TestFileUtil.getPath() + "styleWrite" + System.currentTimeMillis() + ".xlsx";
// 头的策略
WriteCellStyle headWriteCellStyle = new WriteCellStyle();
// 背景设置为红色
headWriteCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex());
WriteFont headWriteFont = new WriteFont();
headWriteFont.setFontHeightInPoints((short)20);
headWriteCellStyle.setWriteFont(headWriteFont);
// 内容的策略
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
// 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色.头默认了 FillPatternType所以可以不指定
contentWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);
// 背景绿色
contentWriteCellStyle.setFillForegroundColor(IndexedColors.GREEN.getIndex());
WriteFont contentWriteFont = new WriteFont();
// 字体大小
contentWriteFont.setFontHeightInPoints((short)20);
contentWriteCellStyle.setWriteFont(contentWriteFont);
// 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现
HorizontalCellStyleStrategy horizontalCellStyleStrategy =
new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);
// 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
EasyExcelFactory.write(fileName, DemoData.class).registerWriteHandler(horizontalCellStyleStrategy).sheet("模板")
.doWrite(data());
}
/**
* 合并单元格
* <li>1. 创建excel对应的实体对象 参照{@link DemoData}
* <li>2. 创建一个merge策略 并注册
* <li>3. 直接写即可
*/
@Test
public void mergeWrite() {
String fileName = TestFileUtil.getPath() + "mergeWrite" + System.currentTimeMillis() + ".xlsx";
// 每隔2行会合并 把eachColumn 设置成 3 也就是我们数据的长度,所以就第一列会合并。当然其他合并策略也可以自己写
LoopMergeStrategy loopMergeStrategy = new LoopMergeStrategy(2, 0);
// 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
EasyExcelFactory.write(fileName, DemoData.class).registerWriteHandler(loopMergeStrategy).sheet("模板")
.doWrite(data());
}
/**
* 使用table去写入
* <li>1. 创建excel对应的实体对象 参照{@link DemoData}
* <li>2. 然后写入table即可
*/
@Test
public void tableWrite() {
String fileName = TestFileUtil.getPath() + "tableWrite" + System.currentTimeMillis() + ".xlsx";
// 这里直接写多个table的案例了,如果只有一个 也可以直一行代码搞定,参照其他案例
// 这里 需要指定写用哪个class去读
ExcelWriter excelWriter = EasyExcelFactory.write(fileName, DemoData.class).build();
// 把sheet设置为不需要头 不然会输出sheet的头 这样看起来第一个table 就有2个头了
WriteSheet writeSheet = EasyExcelFactory.writerSheet("模板").needHead(Boolean.FALSE).build();
// 这里必须指定需要头,table 会继承sheet的配置,sheet配置了不需要,table 默认也是不需要
WriteTable writeTable0 = EasyExcelFactory.writerTable(0).needHead(Boolean.TRUE).build();
WriteTable writeTable1 = EasyExcelFactory.writerTable(1).needHead(Boolean.TRUE).build();
// 第一次写入会创建头
excelWriter.write(data(), writeSheet, writeTable0);
// 第二次写如也会创建头,然后在第一次的后面写入数据
excelWriter.write(data(), writeSheet, writeTable1);
/// 千万别忘记finish 会帮忙关闭流
excelWriter.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.setString("字符串" + i);
data.setDate(new Date());
data.setDoubleData(0.56);
list.add(data);

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

Binary file not shown.

BIN
src/test/resources/template/template03.xls

Binary file not shown.

BIN
src/test/resources/template/template07.xlsx

Binary file not shown.

2
update.md

@ -3,6 +3,8 @@
* 优化读写对外接口
* 加入转换器,方便格式转换
* 极大优化读大文件的内存和效率
* sheetNo 改成0开始
* 读支持指定列名
# 1.2.4
修复read()方法存在的bug
# 1.2.1

Loading…
Cancel
Save