在企业级应用开发中,Excel报表是一种最常见的报表需求。Excel报表开发一般分为两种形式:为了方便操作,基于Excel的报表批量上传数据;通过java代码生成Excel报表。
目前世面上的Excel分为两个大的版本Excel2003和Excel2007及以上两个版本,两者之间的区别如下:
Excel2003是一个特有的二进制格式,其核心结构是复合文档类型的结构,存储数据量较小;Excel2007 的核心结构是 XML 类型的结构,采用的是基于 XML 的压缩方式,使其占用的空间更小,操作效率更高
Java中常见的用来操作Excl的方式一般有2种:JXL和POI。
JXL只能对Excel进行操作,属于比较老的框架,它只支持到Excel 95-2000的版本。现在已经停止更新和维护。
Apache POI是Apache软件基金会的开源项目,由Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java语言操作Microsoft Office的功能。可对微软的Word,Excel,Ppt进行操作,包括office2003和2007,Excl2003和2007。poi现在一直有更新。所以现在主流使用POI。
<dependencies>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>4.0.1</version>
</dependency>
</dependencies>
@Test
public void createExcel throws Exception {
//1.创建工作簿 HSSFWorkbook -- 2003
Workbook wb = new XSSFWorkbook(); //2007版本
//2.创建表单sheet
Sheet sheet = wb.createSheet("test");
//3.文件流
FileOutputStream pis = new FileOutputStream("E:\\excel\\poi\\test.xlsx");
//4.写入文件
wb.write(pis);
pis.close();
}
@Test
public void createCell throws Exception {
//创建工作簿 HSSFWorkbook -- 2003
Workbook wb = new XSSFWorkbook(); //2007版本
//创建表单sheet
Sheet sheet = wb.createSheet("test");
//创建行对象 参数:索引(从0开始)
Row row = sheet.createRow(2);
//创建单元格对象 参数:索引(从0开始)
Cell cell = row.createCell(2);
//向单元格中写入内容
cell.setCellValue("君莫笑");
//文件流
FileOutputStream pis = new FileOutputStream("E:\\excel\\poi\\test1.xlsx");
//写入文件
wb.write(pis);
pis.close();
}
@Test
public void setStyle() throws Exception {
//创建工作簿 HSSFWorkbook -- 2003
Workbook wb = new XSSFWorkbook(); //2007版本
//创建表单sheet
Sheet sheet = wb.createSheet("test");
//创建行对象 参数:索引(从0开始)
Row row = sheet.createRow(2);
//创建单元格对象 参数:索引(从0开始)
Cell cell = row.createCell(2);
//向单元格中写入内容
cell.setCellValue("君莫笑");
//样式处理
//创建样式对象
CellStyle style = wb.createCellStyle();
style.setBorderTop(BorderStyle.THIN);//上边框
style.setBorderBottom(BorderStyle.THIN);//下边框
style.setBorderLeft(BorderStyle.THIN);//左边框
style.setBorderRight(BorderStyle.THIN);//右边框
//创建字体对象
Font font = wb.createFont();
font.setFontName("华文行楷"); //字体
font.setFontHeightInPoints((short)28);//字号
style.setFont(font);
//行高和列宽
row.setHeightInPoints(50);//行高
//列宽的宽度 字符宽度
sheet.setColumnWidth(2,31 * 256);//列宽
//剧中显示
style.setAlignment(HorizontalAlignment.CENTER);//水平居中
style.setVerticalAlignment(VerticalAlignment.CENTER);//垂直居中
//向单元格设置样式
cell.setCellStyle(style);
//文件流
FileOutputStream pis = new FileOutputStream("E:\\excel\\poi\\test2.xlsx");
//写入文件
wb.write(pis);
pis.close();
}
blic void createPicture() throws Exception {
//创建工作簿 HSSFWorkbook -- 2003
Workbook wb = new XSSFWorkbook(); //2007版本
//创建表单sheet
Sheet sheet = wb.createSheet("test");
//读取图片流
FileInputStream stream = new FileInputStream("E:\\excel\\poi\\test.jpg");
//转化二进制数组
byte[] bytes = IOUtils.toByteArray(stream);
stream.read(bytes);
//向POI内存中添加一张图片,返回图片在图片集合中的索引
int index = wb.addPicture(bytes, Workbook.PICTURE_TYPE_JPEG);//参数一:图片的二进制数据,参数二:图片类型
//绘制图片工具类
CreationHelper helper = wb.getCreationHelper();
//创建一个绘图对象
Drawing<?> patriarch = sheet.createDrawingPatriarch();
//创建锚点,设置图片坐标
ClientAnchor anchor = helper.createClientAnchor();
anchor.setRow1(0);
anchor.setCol1(0);
//绘制图片
Picture picture = patriarch.createPicture(anchor, index);//图片位置,图片的索引
picture.resize();//自适应渲染图片
//文件流
FileOutputStream pis = new FileOutputStream("E:\\excel\\poi\\test3.xlsx");
//写入文件
wb.write(pis);
pis.close();
}
/**
* 读取excel并解析
* sheet.getLastRowNum() : 最后一行的索引
* row.getLastCellNum() : 最后一个单元格的号码
*/
@Test
public void parseExcel() throws Exception {
//1.根据Excel文件创建工作簿
Workbook wb = new XSSFWorkbook("E:\\excel\\poi\\test.xlsx");
//2.获取Sheet
Sheet sheet = wb.getSheetAt(0);//参数:索引
//3.获取Sheet中的每一行,和每一个单元格
for (int rowNum = 0; rowNum<= sheet.getLastRowNum() ;rowNum ++) {
Row row = sheet.getRow(rowNum);//根据索引获取每一个行
StringBuilder sb = new StringBuilder();
for(int cellNum=2;cellNum< row.getLastCellNum(); cellNum ++) {
//根据索引获取每一个单元格
Cell cell = row.getCell(cellNum);
//获取每一个单元格的内容
Object value = getCellValue(cell);
sb.append(value).append("-");
}
System.out.println(sb.toString());
}
}
public static Object getCellValue(Cell cell) {
//1.获取到单元格的属性类型
CellType cellType = cell.getCellType();
//2.根据单元格数据类型获取数据
Object value = null;
switch (cellType) {
case STRING:
value = cell.getStringCellValue();
break;
case BOOLEAN:
value = cell.getBooleanCellValue();
break;
case NUMERIC:
if(DateUtil.isCellDateFormatted(cell)) {
//日期格式
value = cell.getDateCellValue();
}else{
//数字
value = cell.getNumericCellValue();
}
break;
case FORMULA: //公式
value = cell.getCellFormula();
break;
default:
break;
}
return value;
}
/**
* 导入Excel,添加用户
* 文件上传:springboot
*/
@RequestMapping(value="/user/import",method = RequestMethod.POST)
public Result importUser(@RequestParam(name="file") MultipartFile file) throws Exception {
//1.解析Excel
//1.1.根据Excel文件创建工作簿
Workbook wb = new XSSFWorkbook(file.getInputStream());
//1.2.获取Sheet
Sheet sheet = wb.getSheetAt(0);//参数:索引
//1.3.获取Sheet中的每一行,和每一个单元格
//2.获取用户数据列表
List<User> list = new ArrayList<>();
System.out.println(sheet.getLastRowNum());
for (int rowNum = 1; rowNum<= sheet.getLastRowNum() ;rowNum ++) {
Row row = sheet.getRow(rowNum);//根据索引获取每一个行
Object [] values = new Object[row.getLastCellNum()];
for(int cellNum=1;cellNum< row.getLastCellNum(); cellNum ++) {
Cell cell = row.getCell(cellNum);
Object value = getCellValue(cell);
values[cellNum] = value;
}
//在User的构造函数中,根据表格值顺序设置User对应的属性值
User user = new User(values);
list.add(user);
}
//3.调用服务层批量保存用户
userService.saveAll(list);
return new Result(ResultCode.SUCCESS);
}
public static Object getCellValue(Cell cell) {
//1.获取到单元格的属性类型
CellType cellType = cell.getCellType();
//2.根据单元格数据类型获取数据
Object value = null;
switch (cellType) {
case STRING:
value = cell.getStringCellValue();
break;
case BOOLEAN:
value = cell.getBooleanCellValue();
break;
case NUMERIC:
if(DateUtil.isCellDateFormatted(cell)) {
//日期格式
value = cell.getDateCellValue();
}else{
//数字
value = cell.getNumericCellValue();
}
break;
case FORMULA: //公式
value = cell.getCellFormula();
break;
default:
break;
}
return value;
}
@RequestMapping(value = "/export/{month}", method = RequestMethod.GET)
public void export(@PathVariable(name = "month") String month) throws Exception {
//1.构造数据,某个月的用户数据
List<User> list =userService.findByMonth(month);
//2.创建工作簿
XSSFWorkbook workbook = new XSSFWorkbook();
//3.构造sheet
String[] titles = {"编号", "姓名", "手机","最高学历", "国家地区","生日", "时间"};
Sheet sheet = workbook.createSheet();
Row row = sheet.createRow(0);
AtomicInteger headersAi = new AtomicInteger();
for (String title : titles) {
Cell cell = row.createCell(headersAi.getAndIncrement());
cell.setCellValue(title);
}
AtomicInteger datasAi = new AtomicInteger(1);
Cell cell = null;
for (User user : list) {
Row dataRow = sheet.createRow(datasAi.getAndIncrement());
//编号
cell = dataRow.createCell(0);
cell.setCellValue(user.getUserId());
//姓名
cell = dataRow.createCell(1);
cell.setCellValue(user.getUsername());
//手机
cell = dataRow.createCell(2);
cell.setCellValue(user.getMobile());
//最高学历
cell = dataRow.createCell(3);
cell.setCellValue(user.getTheHighestDegreeOfEducation());
//国家地区
cell = dataRow.createCell(4);
cell.setCellValue(user.getNationalArea());
//生日
cell = dataRow.createCell(7);
cell.setCellValue(user.getBirthday());
//时间
cell = dataRow.createCell(12);
cell.setCellValue(user.getCreateTime());
}
String fileName = URLEncoder.encode(month+"人员信息.xlsx", "UTF-8");
response.setContentType("application/octet-stream");
response.setHeader("content-disposition", "attachment;filename=" + new String(fileName.getBytes("ISO8859-1")));
response.setHeader("filename", fileName);
workbook.write(response.getOutputStream());
}
自定义生成Excel报表文件还是有很多不尽如意的地方,特别是针对复杂报表头,单元格样式,字体等操作。手写这些代码不仅费时费力,有时候效果还不太理想。那怎么样才能更方便的对报表样式,报表头进行处理呢?答案是使用已经准备好的Excel模板,只需要关注模板中的数据即可。
@RequestMapping(value = "/export/{month}", method = RequestMethod.GET)
public void export(@PathVariable(name = "month") String month) throws Exception {
//1.构造数据
List<User> list =userService.findByMonth(month);
//2.加载模板流数据
Resource resource = new ClassPathResource("excel-template/demo.xlsx");
FileInputStream fis = new FileInputStream(resource.getFile());
//3.根据文件流,加载指定的工作簿
XSSFWorkbook wb = new XSSFWorkbook(fis);
//4.读取工作表
Sheet sheet = wb.getSheetAt(0);
//5.抽取公共的样式
Row styleRow = sheet.getRow(2);
CellStyle [] styles = new CellStyle[styleRow.getLastCellNum()];
for(int i=0;i<styleRow.getLastCellNum();i++) {
styles[i] = styleRow.getCell(i).getCellStyle();
}
//6.构造每行和单元格数据
AtomicInteger datasAi = new AtomicInteger(2);
Cell cell = null;
for (EmployeeReportResult report : list) {
Row dataRow = sheet.createRow(datasAi.getAndIncrement());
//编号
cell = dataRow.createCell(0);
cell.setCellValue(report.getUserId());
cell.setCellStyle(styles[0]);
//姓名
cell = dataRow.createCell(1);
cell.setCellValue(report.getUsername());
cell.setCellStyle(styles[1]);
//手机
cell = dataRow.createCell(2);
cell.setCellValue(report.getMobile());
cell.setCellStyle(styles[2]);
//最高学历
cell = dataRow.createCell(3);
cell.setCellValue(report.getTheHighestDegreeOfEducation());
cell.setCellStyle(styles[3]);
//国家地区
cell = dataRow.createCell(4);
cell.setCellValue(report.getNationalArea());
cell.setCellStyle(styles[4]);
//生日
cell = dataRow.createCell(7);
cell.setCellValue(report.getBirthday());
cell.setCellStyle(styles[7]);
//离职时间
cell = dataRow.createCell(12);
cell.setCellStyle(styles[12]);
cell.setCellValue(report.getCreateTime());
}
String fileName = URLEncoder.encode(month+"人员信息.xlsx", "UTF-8");
response.setContentType("application/octet-stream");
response.setHeader("content-disposition", "attachment;filename=" + new String(fileName.getBytes("ISO8859-1")));
response.setHeader("filename", fileName);
wb.write(response.getOutputStream());
}
(1)自定义注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ExcelAttribute {
/** 对应的列名称 */
String name() default "";
/** 列序号 */
int sort();
/** 字段类型对应的格式 */
String format() default "";
}
(2)导出工具类
@Getter
@Setter
public class ExcelExportUtil<T> {
private int rowIndex;
private int styleIndex;
private String templatePath;
private Class clazz;
private Field fields[];
public ExcelExportUtil(Class clazz,int rowIndex,int styleIndex) {
this.clazz = clazz;
this.rowIndex = rowIndex;
this.styleIndex = styleIndex;
fields = clazz.getDeclaredFields();
}
/**
* 基于注解导出
*/
public void export(HttpServletResponse response,InputStream is, List<T> objs,String
fileName) throws Exception {
XSSFWorkbook workbook = new XSSFWorkbook(is);
Sheet sheet = workbook.getSheetAt(0);
CellStyle[] styles = getTemplateStyles(sheet.getRow(styleIndex));
AtomicInteger datasAi = new AtomicInteger(rowIndex);
for (T t : objs) {
Row row = sheet.createRow(datasAi.getAndIncrement());
for(int i=0;i<styles.length;i++) {
Cell cell = row.createCell(i);
cell.setCellStyle(styles[i]);
for (Field field : fields) {
if(field.isAnnotationPresent(ExcelAttribute.class)){
field.setAccessible(true);
ExcelAttribute ea = field.getAnnotation(ExcelAttribute.class);
if(i == ea.sort()) {
cell.setCellValue(field.get(t).toString());
}
}
}
}
}
fileName = URLEncoder.encode(fileName, "UTF-8");
response.setContentType("application/octet-stream");
response.setHeader("content-disposition", "attachment;filename=" + new String(fileName.getBytes("ISO8859-1")));
response.setHeader("filename", fileName);
workbook.write(response.getOutputStream());
}
public CellStyle[] getTemplateStyles(Row row) {
CellStyle [] styles = new CellStyle[row.getLastCellNum()];
for(int i=0;i<row.getLastCellNum();i++) {
styles[i] = row.getCell(i).getCellStyle();
}
return styles;
}
}
(3)导入工具类
public class ExcelImportUtil<T> {
private Class clazz;
private Field fields[];
public ExcelImportUtil(Class clazz) {
this.clazz = clazz;
fields = clazz.getDeclaredFields();
}
/**
* 基于注解读取excel
*/
public List<T> readExcel(InputStream is, int rowIndex,int cellIndex) {
List<T> list = new ArrayList<T>();
T entity = null;
try {
XSSFWorkbook workbook = new XSSFWorkbook(is);
Sheet sheet = workbook.getSheetAt(0);
// 不准确
int rowLength = sheet.getLastRowNum();
System.out.println(sheet.getLastRowNum());
for (int rowNum = rowIndex; rowNum <= sheet.getLastRowNum(); rowNum++) {
Row row = sheet.getRow(rowNum);
entity = (T) clazz.newInstance();
System.out.println(row.getLastCellNum());
for (int j = cellIndex; j < row.getLastCellNum(); j++) {
Cell cell = row.getCell(j);
for (Field field : fields) {
if(field.isAnnotationPresent(ExcelAttribute.class)){
field.setAccessible(true);
ExcelAttribute ea =
field.getAnnotation(ExcelAttribute.class);
if(j == ea.sort()) {
field.set(entity, covertAttrType(field, cell));
}
}
}
}
list.add(entity);
}
} catch (Exception e) {
e.printStackTrace();
}
return list;
}
/**
* 类型转换 将cell 单元格格式转为 字段类型
* */
private Object covertAttrType(Field field, Cell cell) throws Exception {
String fieldType = field.getType().getSimpleName();
if ("String".equals(fieldType)) {
return getValue(cell);
}else if ("Date".equals(fieldType)) {
return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(getValue(cell)) ;
}else if ("int".equals(fieldType) || "Integer".equals(fieldType)) {
return Integer.parseInt(getValue(cell));
}else if ("double".equals(fieldType) || "Double".equals(fieldType)) {
return Double.parseDouble(getValue(cell));
}else {
return null;
}
}
/**
* 格式转为String
* @param cell
* @return
*/
public String getValue(Cell cell) {
if (cell == null) {
return "";
}
switch (cell.getCellType()) {
case STRING:
return cell.getRichStringCellValue().getString().trim();
case NUMERIC:
if (DateUtil.isCellDateFormatted(cell)) {
Date dt = DateUtil.getJavaDate(cell.getNumericCellValue());
return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(dt);
} else {
// 防止数值变成科学计数法
String strCell = "";
Double num = cell.getNumericCellValue();
BigDecimal bd = new BigDecimal(num.toString());
if (bd != null) {
strCell = bd.toPlainString();
}
// 去除 浮点型 自动加的 .0
if (strCell.endsWith(".0")) {
strCell = strCell.substring(0, strCell.indexOf("."));
}
return strCell;
}
case BOOLEAN:
return String.valueOf(cell.getBooleanCellValue());
default:
return "";
}
}
}
使用Apache POI完成百万数据量的Excel报表导出。
基于XSSFWork导出Excel报表,是通过将所有单元格对象保存到内存中,当所有的Excel单元格全部创建完成之后一次性写入到Excel并导出。当百万数据级别的Excel导出时,随着表格的不断创建,内存中对象越来越多,直至内存溢出。Apache Poi提供了SXSSFWork对象,专门用于处理大数据量Excel报表导出。
在实例化SXSSFWork这个对象时,可以指定在内存中所产生的POI导出相关对象的数量(默认100),一旦内存中的对象的个数达到这个指定值时,就将内存中的这些对象的内容写入到磁盘中(XML的文件格式),就可以将这些对象从内存中销毁,以后只要达到这个值,就会以类似的处理方式处理,直至Excel导出完成。
在原有代码的基础上替换之前的XSSFWorkbook,使用SXSSFWorkbook完成创建过程即可,但是SXSSFWorkbook不支持模板导出。
@RequestMapping(value = "/export/{month}", method = RequestMethod.GET)
public void export(@PathVariable(name = "month") String month) throws Exception {
//1.构造数据,某个月的用户数据
List<User> list =userService.findByMonth(month);
//2.创建工作簿
SXSSFWorkbook workbook = new SXSSFWorkbook();
//3.构造sheet
String[] titles = {"编号", "姓名", "手机","最高学历", "国家地区","生日", "时间"};
Sheet sheet = workbook.createSheet();
Row row = sheet.createRow(0);
AtomicInteger headersAi = new AtomicInteger();
for (String title : titles) {
Cell cell = row.createCell(headersAi.getAndIncrement());
cell.setCellValue(title);
}
AtomicInteger datasAi = new AtomicInteger(1);
Cell cell = null;
for (User user : list) {
Row dataRow = sheet.createRow(datasAi.getAndIncrement());
//编号
cell = dataRow.createCell(0);
cell.setCellValue(user.getUserId());
//姓名
cell = dataRow.createCell(1);
cell.setCellValue(user.getUsername());
//手机
cell = dataRow.createCell(2);
cell.setCellValue(user.getMobile());
//最高学历
cell = dataRow.createCell(3);
cell.setCellValue(user.getTheHighestDegreeOfEducation());
//国家地区
cell = dataRow.createCell(4);
cell.setCellValue(user.getNationalArea());
//生日
cell = dataRow.createCell(7);
cell.setCellValue(user.getBirthday());
//时间
cell = dataRow.createCell(12);
cell.setCellValue(user.getCreateTime());
}
String fileName = URLEncoder.encode(month+"人员信息.xlsx", "UTF-8");
response.setContentType("application/octet-stream");
response.setHeader("content-disposition", "attachment;filename=" + new String(fileName.getBytes("ISO8859-1")));
response.setHeader("filename", fileName);
workbook.write(response.getOutputStream());
}
使用POI基于事件模式解析百万数据的Excel文件。
用户模式:加载并读取Excel时,是通过一次性的将所有数据加载到内存中再去解析每个单元格内容。当Excel数据量较大时,由于不同的运行环境可能会造成内存不足甚至OOM异常。
事件模式:它逐行扫描文档,一边扫描一边解析。由于应用程序只是在读取数据时检查数据,因此不需要将数据存储在内存中,这对于大型文档的解析是个巨大优势。
(1)设置POI的事件模式
根据Excel获取文件流
根据文件流创建OPCPackage
创建XSSFReader对象
(2)Sax解析
自定义Sheet处理器
创建Sax的XmlReader对象
设置Sheet的事件处理器
逐行读取
我们都知道对于Excel2007的实质是一种特殊的XML存储数据,那就可以使用基于SAX的方式解析XML完成Excel的读取。SAX提供了一种从XML文档中读取数据的机制。它逐行扫描文档,一边扫描一边解析。由于应用程序只是在读取数据时检查数据,因此不需要将数据存储在内存中,这对于大型文档的解析是个巨大优势。
import cn.itcast.poi.entity.PoiEntity;
import org.apache.poi.xssf.eventusermodel.XSSFSheetXMLHandler;
import org.apache.poi.xssf.usermodel.XSSFComment;
/**
* 自定义的事件处理器
* 处理每一行数据读取
* 实现接口
*/
public class SheetHandler implements XSSFSheetXMLHandler.SheetContentsHandler {
private PoiEntity entity;
/**
* 当开始解析某一行的时候触发
* i:行索引
*/
@Override
public void startRow(int i) {
//实例化对象
if(i>0) {
entity = new PoiEntity();
}
}
/**
* 当结束解析某一行的时候触发
* i:行索引
*/
@Override
public void endRow(int i) {
//使用对象进行业务操作
System.out.println(entity);
}
/**
* 对行中的每一个表格进行处理
* cellReference: 单元格名称
* value:数据
* xssfComment:批注
*/
@Override
public void cell(String cellReference, String value, XSSFComment xssfComment) {
//对对象属性赋值
if(entity != null) {
String pix = cellReference.substring(0,1);
switch (pix) {
case "A":
entity.setId(value);
break;
case "B":
entity.setBreast(value);
break;
case "C":
entity.setAdipocytes(value);
break;
case "D":
entity.setNegative(value);
break;
case "E":
entity.setStaining(value);
break;
case "F":
entity.setSupportive(value);
break;
default:
break;
}
}
}
}
/**
* 自定义Excel解析器
*/
public class ExcelParser {
public void parse (String path) throws Exception {
//1.根据Excel获取OPCPackage对象
OPCPackage pkg = OPCPackage.open(path, PackageAccess.READ);
try {
//2.创建XSSFReader对象
XSSFReader reader = new XSSFReader(pkg);
//3.获取SharedStringsTable对象
SharedStringsTable sst = reader.getSharedStringsTable();
//4.获取StylesTable对象
StylesTable styles = reader.getStylesTable();
//5.创建Sax的XmlReader对象
XMLReader parser = XMLReaderFactory.createXMLReader();
//6.设置处理器
parser.setContentHandler(new XSSFSheetXMLHandler(styles,sst, new
SheetHandler(), false));
XSSFReader.SheetIterator sheets = (XSSFReader.SheetIterator)
reader.getSheetsData();
//7.逐行读取
while (sheets.hasNext()) {
InputStream sheetstream = sheets.next();
InputSource sheetSource = new InputSource(sheetstream);
try {
parser.parse(sheetSource);
} finally {
sheetstream.close();
}
}
} finally {
pkg.close();
}
}
}