quickstart.md 41.3 KB
Newer Older
J
update  
jipengfei.jpf 已提交
1
# easyexcel核心功能
Z
zhuangjiaju 已提交
2
## 目录
庄家钜's avatar
庄家钜 已提交
3
### 前言
庄家钜's avatar
庄家钜 已提交
4
#### 关于@Data
庄家钜's avatar
庄家钜 已提交
5
读写的对象都用到了[Lombok](https://www.projectlombok.org/),他会自动生成`get`,`set` ,如果不需要自己创建对象并生成`get`,`set`
庄家钜's avatar
庄家钜 已提交
6 7 8 9
#### 以下功能目前不支持
* 单个文件的并发写入、读取
* 读取图片
*
庄家钜's avatar
庄家钜 已提交
10 11
#### 关于版本兼容
目前poi用的 4.0.1 建议检查是否该版本。如果出现找不到class之类的,八成就是这个原因。
庄家钜's avatar
庄家钜 已提交
12 13
#### 详细参数介绍
有些参数不知道怎么用,或者有些功能不知道用什么参数,参照:[详细参数介绍](/docs/API.md)
庄家钜's avatar
庄家钜 已提交
14
#### 开源项目不容易,如果觉得本项目对您的工作还是有帮助的话,请在右上角帮忙点个★Star。
Z
zhuangjiaju 已提交
15 16 17
### 读
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 已提交
18 19 20 21 22
* [指定列的下标或者列名](#indexOrNameRead)
* [读多个sheet](#repeatedRead)
* [日期、数字或者自定义格式转换](#converterRead)
* [多行头](#complexHeaderRead)
* [同步的返回](#synchronousRead)
庄家钜's avatar
庄家钜 已提交
23
* [读取表头数据](#headerRead)
庄家钜's avatar
庄家钜 已提交
24
* [数据转换等异常处理](#exceptionRead)
Z
zhuangjiaju 已提交
25
* [web中的读](#webRead)
庄家钜's avatar
庄家钜 已提交
26

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

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

Z
zhuangjiaju 已提交
78
    @Override
J
update  
jipengfei.jpf 已提交
79
    public void doAfterAllAnalysed(AnalysisContext context) {
Z
zhuangjiaju 已提交
80 81
        saveData();
        LOGGER.info("所有数据解析完成!");
J
update  
jipengfei.jpf 已提交
82
    }
Z
zhuangjiaju 已提交
83 84 85 86 87 88 89

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

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

Z
zhuangjiaju 已提交
118 119
### <span id="indexOrNameRead" />指定列的下标或者列名
##### excel示例
Z
zhuangjiaju 已提交
120
参照:[excel示例](#simpleReadExcel)
Z
zhuangjiaju 已提交
121
##### 对象
Z
zhuangjiaju 已提交
122 123 124 125 126 127 128 129 130 131 132 133 134 135 136
```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 已提交
137 138
}
```
Z
zhuangjiaju 已提交
139
##### 监听器
Z
zhuangjiaju 已提交
140
参照:[监听器](#simpleReadListener) 只是泛型变了而已
Z
zhuangjiaju 已提交
141
##### 代码
Z
zhuangjiaju 已提交
142 143 144 145
```java
    /**
     * 指定列的下标或者列名
     *
罗成 已提交
146 147 148
     * <p>1. 创建excel对应的实体对象,并使用{@link ExcelProperty}注解. 参照{@link IndexOrNameData}
     * <p>2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link IndexOrNameDataListener}
     * <p>3. 直接读即可
Z
zhuangjiaju 已提交
149
     */
J
update  
jipengfei.jpf 已提交
150
    @Test
Z
zhuangjiaju 已提交
151 152 153
    public void indexOrNameRead() {
        String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
        // 这里默认读取第一个sheet
Z
zhuangjiaju 已提交
154
        EasyExcel.read(fileName, IndexOrNameData.class, new IndexOrNameDataListener()).sheet().doRead();
J
update  
jipengfei.jpf 已提交
155 156 157
    }
```

Z
zhuangjiaju 已提交
158 159
### <span id="repeatedRead" />读多个sheet
##### excel示例
Z
zhuangjiaju 已提交
160
参照:[excel示例](#simpleReadExcel)
Z
zhuangjiaju 已提交
161
##### 对象
Z
zhuangjiaju 已提交
162
参照:[对象](#simpleReadObject)
Z
zhuangjiaju 已提交
163
##### 监听器
Z
zhuangjiaju 已提交
164
参照:[监听器](#simpleReadListener)
Z
zhuangjiaju 已提交
165
##### 代码
Z
zhuangjiaju 已提交
166 167
```java
    /**
Z
zhuangjiaju 已提交
168
     * 读多个sheet,这里注意一个sheet不能读取多次,多次读取需要重新读取文件
罗成 已提交
169 170 171
     * <p>1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
     * <p>3. 直接读即可
Z
zhuangjiaju 已提交
172 173 174 175
     */
    @Test
    public void repeatedRead() {
        String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
Z
zhuangjiaju 已提交
176 177 178
        ExcelReader excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build();
        ReadSheet readSheet1 = EasyExcel.readSheet(0).build();
        ReadSheet readSheet2 = EasyExcel.readSheet(1).build();
Z
zhuangjiaju 已提交
179 180 181 182 183
        excelReader.read(readSheet1);
        excelReader.read(readSheet2);
        // 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
        excelReader.finish();
    }
J
update  
jipengfei.jpf 已提交
184 185
```

Z
zhuangjiaju 已提交
186 187
### <span id="converterRead" />日期、数字或者自定义格式转换
##### excel示例
Z
zhuangjiaju 已提交
188
参照:[excel示例](#simpleReadExcel)
Z
zhuangjiaju 已提交
189
##### 对象
Z
zhuangjiaju 已提交
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207
```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 已提交
208 209
}
```
Z
zhuangjiaju 已提交
210
##### 监听器
Z
zhuangjiaju 已提交
211
参照:[监听器](#simpleReadListener) 只是泛型变了
Z
zhuangjiaju 已提交
212
##### 自定义转换器
Z
zhuangjiaju 已提交
213 214 215 216 217
````java
public class CustomStringStringConverter implements Converter<String> {
    @Override
    public Class supportJavaTypeKey() {
        return String.class;
J
update  
jipengfei.jpf 已提交
218 219
    }

Z
zhuangjiaju 已提交
220 221 222 223
    @Override
    public CellDataTypeEnum supportExcelTypeKey() {
        return CellDataTypeEnum.STRING;
    }
J
update  
jipengfei.jpf 已提交
224

Z
zhuangjiaju 已提交
225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240
    /**
     * 这里读的时候会调用
     *
     * @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 已提交
241

Z
zhuangjiaju 已提交
242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257
    /**
     * 这里是写的时候会调用 不用管
     *
     * @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 已提交
258 259

}
Z
zhuangjiaju 已提交
260
````
Z
zhuangjiaju 已提交
261
##### 代码
Z
zhuangjiaju 已提交
262 263 264 265 266
```java
    /**
     * 日期、数字或者自定义格式转换
     * <p>
     * 默认读的转换器{@link DefaultConverterLoader#loadDefaultReadConverter()}
罗成 已提交
267 268 269
     * <p>1. 创建excel对应的实体对象 参照{@link ConverterData}.里面可以使用注解{@link DateTimeFormat}、{@link NumberFormat}或者自定义注解
     * <p>2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link ConverterDataListener}
     * <p>3. 直接读即可
Z
zhuangjiaju 已提交
270 271 272 273 274
     */
    @Test
    public void converterRead() {
        String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 然后千万别忘记 finish
Z
zhuangjiaju 已提交
275
        EasyExcel.read(fileName, ConverterData.class, new ConverterDataListener())
Z
zhuangjiaju 已提交
276 277 278 279 280
            // 这里注意 我们也可以registerConverter来指定自定义转换器, 但是这个转换变成全局了, 所有java为string,excel为string的都会用这个转换器。
            // 如果就想单个字段使用请使用@ExcelProperty 指定converter
            // .registerConverter(new CustomStringStringConverter())
            // 读取sheet
            .sheet().doRead();
J
update  
jipengfei.jpf 已提交
281 282 283
    }
```

Z
zhuangjiaju 已提交
284 285
### <span id="complexHeaderRead" />多行头
##### excel示例
Z
zhuangjiaju 已提交
286
参照:[excel示例](#simpleReadExcel)
Z
zhuangjiaju 已提交
287
##### 对象
Z
zhuangjiaju 已提交
288
参照:[对象](#simpleReadObject)
Z
zhuangjiaju 已提交
289
##### 监听器
Z
zhuangjiaju 已提交
290
参照:[监听器](#simpleReadListener)
Z
zhuangjiaju 已提交
291
##### 代码
Z
zhuangjiaju 已提交
292 293 294 295
```java
    /**
     * 多行头
     *
罗成 已提交
296 297 298
     * <p>1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}
     * <p>3. 设置headRowNumber参数,然后读。 这里要注意headRowNumber如果不指定, 会根据你传入的class的{@link ExcelProperty#value()}里面的表头的数量来决定行数,
Z
zhuangjiaju 已提交
299 300 301 302 303 304
     * 如果不传入class则默认为1.当然你指定了headRowNumber不管是否传入class都是以你传入的为准。
     */
    @Test
    public void complexHeaderRead() {
        String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 然后千万别忘记 finish
Z
zhuangjiaju 已提交
305
        EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet()
Z
zhuangjiaju 已提交
306 307 308
            // 这里可以设置1,因为头就是一行。如果多行头,可以设置其他值。不传入也可以,因为默认会根据DemoData 来解析,他没有指定头,也就是默认1行
            .headRowNumber(1).doRead();
    }
J
update  
jipengfei.jpf 已提交
309 310
```

Z
zhuangjiaju 已提交
311 312
### <span id="synchronousRead" />同步的返回
##### excel示例
Z
zhuangjiaju 已提交
313
参照:[excel示例](#simpleReadExcel)
Z
zhuangjiaju 已提交
314
##### 对象
Z
zhuangjiaju 已提交
315
参照:[对象](#simpleReadObject)
Z
zhuangjiaju 已提交
316
##### 代码
Z
zhuangjiaju 已提交
317 318 319 320 321 322 323 324
```java
    /**
     * 同步的返回,不推荐使用,如果数据量大会把数据放到内存里面
     */
    @Test
    public void synchronousRead() {
        String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 同步读取会自动finish
Z
zhuangjiaju 已提交
325
        List<Object> list = EasyExcel.read(fileName).head(DemoData.class).sheet().doReadSync();
Z
zhuangjiaju 已提交
326 327 328 329
        for (Object obj : list) {
            DemoData data = (DemoData)obj;
            LOGGER.info("读取到数据:{}", JSON.toJSONString(data));
        }
J
update  
jipengfei.jpf 已提交
330

Z
zhuangjiaju 已提交
331
        // 这里 也可以不指定class,返回一个list,然后读取第一个sheet 同步读取会自动finish
Z
zhuangjiaju 已提交
332
        list = EasyExcel.read(fileName).sheet().doReadSync();
Z
zhuangjiaju 已提交
333 334 335 336
        for (Object obj : list) {
            // 返回每条数据的键值对 表示所在的列 和所在列的值
            Map<Integer, String> data = (Map<Integer, String>)obj;
            LOGGER.info("读取到数据:{}", JSON.toJSONString(data));
J
update  
jipengfei.jpf 已提交
337 338 339 340
        }
    }
```

庄家钜's avatar
庄家钜 已提交
341
### <span id="headerRead" />读取表头数据
庄家钜's avatar
庄家钜 已提交
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
##### 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
庄家钜 已提交
381
### <span id="exceptionRead" />数据转换等异常处理
庄家钜's avatar
庄家钜 已提交
382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422
##### 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 已提交
423 424 425 426 427 428 429 430 431 432 433 434 435
### <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
    /**
     * 文件上传
罗成 已提交
436 437 438
     * <p>1. 创建excel对应的实体对象 参照{@link UploadData}
     * <p>2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link UploadDataListener}
     * <p>3. 直接读即可
Z
zhuangjiaju 已提交
439 440 441 442
     */
    @PostMapping("upload")
    @ResponseBody
    public String upload(MultipartFile file) throws IOException {
Z
zhuangjiaju 已提交
443
        EasyExcel.read(file.getInputStream(), UploadData.class, new UploadDataListener()).sheet().doRead();
Z
zhuangjiaju 已提交
444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481
        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
    /**
     * 最简单的写
罗成 已提交
482 483
     * <p>1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>2. 直接写即可
Z
zhuangjiaju 已提交
484 485 486 487 488 489 490
     */
    @Test
    public void simpleWrite() {
        // 写法1
        String fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
        // 如果这里想使用03 则 传入excelType参数即可
Z
zhuangjiaju 已提交
491
        EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
Z
zhuangjiaju 已提交
492 493 494 495

        // 写法2
        fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去读
Z
zhuangjiaju 已提交
496 497
        ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build();
        WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
Z
zhuangjiaju 已提交
498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525
        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
    /**
     * 指定写入的列
罗成 已提交
526 527 528
     * <p>1. 创建excel对应的实体对象 参照{@link IndexData}
     * <p>2. 使用{@link ExcelProperty}注解指定写入的列
     * <p>3. 直接写即可
Z
zhuangjiaju 已提交
529 530 531 532 533
     */
    @Test
    public void indexWrite() {
        String fileName = TestFileUtil.getPath() + "indexWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
Z
zhuangjiaju 已提交
534
        EasyExcel.write(fileName, IndexData.class).sheet("模板").doWrite(data());
Z
zhuangjiaju 已提交
535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556
    }
```

### <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
    /**
     * 复杂头写入
罗成 已提交
557 558 559
     * <p>1. 创建excel对应的实体对象 参照{@link ComplexHeadData}
     * <p>2. 使用{@link ExcelProperty}注解指定复杂的头
     * <p>3. 直接写即可
Z
zhuangjiaju 已提交
560 561 562 563 564
     */
    @Test
    public void complexHeadWrite() {
        String fileName = TestFileUtil.getPath() + "complexHeadWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
Z
zhuangjiaju 已提交
565
        EasyExcel.write(fileName, ComplexHeadData.class).sheet("模板").doWrite(data());
Z
zhuangjiaju 已提交
566 567 568 569 570 571 572 573 574 575 576 577
    }
```

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

### <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
    /**
     * 日期、数字或者自定义格式转换
罗成 已提交
633 634 635
     * <p>1. 创建excel对应的实体对象 参照{@link ConverterData}
     * <p>2. 使用{@link ExcelProperty}配合使用注解{@link DateTimeFormat}、{@link NumberFormat}或者自定义注解
     * <p>3. 直接写即可
Z
zhuangjiaju 已提交
636 637 638 639 640
     */
    @Test
    public void converterWrite() {
        String fileName = TestFileUtil.getPath() + "converterWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
Z
zhuangjiaju 已提交
641
        EasyExcel.write(fileName, ConverterData.class).sheet("模板").doWrite(data());
Z
zhuangjiaju 已提交
642 643 644
    }
```

Z
zhuangjiaju 已提交
645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667
### <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
    /**
     * 图片导出
罗成 已提交
668 669
     * <p>1. 创建excel对应的实体对象 参照{@link ImageData}
     * <p>2. 直接写即可
Z
zhuangjiaju 已提交
670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696
     */
    @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 已提交
697 698 699 700 701 702 703 704 705 706 707
### <span id="templateWrite" />根据模板写入
##### 模板excel示例
参照:[模板excel示例](#simpleReadExcel)
##### excel示例
![img](img/readme/quickstart/write/templateWrite.png)
##### 对象
参照:[对象](#simpleWriteObject)
##### 代码
```java
    /**
     * 根据模板写入
罗成 已提交
708 709 710 711
     * <p>1. 创建excel对应的实体对象 参照{@link IndexData}
     * <p>2. 使用{@link ExcelProperty}注解指定写入的列
     * <p>3. 使用withTemplate 读取模板
     * <p>4. 直接写即可
Z
zhuangjiaju 已提交
712 713 714 715 716 717
     */
    @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 已提交
718
        EasyExcel.write(fileName, DemoData.class).withTemplate(templateFileName).sheet().doWrite(data());
Z
zhuangjiaju 已提交
719 720 721 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
    }
```

### <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
    /**
     * 列宽、行高
罗成 已提交
748 749 750
     * <p>1. 创建excel对应的实体对象 参照{@link WidthAndHeightData}
     * <p>2. 使用注解{@link ColumnWidth}、{@link HeadRowHeight}、{@link ContentRowHeight}指定宽度或高度
     * <p>3. 直接写即可
Z
zhuangjiaju 已提交
751 752 753 754 755
     */
    @Test
    public void widthAndHeightWrite() {
        String fileName = TestFileUtil.getPath() + "widthAndHeightWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
Z
zhuangjiaju 已提交
756
        EasyExcel.write(fileName, WidthAndHeightData.class).sheet("模板").doWrite(data());
Z
zhuangjiaju 已提交
757 758 759 760 761 762 763 764 765 766 767 768
    }
```

### <span id="styleWrite" />自定义样式
##### excel示例
![img](img/readme/quickstart/write/styleWrite.png)
##### 对象
参照:[对象](#simpleWriteObject)
##### 代码
```java
    /**
     * 自定义样式
罗成 已提交
769 770 771
     * <p>1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>2. 创建一个style策略 并注册
     * <p>3. 直接写即可
Z
zhuangjiaju 已提交
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
     */
    @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 已提交
798
        EasyExcel.write(fileName, DemoData.class).registerWriteHandler(horizontalCellStyleStrategy).sheet("模板")
Z
zhuangjiaju 已提交
799 800 801 802 803 804 805 806 807 808 809 810 811
            .doWrite(data());
    }
```

### <span id="mergeWrite" />合并单元格
##### excel示例
![img](img/readme/quickstart/write/mergeWrite.png)
##### 对象
参照:[对象](#simpleWriteObject)
##### 代码
```java
   /**
     * 合并单元格
罗成 已提交
812 813 814
     * <p>1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>2. 创建一个merge策略 并注册
     * <p>3. 直接写即可
Z
zhuangjiaju 已提交
815 816 817 818 819 820 821
     */
    @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 已提交
822
        EasyExcel.write(fileName, DemoData.class).registerWriteHandler(loopMergeStrategy).sheet("模板")
Z
zhuangjiaju 已提交
823 824 825 826 827 828 829 830 831 832 833 834 835
            .doWrite(data());
    }
```

### <span id="tableWrite" />使用table去写入
##### excel示例
![img](img/readme/quickstart/write/tableWrite.png)
##### 对象
参照:[对象](#simpleWriteObject)
##### 代码
```java
   /**
     * 使用table去写入
罗成 已提交
836 837
     * <p>1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>2. 然后写入table即可
Z
zhuangjiaju 已提交
838 839 840 841 842 843
     */
    @Test
    public void tableWrite() {
        String fileName = TestFileUtil.getPath() + "tableWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里直接写多个table的案例了,如果只有一个 也可以直一行代码搞定,参照其他案例
        // 这里 需要指定写用哪个class去读
Z
zhuangjiaju 已提交
844
        ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build();
Z
zhuangjiaju 已提交
845
        // 把sheet设置为不需要头 不然会输出sheet的头 这样看起来第一个table 就有2个头了
Z
zhuangjiaju 已提交
846
        WriteSheet writeSheet = EasyExcel.writerSheet("模板").needHead(Boolean.FALSE).build();
Z
zhuangjiaju 已提交
847
        // 这里必须指定需要头,table 会继承sheet的配置,sheet配置了不需要,table 默认也是不需要
Z
zhuangjiaju 已提交
848 849
        WriteTable writeTable0 = EasyExcel.writerTable(0).needHead(Boolean.TRUE).build();
        WriteTable writeTable1 = EasyExcel.writerTable(1).needHead(Boolean.TRUE).build();
Z
zhuangjiaju 已提交
850 851 852 853 854 855 856 857 858
        // 第一次写入会创建头
        excelWriter.write(data(), writeSheet, writeTable0);
        // 第二次写如也会创建头,然后在第一次的后面写入数据
        excelWriter.write(data(), writeSheet, writeTable1);
        /// 千万别忘记finish 会帮忙关闭流
        excelWriter.finish();
    }
```

Z
zhuangjiaju 已提交
859 860 861 862 863 864 865 866 867 868 869 870
### <span id="tableWrite" />动态头,实时生成头写入
##### excel示例
![img](img/readme/quickstart/write/dynamicHeadWrite.png)
##### 对象
参照:[对象](#simpleWriteObject)
##### 代码
```java
    /**
     * 动态头,实时生成头写入
     * <p>
     * 思路是这样子的,先创建List<String>头格式的sheet仅仅写入头,然后通过table 不写入头的方式 去写入数据
     *
罗成 已提交
871 872
     * <p>1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>2. 然后写入table即可
Z
zhuangjiaju 已提交
873 874 875 876 877
     */
    @Test
    public void dynamicHeadWrite() {
        String fileName = TestFileUtil.getPath() + "dynamicHeadWrite" + System.currentTimeMillis() + ".xlsx";
        // write的时候 不传入 class 在table的时候传入
Z
zhuangjiaju 已提交
878
        EasyExcel.write(fileName)
Z
zhuangjiaju 已提交
879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899
            // 这里放入动态头
            .head(head()).sheet("模板")
            // table的时候 传入class 并且设置needHead =false
            .table().head(DemoData.class).needHead(Boolean.FALSE).doWrite(data());
    }

    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 已提交
900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916
### <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
庄家钜 已提交
917
   /**
Z
zhuangjiaju 已提交
918 919 920 921 922 923 924
     * 自动列宽(不太精确)
     * <p>
     * 这个目前不是很好用,比如有数字就会导致换行。而且长度也不是刚好和实际长度一致。 所以需要精确到刚好列宽的慎用。 当然也可以自己参照
     * {@link LongestMatchColumnWidthStyleStrategy}重新实现.
     * <p>
     * poi 自带{@link SXSSFSheet#autoSizeColumn(int)} 对中文支持也不太好。目前没找到很好的算法。 有的话可以推荐下。
     *
庄家钜's avatar
庄家钜 已提交
925 926 927 928 929 930
     * <p>
     * 1. 创建excel对应的实体对象 参照{@link LongestMatchColumnWidthData}
     * <p>
     * 2. 注册策略{@link LongestMatchColumnWidthStyleStrategy}
     * <p>
     * 3. 直接写即可
Z
zhuangjiaju 已提交
931 932 933 934 935 936
     */
    @Test
    public void longestMatchColumnWidthWrite() {
        String fileName =
            TestFileUtil.getPath() + "longestMatchColumnWidthWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
庄家钜's avatar
庄家钜 已提交
937 938
        EasyExcel.write(fileName, LongestMatchColumnWidthData.class)
            .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).sheet("模板").doWrite(dataLong());
Z
zhuangjiaju 已提交
939 940 941 942 943 944 945 946 947 948 949 950 951 952 953
    }

    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
庄家钜 已提交
954 955 956 957 958
### <span id="customHandlerWrite" />自定义拦截器(上面几点都不符合但是要对单元格进行操作的参照这个)
##### excel示例
![img](img/readme/quickstart/write/customHandlerWrite.png)
##### 对象
参照:[对象](#simpleWriteObject)
庄家钜's avatar
庄家钜 已提交
959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019
##### 定义拦截器
````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
庄家钜 已提交
1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041
##### 代码
```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 已提交
1042 1043 1044 1045 1046 1047 1048
### <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
庄家钜 已提交
1049
    /**
Z
zhuangjiaju 已提交
1050
     * 文件下载
庄家钜's avatar
庄家钜 已提交
1051 1052 1053 1054 1055 1056
     * <p>
     * 1. 创建excel对应的实体对象 参照{@link DownloadData}
     * <p>
     * 2. 设置返回的 参数
     * <p>
     * 3. 直接写,这里注意,finish的时候会自动关闭OutputStream,当然你外面再关闭流问题不大
Z
zhuangjiaju 已提交
1057 1058 1059
     */
    @GetMapping("download")
    public void download(HttpServletResponse response) throws IOException {
庄家钜's avatar
庄家钜 已提交
1060
        // 这里注意 有同学反应下载的文件名不对。这个时候 请别使用swagger 他会影像
Z
zhuangjiaju 已提交
1061 1062 1063
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
        response.setHeader("Content-disposition", "attachment;filename=demo.xlsx");
Z
zhuangjiaju 已提交
1064
        EasyExcel.write(response.getOutputStream(), DownloadData.class).sheet("模板").doWrite(data());
Z
zhuangjiaju 已提交
1065 1066
    }
```
Z
zhuangjiaju 已提交
1067
## 测试数据分析
J
update  
jipengfei.jpf 已提交
1068 1069 1070 1071 1072 1073
![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个人再用估计一台机器就挂了。