quickstart.md 44.9 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 18
#### 详细参数介绍
有些参数不知道怎么用,或者有些功能不知道用什么参数,参照:[详细参数介绍](/docs/API.md)
庄家钜's avatar
庄家钜 已提交
19
#### 开源项目不容易,如果觉得本项目对您的工作还是有帮助的话,请在右上角帮忙点个★Star。
Z
zhuangjiaju 已提交
20 21 22
### 读
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 已提交
23 24 25 26 27
* [指定列的下标或者列名](#indexOrNameRead)
* [读多个sheet](#repeatedRead)
* [日期、数字或者自定义格式转换](#converterRead)
* [多行头](#complexHeaderRead)
* [同步的返回](#synchronousRead)
庄家钜's avatar
庄家钜 已提交
28
* [读取表头数据](#headerRead)
庄家钜's avatar
庄家钜 已提交
29
* [数据转换等异常处理](#exceptionRead)
Z
zhuangjiaju 已提交
30
* [web中的读](#webRead)
庄家钜's avatar
庄家钜 已提交
31

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

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

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

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

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

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

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

        // 方法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 已提交
203
    }
J
update  
jipengfei.jpf 已提交
204 205
```

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

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

Z
zhuangjiaju 已提交
245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260
    /**
     * 这里读的时候会调用
     *
     * @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 已提交
261

Z
zhuangjiaju 已提交
262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277
    /**
     * 这里是写的时候会调用 不用管
     *
     * @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 已提交
278 279

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

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

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

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

庄家钜's avatar
庄家钜 已提交
361
### <span id="headerRead" />读取表头数据
庄家钜's avatar
庄家钜 已提交
362 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
##### 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
庄家钜 已提交
401
### <span id="exceptionRead" />数据转换等异常处理
庄家钜's avatar
庄家钜 已提交
402 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
##### 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 已提交
443 444 445 446 447 448 449 450 451 452 453 454 455
### <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
    /**
     * 文件上传
罗成 已提交
456 457 458
     * <p>1. 创建excel对应的实体对象 参照{@link UploadData}
     * <p>2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link UploadDataListener}
     * <p>3. 直接读即可
Z
zhuangjiaju 已提交
459 460 461 462
     */
    @PostMapping("upload")
    @ResponseBody
    public String upload(MultipartFile file) throws IOException {
Z
zhuangjiaju 已提交
463
        EasyExcel.read(file.getInputStream(), UploadData.class, new UploadDataListener()).sheet().doRead();
Z
zhuangjiaju 已提交
464 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
        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
    /**
     * 最简单的写
罗成 已提交
502 503
     * <p>1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>2. 直接写即可
Z
zhuangjiaju 已提交
504 505 506 507 508 509 510
     */
    @Test
    public void simpleWrite() {
        // 写法1
        String fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
        // 如果这里想使用03 则 传入excelType参数即可
Z
zhuangjiaju 已提交
511
        EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
Z
zhuangjiaju 已提交
512 513 514 515

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

### <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
    /**
     * 复杂头写入
罗成 已提交
577 578 579
     * <p>1. 创建excel对应的实体对象 参照{@link ComplexHeadData}
     * <p>2. 使用{@link ExcelProperty}注解指定复杂的头
     * <p>3. 直接写即可
Z
zhuangjiaju 已提交
580 581 582 583 584
     */
    @Test
    public void complexHeadWrite() {
        String fileName = TestFileUtil.getPath() + "complexHeadWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去读,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
Z
zhuangjiaju 已提交
585
        EasyExcel.write(fileName, ComplexHeadData.class).sheet("模板").doWrite(data());
Z
zhuangjiaju 已提交
586 587 588 589 590 591 592 593 594 595
    }
```

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

        // 方法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 已提交
651 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
    }
```

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

Z
zhuangjiaju 已提交
696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718
### <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
    /**
     * 图片导出
罗成 已提交
719 720
     * <p>1. 创建excel对应的实体对象 参照{@link ImageData}
     * <p>2. 直接写即可
Z
zhuangjiaju 已提交
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
     */
    @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 已提交
748 749 750 751 752 753 754 755 756 757 758
### <span id="templateWrite" />根据模板写入
##### 模板excel示例
参照:[模板excel示例](#simpleReadExcel)
##### excel示例
![img](img/readme/quickstart/write/templateWrite.png)
##### 对象
参照:[对象](#simpleWriteObject)
##### 代码
```java
    /**
     * 根据模板写入
罗成 已提交
759 760 761 762
     * <p>1. 创建excel对应的实体对象 参照{@link IndexData}
     * <p>2. 使用{@link ExcelProperty}注解指定写入的列
     * <p>3. 使用withTemplate 读取模板
     * <p>4. 直接写即可
Z
zhuangjiaju 已提交
763 764 765 766 767 768
     */
    @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 已提交
769
        EasyExcel.write(fileName, DemoData.class).withTemplate(templateFileName).sheet().doWrite(data());
Z
zhuangjiaju 已提交
770 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
    }
```

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

### <span id="styleWrite" />自定义样式
##### excel示例
![img](img/readme/quickstart/write/styleWrite.png)
##### 对象
参照:[对象](#simpleWriteObject)
##### 代码
```java
    /**
     * 自定义样式
罗成 已提交
820 821 822
     * <p>1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>2. 创建一个style策略 并注册
     * <p>3. 直接写即可
Z
zhuangjiaju 已提交
823 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
     */
    @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 已提交
849
        EasyExcel.write(fileName, DemoData.class).registerWriteHandler(horizontalCellStyleStrategy).sheet("模板")
Z
zhuangjiaju 已提交
850 851 852 853 854 855 856 857 858 859 860 861 862
            .doWrite(data());
    }
```

### <span id="mergeWrite" />合并单元格
##### excel示例
![img](img/readme/quickstart/write/mergeWrite.png)
##### 对象
参照:[对象](#simpleWriteObject)
##### 代码
```java
   /**
     * 合并单元格
罗成 已提交
863 864 865
     * <p>1. 创建excel对应的实体对象 参照{@link DemoData}
     * <p>2. 创建一个merge策略 并注册
     * <p>3. 直接写即可
Z
zhuangjiaju 已提交
866 867 868 869 870 871 872
     */
    @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 已提交
873
        EasyExcel.write(fileName, DemoData.class).registerWriteHandler(loopMergeStrategy).sheet("模板")
Z
zhuangjiaju 已提交
874 875 876 877 878 879 880 881 882 883 884 885 886
            .doWrite(data());
    }
```

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

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

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

    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
庄家钜 已提交
1006 1007 1008 1009 1010
### <span id="customHandlerWrite" />自定义拦截器(上面几点都不符合但是要对单元格进行操作的参照这个)
##### excel示例
![img](img/readme/quickstart/write/customHandlerWrite.png)
##### 对象
参照:[对象](#simpleWriteObject)
庄家钜's avatar
庄家钜 已提交
1011 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
##### 定义拦截器
````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
庄家钜 已提交
1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089 1090 1091 1092 1093
##### 代码
```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 已提交
1094 1095 1096 1097 1098 1099 1100
### <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
庄家钜 已提交
1101
    /**
Z
zhuangjiaju 已提交
1102
     * 文件下载
庄家钜's avatar
庄家钜 已提交
1103 1104 1105 1106 1107 1108
     * <p>
     * 1. 创建excel对应的实体对象 参照{@link DownloadData}
     * <p>
     * 2. 设置返回的 参数
     * <p>
     * 3. 直接写,这里注意,finish的时候会自动关闭OutputStream,当然你外面再关闭流问题不大
Z
zhuangjiaju 已提交
1109 1110 1111
     */
    @GetMapping("download")
    public void download(HttpServletResponse response) throws IOException {
1112
        // 这里注意 有同学反应下载的文件名不对。这个时候 请别使用swagger 他会有影响
Z
zhuangjiaju 已提交
1113 1114
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
1115 1116 1117
        // 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系
        String fileName = URLEncoder.encode("测试", "UTF-8");
        response.setHeader("Content-disposition", "attachment;filename=" + fileName + ".xlsx");
Z
zhuangjiaju 已提交
1118
        EasyExcel.write(response.getOutputStream(), DownloadData.class).sheet("模板").doWrite(data());
Z
zhuangjiaju 已提交
1119 1120
    }
```
Z
zhuangjiaju 已提交
1121
## 测试数据分析
J
update  
jipengfei.jpf 已提交
1122 1123 1124 1125 1126 1127
![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个人再用估计一台机器就挂了。