technology-note/java/其他/1.java导出EXCEL文件.md
2019-04-08 10:07:19 +08:00

215 lines
7.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
id: "2018-09-22-15-57"
date: "2018/09/22 15:57"
title: "java导出EXCEL文件"
tags: ["reflex", "java","excel","SXSSFWorksheet"]
categories:
- "java"
- "java工具集"
---
**本篇原创发布于:**[FleyX 的个人博客](tapme.top/blog/detail/2018-09-22-15-57)
**本篇所用到代码**[github](https://github.com/FleyX/demo-project/blob/master/%E6%9D%82%E9%A1%B9/excel%E5%AF%BC%E5%87%BA.java)
**更新说明:**之前用的`HSSFWorkbook`有爆内存的风险,当数据量有几百万时该对象会占用大量内存。更换为`SXSSFWorkbook`可解决内存占用。
## 一、背景
  最近在 java 上做了一个 EXCEL 的导出功能,写了一个通用类,在这里分享分享,该类支持多 sheet且无需手动进行复杂的类型转换只需提供三个参数即可
- `fileName`
excel 文件名
- `HasMap<String,List<?>> data`
具体的数据,每个 List 代表一张表的数据,?表示可为任意的自定义对象
- `LinkedHashMap<String,String[][]> headers`
`Stirng`代表 sheet 名。每个`String[][]`代表一个 sheet 的定义,举个例子如下:
```java
String[][] header = {
{"field1","参数1"}
{"field2","参数2"}
{"field3","参数3"}
}
```
其中的 field1field2field3 为对象中的属性名,参数 1参数 2参数 3 为列名,实际上这个指定了列的名称和这个列用到数据对象的哪个属性。
<!-- more -->
## 二、怎么用
&emsp;&emsp;以一个例子来说明怎么用,假设有两个类 A 和 B 定义如下:
```java
public class A{
private String name;
private String address;
}
public class B{
private int id;
private double sum;
private String cat;
}
```
现在我们通过查询数据库获得了 A 和 B 的两个列表:
```java
List<A> dataA = .....;
List<B> dataB = .....;
```
我们将这两个导出到 excel 中,首先需要定义 sheet
```java
String[][] sheetA = {
{"name","姓名"}
,{"address","住址"}
}
String[][] sheetB = {
{"id","ID"}
,{"sum","余额"}
,{"cat","猫的名字"}
}
```
然后将数据汇总构造一个 ExcelUtil
```java
String fileName = "测试Excel";
HashMap<String,List<?>> data = new HashMap<>();
//ASheet为表名后面headers里的key要跟这里一致
data.put("ASheet",dataA);
data.put("BSheet",dataB);
LinkedHashMap<String,String[][]> headers = new LinkedHashMap<>();
headers.put("ASheet",sheetA);
headers.put("BSheet",sheetB);
ExcelUtil excelUtil = new ExcelUtil(fileName,data,headers);
//获取表格对象
SXSSFWorkbook workbook = excelUtil.createExcel();
//这里内置了一个写到response的方法判断浏览器类型设置合适的参数如果想写到文件也是类似的
workbook.writeToResponse(workbook,request,response);
```
当然通常数据是通过数据库查询的,这里为了演示方便没有从数据库查找。
## 三、实现原理
&emsp;&emsp;这里简单说明下实现过程,从调用`createExcel()`这里开始
#### 1、遍历 headers 创建 sheet
```java
public SXSSFWorkbook createExcel() throws Exception {
try {
//只在内存中保留五百条记录,五百条之前的会写到磁盘上,后面无法再操作
SXSSFWorkbook workbook = new SXSSFWorkbook(500);
//遍历headers创建表格
for (String key : headers.keySet()) {
this.createSheet(workbook, key, headers.get(key), this.data.get(key));
}
return workbook;
} catch (Exception e) {
log.error("创建表格失败:{}", e.getMessage());
throw e;
}
}
```
将 workbooksheet 名,表头数据,行数据传入 crateSheet 方法中创建 sheet。
#### 2、创建表头
&emsp;&emsp;表头也就是一个表格的第一行,通常用来对列进行说明
```java
private void createSheet(SXSSFWorkbook workbook, String sheetName, String[][] header, List<?> data) throws Exception {
Sheet sheet = workbook.createSheet(sheetName);
// 单元行,单元格
Row row;
Cell cell;
//列数
int cellNum = header.length;
//设置表头
row = sheet.createRow(0);
for (int i = 0; i < cellNum; i++) {
cell = row.createCell(i);
String str = header[i][1];
cell.setCellValue(str);
//设置列宽为表头的宽度+4
sheet.setColumnWidth(i, (str.getBytes("utf-8").length + 6) * 256);
}
int rowNum = data.size();
if (rowNum == 0) {
return;
}
//获取Object 属性名与field属性的映射后面通过反射获取值来设置到cell
Field[] fields = data.get(0).getClass().getDeclaredFields();
Map<String, Field> fieldMap = new HashMap<>(fields.length);
for (Field field : fields) {
field.setAccessible(true);
fieldMap.put(field.getName(), field);
}
Object object;
for (int i = 0; i < rowNum; i++) {
row = sheet.createRow(i + 1);
object = data.get(i);
for (int j = 0; j < cellNum; j++) {
cell = row.createCell(j);
this.setCell(cell, object, fieldMap, header[j][0]);
}
}
}
```
#### 3、插入行数据
&emsp;&emsp;这里是最重要的部分,首先通过数据的类对象获取它的反射属性 Field 类,然后将属性名和 Field 做一个 hash 映射,避免循环查找,提高插入速度,接着通过一个 switch 语句,根据属性类别设值,主要代码如下:
```java
private void setCell(Cell cell, Object obj, Map<String, Field> fieldMap, String fieldName) throws Exception {
Field field = fieldMap.get(fieldName);
if(field == null){
throw new Exception("找不到 "+fieldName+" 数据项");
}
Object value = field.get(obj);
if (value == null) {
cell.setCellValue("");
} else {
switch (field.getGenericType().getTypeName()) {
case "java.lang.String":
cell.setCellValue((String) value);
break;
case "java.lang.Integer":
case "int":
cell.setCellValue((int) value);
break;
case "java.lang.Double":
case "double":
cell.setCellValue((double) value);
break;
case "java.util.Date":
cell.setCellValue(this.dateFormat.format((Date) value));
break;
default:
cell.setCellValue(obj.toString());
}
}
}
```
完整代码可以到 github 上查看下载,这里就不列出来了。
github 地址:
**本篇所用到代码**[github](https://github.com/FleyX/demo-project/blob/master/%E6%9D%82%E9%A1%B9/excel%E5%AF%BC%E5%87%BA.java)
**本篇原创发布于:**[FleyX 的个人博客](tapme.top/blog/detail/2018-09-22-15-57)