quickstart.md 45.0 KB
Newer Older
J
update  
jipengfei.jpf 已提交
1
# easyexcel核心功能
2 3
## 各位读取文件务必使用2.0.3起
2.0.0-beta1到2.0.2有小概率会丢失数字。
Z
zhuangjiaju 已提交
4
## 目录
庄家钜's avatar
庄家钜 已提交
5
### 前言
庄家钜's avatar
庄家钜 已提交
6 7 8 9
#### 以下功能目前不支持
* 单个文件的并发写入、读取
* 读取图片
*
10
ClassNotFoundException与java.lang.NoClassDefFoundError的区别 
庄家钜's avatar
庄家钜 已提交
11
* csv读取(这个后续可能会考虑)
12 13
#### 常见问题
* 关于@Data,读写的对象都用到了[Lombok](https://www.projectlombok.org/),他会自动生成`get`,`set` ,如果不需要的话,自己创建对象并生成`get`,`set`
14
* 出现`NoSuchMethodException``ClassNotFoundException`,`NoClassDefFoundError`极大概率是jar冲突,建议`clean`项目,或者统一`poi` 的版本,理论上来说`easyexcel`兼容poi的`3.17`,`4.0.1`,`4.1.0`所有较新版本
15 16
* 如果在读的时候`Listener`里面需要使用spring的`@Autowired`,给`Listener`创建成员变量,然后在构造方法里面传进去。而别必须不让spring管理`Listener`,每次读取都要`new`一个。
* 如果用`String`去接收数字,出现小数点等情况,这个是BUG,但是很难修复,后续版本会修复这个问题。目前请使用`@NumberFormat`直接,里面的参数就是调用了java自带的`NumberFormat.format`方法,不知道怎么入参的可以自己网上查询。
庄家钜's avatar
庄家钜 已提交
17
* 10M以上xlsx读取很慢或者内存占用大 请先阅读[10M以上文件读取说明](/docs/LARGEREAD.md)
庄家钜's avatar
庄家钜 已提交
18 19
#### 详细参数介绍
有些参数不知道怎么用,或者有些功能不知道用什么参数,参照:[详细参数介绍](/docs/API.md)
庄家钜's avatar
庄家钜 已提交
20
#### 开源项目不容易,如果觉得本项目对您的工作还是有帮助的话,请在右上角帮忙点个★Star。
Z
zhuangjiaju 已提交
21 22 23
### 读
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)
Z
zhuangjiaju 已提交
24 25 26 27 28
* [指定列的下标或者列名](#indexOrNameRead)
* [读多个sheet](#repeatedRead)
* [日期、数字或者自定义格式转换](#converterRead)
* [多行头](#complexHeaderRead)
* [同步的返回](#synchronousRead)
庄家钜's avatar
庄家钜 已提交
29
* [读取表头数据](#headerRead)
庄家钜's avatar
庄家钜 已提交
30
* [数据转换等异常处理](#exceptionRead)
Z
zhuangjiaju 已提交
31
* [web中的读](#webRead)
庄家钜's avatar
庄家钜 已提交
32

Z
zhuangjiaju 已提交
33
### 写
Z
zhuangjiaju 已提交
34 35 36 37
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)
* [最简单的写](#simpleWrite)
* [指定写入的列](#indexWrite)
* [复杂头写入](#complexHeadWrite)
38
* [重复多次写入(包括不同sheet)](#repeatedWrite)
Z
zhuangjiaju 已提交
39
* [日期、数字或者自定义格式转换](#converterWrite)
Z
zhuangjiaju 已提交
40
* [图片导出](#imageWrite)
Z
zhuangjiaju 已提交
41 42 43 44 45
* [根据模板写入](#templateWrite)
* [列宽、行高](#widthAndHeightWrite)
* [自定义样式](#styleWrite)
* [合并单元格](#mergeWrite)
* [使用table去写入](#tableWrite)
Z
zhuangjiaju 已提交
46
* [动态头,实时生成头写入](#dynamicHeadWrite)
Z
zhuangjiaju 已提交
47
* [自动列宽(不太精确)](#longestMatchColumnWidthWrite)
庄家钜's avatar
庄家钜 已提交
48
* [自定义拦截器(下拉,超链接等上面几点都不符合但是要对单元格进行操作的参照这个)](#customHandlerWrite)
Z
zhuangjiaju 已提交
49
* [web中的写](#webWrite)
J
update  
jipengfei.jpf 已提交
50

Z
zhuangjiaju 已提交
51
## 读excel样例
Z
zhuangjiaju 已提交
52
### <span id="simpleRead" />最简单的读
Z
zhuangjiaju 已提交
53
##### <span id="simpleReadExcel" />excel示例
Z
zhuangjiaju 已提交
54
![img](img/readme/quickstart/read/demo.png)
Z
zhuangjiaju 已提交
55
##### <span id="simpleReadObject" />对象
Z
zhuangjiaju 已提交
56 57 58 59 60 61 62
```java
@Data
public class DemoData {
    private String string;
    private Date date;
    private Double doubleData;
}
J
update  
jipengfei.jpf 已提交
63
```
Z
zhuangjiaju 已提交
64
##### <span id="simpleReadListener" />监听器
Z
zhuangjiaju 已提交
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
```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();
J
update  
jipengfei.jpf 已提交
81 82 83
        }
    }

Z
zhuangjiaju 已提交
84
    @Override
J
update  
jipengfei.jpf 已提交
85
    public void doAfterAllAnalysed(AnalysisContext context) {
Z
zhuangjiaju 已提交
86 87
        saveData();
        LOGGER.info("所有数据解析完成!");
J
update  
jipengfei.jpf 已提交
88
    }
Z
zhuangjiaju 已提交
89 90 91 92 93 94 95

    /**
     * 加上存储数据库
     */
    private void saveData() {
        LOGGER.info("{}条数据,开始存储数据库!", list.size());
        LOGGER.info("存储数据库成功!");
J
update  
jipengfei.jpf 已提交
96 97 98
    }
}
```
Z
zhuangjiaju 已提交
99
##### 代码
Z
zhuangjiaju 已提交
100 101 102
```java
    /**
     * 最简单的读
罗成 已提交
103 104 105
     * <p>1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
     * <p>3. 直接读即可
Z
zhuangjiaju 已提交
106 107 108 109 110 111
     */
    @Test
    public void simpleRead() {
        // 写法1:
        String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
Z
zhuangjiaju 已提交
112
        EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
Z
zhuangjiaju 已提交
113 114 115

        // 写法2:
        fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
Z
zhuangjiaju 已提交
116 117
        ExcelReader excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build();
        ReadSheet readSheet = EasyExcel.readSheet(0).build();
Z
zhuangjiaju 已提交
118 119 120 121
        excelReader.read(readSheet);
        // 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
        excelReader.finish();
    }
J
update  
jipengfei.jpf 已提交
122 123
```

Z
zhuangjiaju 已提交
124 125
### <span id="indexOrNameRead" />指定列的下标或者列名
##### excel示例
Z
zhuangjiaju 已提交
126
参照:[excel示例](#simpleReadExcel)
Z
zhuangjiaju 已提交
127
##### 对象
Z
zhuangjiaju 已提交
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
```java
@Data
public class IndexOrNameData {
    /**
     * 强制读取第三个 这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去匹配
     */
    @ExcelProperty(index = 2)
    private Double doubleData;
    /**
     * 用名字去匹配,这里需要注意,如果名字重复,会导致只有一个字段读取到数据
     */
    @ExcelProperty("字符串标题")
    private String string;
    @ExcelProperty("日期标题")
    private Date date;
J
update  
jipengfei.jpf 已提交
143 144
}
```
Z
zhuangjiaju 已提交
145
##### 监听器
Z
zhuangjiaju 已提交
146
参照:[监听器](#simpleReadListener) 只是泛型变了而已
Z
zhuangjiaju 已提交
147
##### 代码
Z
zhuangjiaju 已提交
148 149 150 151
```java
    /**
     * 指定列的下标或者列名
     *
罗成 已提交
152 153 154
     * <p>1. 创建excel对应的实体对象,并使用{@link ExcelProperty}注解. 参照{@link IndexOrNameData}
     * <p>2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link IndexOrNameDataListener}
     * <p>3. 直接读即可
Z
zhuangjiaju 已提交
155
     */
J
update  
jipengfei.jpf 已提交
156
    @Test
Z
zhuangjiaju 已提交
157 158 159
    public void indexOrNameRead() {
        String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
        // 这里默认读取第一个sheet
Z
zhuangjiaju 已提交
160
        EasyExcel.read(fileName, IndexOrNameData.class, new IndexOrNameDataListener()).sheet().doRead();
J
update  
jipengfei.jpf 已提交
161 162 163
    }
```

Z
zhuangjiaju 已提交
164 165
### <span id="repeatedRead" />读多个sheet
##### excel示例
Z
zhuangjiaju 已提交
166
参照:[excel示例](#simpleReadExcel)
Z
zhuangjiaju 已提交
167
##### 对象
Z
zhuangjiaju 已提交
168
参照:[对象](#simpleReadObject)
Z
zhuangjiaju 已提交
169
##### 监听器
Z
zhuangjiaju 已提交
170
参照:[监听器](#simpleReadListener)
Z
zhuangjiaju 已提交
171
##### 代码
Z
zhuangjiaju 已提交
172 173
```java
    /**
Z
zhuangjiaju 已提交
174
     * 读多个sheet,这里注意一个sheet不能读取多次,多次读取需要重新读取文件
175 176 177 178 179 180
     * <p>
     * 1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>
     * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
     * <p>
     * 3. 直接读即可
Z
zhuangjiaju 已提交
181 182 183
     */
    @Test
    public void repeatedRead() {
184
        // 方法1 如果 sheet1 sheet2 都是同一数据 监听器和头 都写到最外层
Z
zhuangjiaju 已提交
185
        String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
Z
zhuangjiaju 已提交
186 187 188
        ExcelReader excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build();
        ReadSheet readSheet1 = EasyExcel.readSheet(0).build();
        ReadSheet readSheet2 = EasyExcel.readSheet(1).build();
Z
zhuangjiaju 已提交
189 190 191 192
        excelReader.read(readSheet1);
        excelReader.read(readSheet2);
        // 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
        excelReader.finish();
193 194 195 196 197 198 199 200 201 202 203

        // 方法2 如果 sheet1 sheet2 数据不一致的话
        fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
        excelReader = EasyExcel.read(fileName).build();
        // 这里为了简单 所以注册了 同样的head 和Listener 自己使用功能必须不同的Listener
        readSheet1 = EasyExcel.readSheet(0).head(DemoData.class).registerReadListener(new DemoDataListener()).build();
        readSheet2 = EasyExcel.readSheet(1).head(DemoData.class).registerReadListener(new DemoDataListener()).build();
        excelReader.read(readSheet1);
        excelReader.read(readSheet2);
        // 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
        excelReader.finish();
Z
zhuangjiaju 已提交
204
    }
J
update  
jipengfei.jpf 已提交
205 206
```

Z
zhuangjiaju 已提交
207 208
### <span id="converterRead" />日期、数字或者自定义格式转换
##### excel示例
Z
zhuangjiaju 已提交
209
参照:[excel示例](#simpleReadExcel)
Z
zhuangjiaju 已提交
210
##### 对象
Z
zhuangjiaju 已提交
211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
```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;
J
update  
jipengfei.jpf 已提交
229 230
}
```
Z
zhuangjiaju 已提交
231
##### 监听器
Z
zhuangjiaju 已提交
232
参照:[监听器](#simpleReadListener) 只是泛型变了
Z
zhuangjiaju 已提交
233
##### 自定义转换器
Z
zhuangjiaju 已提交
234 235 236 237 238
````java
public class CustomStringStringConverter implements Converter<String> {
    @Override
    public Class supportJavaTypeKey() {
        return String.class;
J
update  
jipengfei.jpf 已提交
239 240
    }

Z
zhuangjiaju 已提交
241 242 243 244
    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return CellDataTypeEnum.STRING;
    }
J
update  
jipengfei.jpf 已提交
245

Z
zhuangjiaju 已提交
246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
    /**
     * 这里读的时候会调用
     *
     * @param cellData
     *            NotNull
     * @param contentProperty
     *            Nullable
     * @param globalConfiguration
     *            NotNull
     * @return
     */
    @Override
    public String convertToJavaData(CellData cellData, ExcelContentProperty contentProperty,
        GlobalConfiguration globalConfiguration) {
        return "自定义:" + cellData.getStringValue();
    }
J
update  
jipengfei.jpf 已提交
262

Z
zhuangjiaju 已提交
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278
    /**
     * 这里是写的时候会调用 不用管
     *
     * @param value
     *            NotNull
     * @param contentProperty
     *            Nullable
     * @param globalConfiguration
     *            NotNull
     * @return
     */
    @Override
    public CellData convertToExcelData(String value, ExcelContentProperty contentProperty,
        GlobalConfiguration globalConfiguration) {
        return new CellData(value);
    }
J
update  
jipengfei.jpf 已提交
279 280

}
Z
zhuangjiaju 已提交
281
````
Z
zhuangjiaju 已提交
282
##### 代码
Z
zhuangjiaju 已提交
283 284 285 286 287
```java
    /**
     * 日期、数字或者自定义格式转换
     * <p>
     * 默认读的转换器{@link DefaultConverterLoader#loadDefaultReadConverter()}
罗成 已提交
288 289 290
     * <p>1. 创建excel对应的实体对象 参照{@link ConverterData}.里面可以使用注解{@link DateTimeFormat}、{@link NumberFormat}或者自定义注解
     * <p>2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link ConverterDataListener}
     * <p>3. 直接读即可
Z
zhuangjiaju 已提交
291 292 293 294 295
     */
    @Test
    public void converterRead() {
        String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 然后千万别忘记 finish
Z
zhuangjiaju 已提交
296
        EasyExcel.read(fileName, ConverterData.class, new ConverterDataListener())
Z
zhuangjiaju 已提交
297 298 299 300 301
            // 这里注意 我们也可以registerConverter来指定自定义转换器, 但是这个转换变成全局了, 所有java为string,excel为string的都会用这个转换器。
            // 如果就想单个字段使用请使用@ExcelProperty 指定converter
            // .registerConverter(new CustomStringStringConverter())
            // 读取sheet
            .sheet().doRead();
J
update  
jipengfei.jpf 已提交
302 303 304
    }
```

Z
zhuangjiaju 已提交
305 306
### <span id="complexHeaderRead" />多行头
##### excel示例
Z
zhuangjiaju 已提交
307
参照:[excel示例](#simpleReadExcel)
Z
zhuangjiaju 已提交
308
##### 对象
Z
zhuangjiaju 已提交
309
参照:[对象](#simpleReadObject)
Z
zhuangjiaju 已提交
310
##### 监听器
Z
zhuangjiaju 已提交
311
参照:[监听器](#simpleReadListener)
Z
zhuangjiaju 已提交
312
##### 代码
Z
zhuangjiaju 已提交
313 314 315 316
```java
    /**
     * 多行头
     *
罗成 已提交
317 318 319
     * <p>1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
     * <p>3. 设置headRowNumber参数,然后读。 这里要注意headRowNumber如果不指定, 会根据你传入的class的{@link ExcelProperty#value()}里面的表头的数量来决定行数,
Z
zhuangjiaju 已提交
320 321 322 323 324 325
     * 如果不传入class则默认为1.当然你指定了headRowNumber不管是否传入class都是以你传入的为准。
     */
    @Test
    public void complexHeaderRead() {
        String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 然后千万别忘记 finish
Z
zhuangjiaju 已提交
326
        EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet()
Z
zhuangjiaju 已提交
327 328 329
            // 这里可以设置1,因为头就是一行。如果多行头,可以设置其他值。不传入也可以,因为默认会根据DemoData 来解析,他没有指定头,也就是默认1行
            .headRowNumber(1).doRead();
    }
J
update  
jipengfei.jpf 已提交
330 331
```

Z
zhuangjiaju 已提交
332 333
### <span id="synchronousRead" />同步的返回
##### excel示例
Z
zhuangjiaju 已提交
334
参照:[excel示例](#simpleReadExcel)
Z
zhuangjiaju 已提交
335
##### 对象
Z
zhuangjiaju 已提交
336
参照:[对象](#simpleReadObject)
Z
zhuangjiaju 已提交
337
##### 代码
Z
zhuangjiaju 已提交
338 339 340 341 342 343 344 345
```java
    /**
     * 同步的返回,不推荐使用,如果数据量大会把数据放到内存里面
     */
    @Test
    public void synchronousRead() {
        String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 同步读取会自动finish
Z
zhuangjiaju 已提交
346
        List<Object> list = EasyExcel.read(fileName).head(DemoData.class).sheet().doReadSync();
Z
zhuangjiaju 已提交
347 348 349 350
        for (Object obj : list) {
            DemoData data = (DemoData)obj;
            LOGGER.info("读取到数据:{}", JSON.toJSONString(data));
        }
J
update  
jipengfei.jpf 已提交
351

Z
zhuangjiaju 已提交
352
        // 这里 也可以不指定class,返回一个list,然后读取第一个sheet 同步读取会自动finish
Z
zhuangjiaju 已提交
353
        list = EasyExcel.read(fileName).sheet().doReadSync();
Z
zhuangjiaju 已提交
354 355 356 357
        for (Object obj : list) {
            // 返回每条数据的键值对 表示所在的列 和所在列的值
            Map<Integer, String> data = (Map<Integer, String>)obj;
            LOGGER.info("读取到数据:{}", JSON.toJSONString(data));
J
update  
jipengfei.jpf 已提交
358 359 360 361
        }
    }
```

庄家钜's avatar
庄家钜 已提交
362
### <span id="headerRead" />读取表头数据
庄家钜's avatar
庄家钜 已提交
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401
##### excel示例
参照:[excel示例](#simpleReadExcel)
##### 对象
参照:[对象](#simpleReadObject)
##### 监听器
参照:[监听器](#simpleReadListener)
里面多了一个方法,只要重写invokeHeadMap方法即可
```java
    /**
     * 这里会一行行的返回头
     *
     * @param headMap
     * @param context
     */
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        LOGGER.info("解析到一条头数据:{}", JSON.toJSONString(headMap));
    }
```
##### 代码
```java
    /**
     * 读取表头数据
     *
     * <p>
     * 1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>
     * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoHeadDataListener}
     * <p>
     * 3. 直接读即可
     */
    @Test
    public void headerRead() {
        String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 然后千万别忘记 finish
        EasyExcel.read(fileName, DemoData.class, new DemoHeadDataListener()).sheet().doRead();
    }
```

庄家钜's avatar
庄家钜 已提交
402
### <span id="exceptionRead" />数据转换等异常处理
庄家钜's avatar
庄家钜 已提交
403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443
##### excel示例
参照:[excel示例](#simpleReadExcel)
##### 对象
参照:[对象](#simpleReadObject)
##### 监听器
参照:[监听器](#simpleReadListener)
里面多了一个方法,只要重写onException方法即可
```java
  /**
     * 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。
     *
     * @param exception
     * @param context
     * @throws Exception
     */
    @Override
    public void onException(Exception exception, AnalysisContext context) {
        LOGGER.error("解析失败,但是继续解析下一行", exception);
    }
```
##### 代码
```java
    /**
     * 数据转换等异常处理
     *
     * <p>
     * 1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>
     * 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoHeadDataListener}
     * <p>
     * 3. 直接读即可
     */
    @Test
    public void exceptionRead() {
        String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 然后千万别忘记 finish
        EasyExcel.read(fileName, DemoData.class, new DemoHeadDataListener()).sheet().doRead();
    }
```


Z
zhuangjiaju 已提交
444 445 446 447 448 449 450 451 452 453 454 455 456
### <span id="webRead" />web中的读
##### 示例代码
DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java](/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java)
##### excel示例
参照:[excel示例](#simpleReadExcel)
##### 对象
参照:[对象](#simpleReadObject) 只是名字变了
##### 监听器
参照:[监听器](#simpleReadListener) 只是泛型变了
##### 代码
```java
    /**
     * 文件上传
罗成 已提交
457 458 459
     * <p>1. 创建excel对应的实体对象 参照{@link UploadData}
     * <p>2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link UploadDataListener}
     * <p>3. 直接读即可
Z
zhuangjiaju 已提交
460 461 462 463
     */
    @PostMapping("upload")
    @ResponseBody
    public String upload(MultipartFile file) throws IOException {
Z
zhuangjiaju 已提交
464
        EasyExcel.read(file.getInputStream(), UploadData.class, new UploadDataListener()).sheet().doRead();
Z
zhuangjiaju 已提交
465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502
        return "success";
    }
```

## 写excel样例
### 通用数据生成 后面不会重复写
```java
    private List<DemoData> data() {
        List<DemoData> list = new ArrayList<DemoData>();
        for (int i = 0; i < 10; i++) {
            DemoData data = new DemoData();
            data.setString("字符串" + i);
            data.setDate(new Date());
            data.setDoubleData(0.56);
            list.add(data);
        }
        return list;
    }
```
### <span id="simpleWrite" />最简单的写
##### excel示例
![img](img/readme/quickstart/write/simpleWrite.png)
##### <span id="simpleWriteObject" />对象
```java
@Data
public class DemoData {
    @ExcelProperty("字符串标题")
    private String string;
    @ExcelProperty("日期标题")
    private Date date;
    @ExcelProperty("数字标题")
    private Double doubleData;
}
```
##### 代码
```java
    /**
     * 最简单的写
罗成 已提交
503 504
     * <p>1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>2. 直接写即可
Z
zhuangjiaju 已提交
505 506 507 508 509 510 511
     */
    @Test
    public void simpleWrite() {
        // 写法1
        String fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
        // 如果这里想使用03 则 传入excelType参数即可
Z
zhuangjiaju 已提交
512
        EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
Z
zhuangjiaju 已提交
513 514 515 516

        // 写法2
        fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去读
Z
zhuangjiaju 已提交
517 518
        ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build();
        WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
Z
zhuangjiaju 已提交
519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546
        excelWriter.write(data(), writeSheet);
        /// 千万别忘记finish 会帮忙关闭流
        excelWriter.finish();
    }
```

### <span id="indexWrite" />指定写入的列
##### excel示例
![img](img/readme/quickstart/write/indexWrite.png)
##### 对象
```java
@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;
}
```
##### 代码
```java
    /**
     * 指定写入的列
罗成 已提交
547 548 549
     * <p>1. 创建excel对应的实体对象 参照{@link IndexData}
     * <p>2. 使用{@link ExcelProperty}注解指定写入的列
     * <p>3. 直接写即可
Z
zhuangjiaju 已提交
550 551 552 553 554
     */
    @Test
    public void indexWrite() {
        String fileName = TestFileUtil.getPath() + "indexWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
Z
zhuangjiaju 已提交
555
        EasyExcel.write(fileName, IndexData.class).sheet("模板").doWrite(data());
Z
zhuangjiaju 已提交
556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577
    }
```

### <span id="complexHeadWrite" />复杂头写入
##### excel示例
![img](img/readme/quickstart/write/complexHeadWrite.png)
##### 对象
```java
@Data
public class ComplexHeadData {
    @ExcelProperty({"主标题", "字符串标题"})
    private String string;
    @ExcelProperty({"主标题", "日期标题"})
    private Date date;
    @ExcelProperty({"主标题", "数字标题"})
    private Double doubleData;
}
```
##### 代码
```java
    /**
     * 复杂头写入
罗成 已提交
578 579 580
     * <p>1. 创建excel对应的实体对象 参照{@link ComplexHeadData}
     * <p>2. 使用{@link ExcelProperty}注解指定复杂的头
     * <p>3. 直接写即可
Z
zhuangjiaju 已提交
581 582 583 584 585
     */
    @Test
    public void complexHeadWrite() {
        String fileName = TestFileUtil.getPath() + "complexHeadWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
Z
zhuangjiaju 已提交
586
        EasyExcel.write(fileName, ComplexHeadData.class).sheet("模板").doWrite(data());
Z
zhuangjiaju 已提交
587 588 589 590 591 592 593 594 595 596
    }
```

### <span id="repeatedWrite" />重复多次写入
##### excel示例
![img](img/readme/quickstart/write/repeatedWrite.png)
##### 对象
参照:[对象](#simpleWriteObject)
##### 代码
```java
597
  /**
Z
zhuangjiaju 已提交
598
     * 重复多次写入
庄家钜's avatar
庄家钜 已提交
599 600 601 602 603 604
     * <p>
     * 1. 创建excel对应的实体对象 参照{@link ComplexHeadData}
     * <p>
     * 2. 使用{@link ExcelProperty}注解指定复杂的头
     * <p>
     * 3. 直接调用二次写入即可
Z
zhuangjiaju 已提交
605 606 607
     */
    @Test
    public void repeatedWrite() {
608
        // 方法1 如果写到同一个sheet
Z
zhuangjiaju 已提交
609 610
        String fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去读
Z
zhuangjiaju 已提交
611
        ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build();
庄家钜's avatar
庄家钜 已提交
612
        // 这里注意 如果同一个sheet只要创建一次
Z
zhuangjiaju 已提交
613
        WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
庄家钜's avatar
庄家钜 已提交
614 615 616 617 618 619
        // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来
        for (int i = 0; i < 5; i++) {
            // 分页去数据库查询数据 这里可以去数据库查询每一页的数据
            List<DemoData> data = data();
            excelWriter.write(data, writeSheet);
        }
Z
zhuangjiaju 已提交
620 621
        /// 千万别忘记finish 会帮忙关闭流
        excelWriter.finish();
622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651

        // 方法2 如果写到不同的sheet 同一个对象
        fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 指定文件
        excelWriter = EasyExcel.write(fileName, DemoData.class).build();
        // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面
        for (int i = 0; i < 5; i++) {
            // 每次都要创建writeSheet 这里注意必须指定sheetNo
            writeSheet = EasyExcel.writerSheet(i, "模板").build();
            // 分页去数据库查询数据 这里可以去数据库查询每一页的数据
            List<DemoData> data = data();
            excelWriter.write(data, writeSheet);
        }
        /// 千万别忘记finish 会帮忙关闭流
        excelWriter.finish();

        // 方法3 如果写到不同的sheet 不同的对象
        fileName = TestFileUtil.getPath() + "repeatedWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 指定文件
        excelWriter = EasyExcel.write(fileName).build();
        // 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面
        for (int i = 0; i < 5; i++) {
            // 每次都要创建writeSheet 这里注意必须指定sheetNo。这里注意DemoData.class 可以每次都变,我这里为了方便 所以用的同一个class 实际上可以一直变
            writeSheet = EasyExcel.writerSheet(i, "模板").head(DemoData.class).build();
            // 分页去数据库查询数据 这里可以去数据库查询每一页的数据
            List<DemoData> data = data();
            excelWriter.write(data, writeSheet);
        }
        /// 千万别忘记finish 会帮忙关闭流
        excelWriter.finish();
Z
zhuangjiaju 已提交
652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684
    }
```

### <span id="converterWrite" />日期、数字或者自定义格式转换
##### excel示例
![img](img/readme/quickstart/write/converterWrite.png)
##### 对象
```java
@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;
}
```
##### 代码
```java
    /**
     * 日期、数字或者自定义格式转换
罗成 已提交
685 686 687
     * <p>1. 创建excel对应的实体对象 参照{@link ConverterData}
     * <p>2. 使用{@link ExcelProperty}配合使用注解{@link DateTimeFormat}、{@link NumberFormat}或者自定义注解
     * <p>3. 直接写即可
Z
zhuangjiaju 已提交
688 689 690 691 692
     */
    @Test
    public void converterWrite() {
        String fileName = TestFileUtil.getPath() + "converterWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
Z
zhuangjiaju 已提交
693
        EasyExcel.write(fileName, ConverterData.class).sheet("模板").doWrite(data());
Z
zhuangjiaju 已提交
694 695 696
    }
```

Z
zhuangjiaju 已提交
697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719
### <span id="imageWrite" />图片导出
##### excel示例
![img](img/readme/quickstart/write/imageWrite.png)
##### 对象
```java
@Data
@ContentRowHeight(100)
@ColumnWidth(100 / 8)
public class ImageData {
    private File file;
    private InputStream inputStream;
    /**
     * 如果string类型 必须指定转换器,string默认转换成string
     */
    @ExcelProperty(converter = StringImageConverter.class)
    private String string;
    private byte[] byteArray;
}
```
##### 代码
```java
    /**
     * 图片导出
罗成 已提交
720 721
     * <p>1. 创建excel对应的实体对象 参照{@link ImageData}
     * <p>2. 直接写即可
Z
zhuangjiaju 已提交
722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748
     */
    @Test
    public void imageWrite() throws Exception {
        String fileName = TestFileUtil.getPath() + "imageWrite" + System.currentTimeMillis() + ".xlsx";
        // 如果使用流 记得关闭
        InputStream inputStream = null;
        try {
            List<ImageData> list = new ArrayList<ImageData>();
            ImageData imageData = new ImageData();
            list.add(imageData);
            String imagePath = TestFileUtil.getPath() + "converter" + File.separator + "img.jpg";
            // 放入四种类型的图片 实际使用只要选一种即可
            imageData.setByteArray(FileUtils.readFileToByteArray(new File(imagePath)));
            imageData.setFile(new File(imagePath));
            imageData.setString(imagePath);
            inputStream = FileUtils.openInputStream(new File(imagePath));
            imageData.setInputStream(inputStream);
            EasyExcel.write(fileName, ImageData.class).sheet().doWrite(list);
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }
        }
    }
```


Z
zhuangjiaju 已提交
749 750 751 752 753 754 755 756 757 758 759
### <span id="templateWrite" />根据模板写入
##### 模板excel示例
参照:[模板excel示例](#simpleReadExcel)
##### excel示例
![img](img/readme/quickstart/write/templateWrite.png)
##### 对象
参照:[对象](#simpleWriteObject)
##### 代码
```java
    /**
     * 根据模板写入
罗成 已提交
760 761 762 763
     * <p>1. 创建excel对应的实体对象 参照{@link IndexData}
     * <p>2. 使用{@link ExcelProperty}注解指定写入的列
     * <p>3. 使用withTemplate 读取模板
     * <p>4. 直接写即可
Z
zhuangjiaju 已提交
764 765 766 767 768 769
     */
    @Test
    public void templateWrite() {
        String templateFileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
        String fileName = TestFileUtil.getPath() + "templateWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
Z
zhuangjiaju 已提交
770
        EasyExcel.write(fileName, DemoData.class).withTemplate(templateFileName).sheet().doWrite(data());
Z
zhuangjiaju 已提交
771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799
    }
```

### <span id="widthAndHeightWrite" />列宽、行高
##### excel示例
![img](img/readme/quickstart/write/widthAndHeightWrite.png)
##### 对象
````java
@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;
}
````
##### 代码
```java
    /**
     * 列宽、行高
罗成 已提交
800 801 802
     * <p>1. 创建excel对应的实体对象 参照{@link WidthAndHeightData}
     * <p>2. 使用注解{@link ColumnWidth}、{@link HeadRowHeight}、{@link ContentRowHeight}指定宽度或高度
     * <p>3. 直接写即可
Z
zhuangjiaju 已提交
803 804 805 806 807
     */
    @Test
    public void widthAndHeightWrite() {
        String fileName = TestFileUtil.getPath() + "widthAndHeightWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
Z
zhuangjiaju 已提交
808
        EasyExcel.write(fileName, WidthAndHeightData.class).sheet("模板").doWrite(data());
Z
zhuangjiaju 已提交
809 810 811 812 813 814 815 816 817 818 819 820
    }
```

### <span id="styleWrite" />自定义样式
##### excel示例
![img](img/readme/quickstart/write/styleWrite.png)
##### 对象
参照:[对象](#simpleWriteObject)
##### 代码
```java
    /**
     * 自定义样式
罗成 已提交
821 822 823
     * <p>1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>2. 创建一个style策略 并注册
     * <p>3. 直接写即可
Z
zhuangjiaju 已提交
824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849
     */
    @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,名字为模板 然后文件流会自动关闭
Z
zhuangjiaju 已提交
850
        EasyExcel.write(fileName, DemoData.class).registerWriteHandler(horizontalCellStyleStrategy).sheet("模板")
Z
zhuangjiaju 已提交
851 852 853 854 855 856 857 858 859 860 861 862 863
            .doWrite(data());
    }
```

### <span id="mergeWrite" />合并单元格
##### excel示例
![img](img/readme/quickstart/write/mergeWrite.png)
##### 对象
参照:[对象](#simpleWriteObject)
##### 代码
```java
   /**
     * 合并单元格
罗成 已提交
864 865 866
     * <p>1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>2. 创建一个merge策略 并注册
     * <p>3. 直接写即可
Z
zhuangjiaju 已提交
867 868 869 870 871 872 873
     */
    @Test
    public void mergeWrite() {
        String fileName = TestFileUtil.getPath() + "mergeWrite" + System.currentTimeMillis() + ".xlsx";
        // 每隔2行会合并 把eachColumn 设置成 3 也就是我们数据的长度,所以就第一列会合并。当然其他合并策略也可以自己写
        LoopMergeStrategy loopMergeStrategy = new LoopMergeStrategy(2, 0);
        // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
Z
zhuangjiaju 已提交
874
        EasyExcel.write(fileName, DemoData.class).registerWriteHandler(loopMergeStrategy).sheet("模板")
Z
zhuangjiaju 已提交
875 876 877 878 879 880 881 882 883 884 885 886 887
            .doWrite(data());
    }
```

### <span id="tableWrite" />使用table去写入
##### excel示例
![img](img/readme/quickstart/write/tableWrite.png)
##### 对象
参照:[对象](#simpleWriteObject)
##### 代码
```java
   /**
     * 使用table去写入
罗成 已提交
888 889
     * <p>1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>2. 然后写入table即可
Z
zhuangjiaju 已提交
890 891 892 893 894 895
     */
    @Test
    public void tableWrite() {
        String fileName = TestFileUtil.getPath() + "tableWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里直接写多个table的案例了,如果只有一个 也可以直一行代码搞定,参照其他案例
        // 这里 需要指定写用哪个class去读
Z
zhuangjiaju 已提交
896
        ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build();
Z
zhuangjiaju 已提交
897
        // 把sheet设置为不需要头 不然会输出sheet的头 这样看起来第一个table 就有2个头了
Z
zhuangjiaju 已提交
898
        WriteSheet writeSheet = EasyExcel.writerSheet("模板").needHead(Boolean.FALSE).build();
Z
zhuangjiaju 已提交
899
        // 这里必须指定需要头,table 会继承sheet的配置,sheet配置了不需要,table 默认也是不需要
Z
zhuangjiaju 已提交
900 901
        WriteTable writeTable0 = EasyExcel.writerTable(0).needHead(Boolean.TRUE).build();
        WriteTable writeTable1 = EasyExcel.writerTable(1).needHead(Boolean.TRUE).build();
Z
zhuangjiaju 已提交
902 903 904 905 906 907 908 909 910
        // 第一次写入会创建头
        excelWriter.write(data(), writeSheet, writeTable0);
        // 第二次写如也会创建头,然后在第一次的后面写入数据
        excelWriter.write(data(), writeSheet, writeTable1);
        /// 千万别忘记finish 会帮忙关闭流
        excelWriter.finish();
    }
```

Z
zhuangjiaju 已提交
911 912 913 914 915 916 917 918 919 920 921 922
### <span id="tableWrite" />动态头,实时生成头写入
##### excel示例
![img](img/readme/quickstart/write/dynamicHeadWrite.png)
##### 对象
参照:[对象](#simpleWriteObject)
##### 代码
```java
    /**
     * 动态头,实时生成头写入
     * <p>
     * 思路是这样子的,先创建List<String>头格式的sheet仅仅写入头,然后通过table 不写入头的方式 去写入数据
     *
923 924 925 926
     * <p>
     * 1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>
     * 2. 然后写入table即可
Z
zhuangjiaju 已提交
927 928 929 930
     */
    @Test
    public void dynamicHeadWrite() {
        String fileName = TestFileUtil.getPath() + "dynamicHeadWrite" + System.currentTimeMillis() + ".xlsx";
Z
zhuangjiaju 已提交
931
        EasyExcel.write(fileName)
Z
zhuangjiaju 已提交
932 933
            // 这里放入动态头
            .head(head()).sheet("模板")
934 935
            // 当然这里数据也可以用 List<List<String>> 去传入
            .doWrite(data());
Z
zhuangjiaju 已提交
936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952
    }

    private List<List<String>> head() {
        List<List<String>> list = new ArrayList<List<String>>();
        List<String> head0 = new ArrayList<String>();
        head0.add("字符串" + System.currentTimeMillis());
        List<String> head1 = new ArrayList<String>();
        head1.add("数字" + System.currentTimeMillis());
        List<String> head2 = new ArrayList<String>();
        head2.add("日期" + System.currentTimeMillis());
        list.add(head0);
        list.add(head1);
        list.add(head2);
        return list;
    }
```

Z
zhuangjiaju 已提交
953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969
### <span id="longestMatchColumnWidthWrite" />自动列宽(不太精确)
##### excel示例
![img](img/readme/quickstart/write/longestMatchColumnWidthWrite.png)
##### 对象
```java
@Data
public class LongestMatchColumnWidthData {
    @ExcelProperty("字符串标题")
    private String string;
    @ExcelProperty("日期标题很长日期标题很长日期标题很长很长")
    private Date date;
    @ExcelProperty("数字")
    private Double doubleData;
}
```
##### 代码
```java
庄家钜's avatar
庄家钜 已提交
970
   /**
Z
zhuangjiaju 已提交
971 972 973 974 975 976 977
     * 自动列宽(不太精确)
     * <p>
     * 这个目前不是很好用,比如有数字就会导致换行。而且长度也不是刚好和实际长度一致。 所以需要精确到刚好列宽的慎用。 当然也可以自己参照
     * {@link LongestMatchColumnWidthStyleStrategy}重新实现.
     * <p>
     * poi 自带{@link SXSSFSheet#autoSizeColumn(int)} 对中文支持也不太好。目前没找到很好的算法。 有的话可以推荐下。
     *
庄家钜's avatar
庄家钜 已提交
978 979 980 981 982 983
     * <p>
     * 1. 创建excel对应的实体对象 参照{@link LongestMatchColumnWidthData}
     * <p>
     * 2. 注册策略{@link LongestMatchColumnWidthStyleStrategy}
     * <p>
     * 3. 直接写即可
Z
zhuangjiaju 已提交
984 985 986 987 988 989
     */
    @Test
    public void longestMatchColumnWidthWrite() {
        String fileName =
            TestFileUtil.getPath() + "longestMatchColumnWidthWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
庄家钜's avatar
庄家钜 已提交
990 991
        EasyExcel.write(fileName, LongestMatchColumnWidthData.class)
            .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).sheet("模板").doWrite(dataLong());
Z
zhuangjiaju 已提交
992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006
    }

    private List<LongestMatchColumnWidthData> dataLong() {
        List<LongestMatchColumnWidthData> list = new ArrayList<LongestMatchColumnWidthData>();
        for (int i = 0; i < 10; i++) {
            LongestMatchColumnWidthData data = new LongestMatchColumnWidthData();
            data.setString("测试很长的字符串测试很长的字符串测试很长的字符串" + i);
            data.setDate(new Date());
            data.setDoubleData(1000000000000.0);
            list.add(data);
        }
        return list;
    }
```

庄家钜's avatar
庄家钜 已提交
1007 1008 1009 1010 1011
### <span id="customHandlerWrite" />自定义拦截器(上面几点都不符合但是要对单元格进行操作的参照这个)
##### excel示例
![img](img/readme/quickstart/write/customHandlerWrite.png)
##### 对象
参照:[对象](#simpleWriteObject)
庄家钜's avatar
庄家钜 已提交
1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 1047 1048 1049 1050 1051 1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068 1069 1070 1071 1072
##### 定义拦截器
````java
/**
 * 自定义拦截器。对第一行第一列的头超链接到:https://github.com/alibaba/easyexcel
 *
 * @author Jiaju Zhuang
 */
public class CustomCellWriteHandler implements CellWriteHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(CustomCellWriteHandler.class);

    @Override
    public void beforeCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row,
        Head head, int relativeRowIndex, boolean isHead) {

    }

    @Override
    public void afterCellCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, CellData cellData,
        Cell cell, Head head, int relativeRowIndex, boolean isHead) {
        // 这里可以对cell进行任何操作
        LOGGER.info("第{}行,第{}列写入完成。", cell.getRowIndex(), cell.getColumnIndex());
        if (isHead && cell.getColumnIndex() == 0) {
            CreationHelper createHelper = writeSheetHolder.getSheet().getWorkbook().getCreationHelper();
            Hyperlink hyperlink = createHelper.createHyperlink(HyperlinkType.URL);
            hyperlink.setAddress("https://github.com/alibaba/easyexcel");
            cell.setHyperlink(hyperlink);
        }
    }

}
````
````java
/**
 * 自定义拦截器.对第一列第一行和第二行的数据新增下拉框,显示 测试1 测试2
 *
 * @author Jiaju Zhuang
 */
public class CustomSheetWriteHandler implements SheetWriteHandler {

    private static final Logger LOGGER = LoggerFactory.getLogger(CustomSheetWriteHandler.class);

    @Override
    public void beforeSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {

    }

    @Override
    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
        LOGGER.info("第{}个Sheet写入成功。", writeSheetHolder.getSheetNo());

        // 区间设置 第一列第一行和第二行的数据。由于第一行是头,所以第一、二行的数据实际上是第二三行
        CellRangeAddressList cellRangeAddressList = new CellRangeAddressList(1, 2, 0, 0);
        DataValidationHelper helper = writeSheetHolder.getSheet().getDataValidationHelper();
        DataValidationConstraint constraint = helper.createExplicitListConstraint(new String[] {"测试1", "测试2"});
        DataValidation dataValidation = helper.createValidation(constraint, cellRangeAddressList);
        writeSheetHolder.getSheet().addValidationData(dataValidation);
    }
}

````
庄家钜's avatar
庄家钜 已提交
1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093 1094
##### 代码
```java
    /**
     * 下拉,超链接等自定义拦截器(上面几点都不符合但是要对单元格进行操作的参照这个)
     * <p>
     * demo这里实现2点。1. 对第一行第一列的头超链接到:https://github.com/alibaba/easyexcel 2. 对第一列第一行和第二行的数据新增下拉框,显示 测试1 测试2
     * <p>
     * 1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>
     * 2. 注册拦截器 {@link CustomCellWriteHandler} {@link CustomSheetWriteHandler}
     * <p>
     * 2. 直接写即可
     */
    @Test
    public void customHandlerWrite() {
        String fileName = TestFileUtil.getPath() + "customHandlerWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
        EasyExcel.write(fileName, DemoData.class).registerWriteHandler(new CustomSheetWriteHandler())
            .registerWriteHandler(new CustomCellWriteHandler()).sheet("模板").doWrite(data());
    }
```

Z
zhuangjiaju 已提交
1095 1096 1097 1098 1099 1100 1101
### <span id="webWrite" />web中的写
##### 示例代码
DEMO代码地址:[https://github.com/alibaba/easyexcel/blob/master/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java](/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java)
##### 对象
参照:[对象](#simpleWriteObject) 就是名称变了下
##### 代码
```java
庄家钜's avatar
庄家钜 已提交
1102
    /**
Z
zhuangjiaju 已提交
1103
     * 文件下载
庄家钜's avatar
庄家钜 已提交
1104 1105 1106 1107 1108 1109
     * <p>
     * 1. 创建excel对应的实体对象 参照{@link DownloadData}
     * <p>
     * 2. 设置返回的 参数
     * <p>
     * 3. 直接写,这里注意,finish的时候会自动关闭OutputStream,当然你外面再关闭流问题不大
Z
zhuangjiaju 已提交
1110 1111 1112
     */
    @GetMapping("download")
    public void download(HttpServletResponse response) throws IOException {
1113
        // 这里注意 有同学反应下载的文件名不对。这个时候 请别使用swagger 他会有影响
Z
zhuangjiaju 已提交
1114 1115
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
1116 1117 1118
        // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
        String fileName = URLEncoder.encode("测试", "UTF-8");
        response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
Z
zhuangjiaju 已提交
1119
        EasyExcel.write(response.getOutputStream(), DownloadData.class).sheet("模板").doWrite(data());
Z
zhuangjiaju 已提交
1120 1121
    }
```
Z
zhuangjiaju 已提交
1122
## 测试数据分析
J
update  
jipengfei.jpf 已提交
1123 1124 1125 1126 1127 1128
![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)
![POI usermodel PK easyexcel(Excel 2007) (1).png](http://ata2-img.cn-hangzhou.img-pub.aliyun-inc.com/4de1ac95bdfaa4b1870b224af4f4cb75.png)
从上面的性能测试可以看出easyexcel在解析耗时上比poiuserModel模式弱了一些。主要原因是我内部采用了反射做模型字段映射,中间我也加了cache,但感觉这点差距可以接受的。但在内存消耗上差别就比较明显了,easyexcel在后面文件再增大,内存消耗几乎不会增加了。但poi userModel就不一样了,简直就要爆掉了。想想一个excel解析200M,同时有20个人再用估计一台机器就挂了。