Java操作Excel的相关技术
1 POI报表概述
1.1 背景说明
java代码操作excel有两种api类库
- jxl (只能操作旧版本的excel)
- poi(新的和旧的excel版本都能操作)
常见的应用场景:
- 为了方便操作,基于Excel的报表批量上传数据
- 通过java代码生成Excel报表导出
1.2 Excel的两种形式
目前世面上的Excel分为两个大的版本Excel2003和Excel2007及以上两个版本.
- Excel2003是一个特有的二进制格式,其核心结构是复合文档类型的结构,存储数据量较小
- Excel2007 的核心结构是 XML 类型的结构,采用的是基于 XML 的压缩方式,使其占用的空间更小,操作效率更高
1.3 常见的Excel操作工具
Java中常见的用来操作Excel的方式一般有2种:JXL和POI
- JXL只能对Excel进行操作,属于比较老的框架,它只支持到Excel 95-2000的版本。现在已经停止更新和维护
- POI是apache的项目,可对微软的Word,Excel,Ppt 进行操作,包括office2003和2007,Excl2003和2007。poi现在一直有更新。所以现在主流使用POI
1.4 POI是?
Apache POI是Apache软件基金会的开源项目,由Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java语言操作Microsoft Office的功能.
Apache POI是目前最流行的操作Microsoft Office的API组件,借助POI可以方便的完成诸如:数据报表生成,数据批量上传,数据备份等工作.
1.5 API对象说明
工作簿 一个excel文件 | WorkBook | HSSfWordBook : 2003版本 | XSSfWorkBook : 2007级以上 |
---|---|---|---|
工作表一个一个的sheet | Sheet | HssfSheet : 2003版本 | XssfSheet : 2007级以上 |
行 | Row | HssfRow : 2003版本 | XssfRow : 2007级以上 |
单元格 | Cell | HssfCell : 2003版本 | XssfCell : 2007级以上 |
2 POI入门案列
2.1 环境搭建
pom.xml中导入坐标
<dependencies>
<!-- 操作低版本的excel-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.0.1</version>
</dependency>
<!-- 操作高版本的excel-->
<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>
2.2 创建Excel
package com.cf;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
//创建一个Excel并且向里面随便写入一句话
public class POIDemo {
public static void main(String[] args) throws Exception{
/* XSSFWorkbook 工作薄 一个Excel文件
XSSFSheet 工作表 Excel中的工作表
XSSFRow 行 每个工作表的行
XSSFCell 单元格 每个行中的单元格
XSSFCellStyle 单元格样式 */
//1、创建一个全新的工作薄 里面什么都没有
XSSFWorkbook workbook = new XSSFWorkbook();
//2、创建一个工作表
XSSFSheet sheet = workbook.createSheet("POI报表测试");
//设置列宽
sheet.setColumnWidth(0,17*256); // 单位 1代表一个字母的256分之一
//3、创建第一行行
XSSFRow row = sheet.createRow(0);
//4、创建第一个单元格
XSSFCell cell = row.createCell(0);
//5、向单元格中放一句话
cell.setCellValue("这是第一次用POI写数据");
//6、把Excel输出到磁盘上
workbook.write(new FileOutputStream("d://demo.xlsx"));
workbook.close();
}
}
2.3 读取Excel
package com.cf;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileInputStream;
import java.io.FileOutputStream;
//读取一个Excel中的内容
public class POITest {
public static void main(String[] args) throws Exception{
//1、创建一个的工作薄
XSSFWorkbook workbook = new XSSFWorkbook(new FileInputStream("d://demo.xlsx"));
//2、获取一个工作表
XSSFSheet sheet = workbook.getSheetAt(0);
//3、获取第一行行
XSSFRow row = sheet.getRow(0);
//4、获取第一个单元格
XSSFCell cell = row.getCell(0);
//5、获取单元格中的内容 字符串类型
//需要注意不同的类型,需要不同的方法去读取
// 字符,布尔,数字(日期,普通数字),公式,空
String value = cell.getStringCellValue();
System.out.println(value);
workbook.close();
/* 引入1: 根据不同的类型获取单元格值
Cell cell = rowData.getCell(cellNum);
if(cell !=null)
{
int cellType = cell.getCellType();
//判断单元格数据类型
String cellValue = "";
switch (cellType) {
case HSSFCell.CELL_TYPE_STRING://字符串
System.out.print("【STRING】");
cellValue = cell.getStringCellValue();
break;
case HSSFCell.CELL_TYPE_BOOLEAN://布尔
System.out.print("【BOOLEAN】");
cellValue = String.valueOf(cell.getBooleanCellValue());
break;
case HSSFCell.CELL_TYPE_BLANK://空
System.out.print("【BLANK】");
break;
case HSSFCell.CELL_TYPE_NUMERIC:
System.out.print("【NUMERIC】");
//cellValue = String.valueOf(cell.getNumericCellValue());
if (HSSFDateUtil.isCellDateFormatted(cell)) {//日期
System.out.print("【日期】");
Date date = cell.getDateCellValue();
cellValue = new DateTime(date).toString("yyyy-MM-dd");
} else {
// 不是日期格式,则防止当数字过长时以科学计数法显示
System.out.print("【转换成字符串】");
cell.setCellType(HSSFCell.CELL_TYPE_STRING);
cellValue = cell.toString();
}
break;
case Cell.CELL_TYPE_ERROR:
System.out.print("【数据类型错误】");
break;
}
*/
/* 引入2:读取公式 并计算结果
// 读取第五行第一列
Row row = sheet.getRow(4);
Cell cell = row.getCell(0);
//公式计算器
FormulaEvaluator formulaEvaluator = new HSSFFormulaEvaluator((HSSFWorkbook) workbook);
// 输出单元内容
int cellType = cell.getCellType();
switch (cellType) {
case Cell.CELL_TYPE_FORMULA://2 公式
//得到公式
String formula = cell.getCellFormula();
System.out.println(formula);
CellValue evaluate = formulaEvaluator.evaluate(cell);
//String cellValue = String.valueOf(evaluate.getNumberValue());
String cellValue = evaluate.formatAsString();
System.out.println(cellValue);
break;
}
*/
}
}
2.4 创建有样式的Excel
package com.cf;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileOutputStream;
//使用java代码创建一个新版本全新的excel,里面带样式
public class POIDemo1 {
public static void main(String[] args) throws Exception {
//创建了一个里面什么都没有的工作薄
Workbook workbook = new XSSFWorkbook();
//创建样式、
CellStyle cellStyle = workbook.createCellStyle();
//居中对齐
cellStyle.setAlignment(HorizontalAlignment.CENTER); //水平对齐方式
cellStyle.setVerticalAlignment(VerticalAlignment.CENTER); //垂直对齐方式
//字体 宋体 16号 加粗
Font font = workbook.createFont();
font.setFontName("宋体");
font.setFontHeightInPoints((short) 16);
font.setBold(true);
cellStyle.setFont(font);
//创建样式、
CellStyle cellStyle1 = workbook.createCellStyle();
//居中对齐
cellStyle1.setAlignment(HorizontalAlignment.CENTER); //水平对齐方式
cellStyle1.setVerticalAlignment(VerticalAlignment.CENTER); //垂直对齐方式
cellStyle1.setBorderBottom(BorderStyle.THIN); //下边框 细线
cellStyle1.setBorderLeft(BorderStyle.THIN);
cellStyle1.setBorderRight(BorderStyle.THIN);
cellStyle1.setBorderTop(BorderStyle.THIN);
//字体 黑体 12号
Font font1 = workbook.createFont();
font1.setFontName("黑体");
font1.setFontHeightInPoints((short) 12);
font1.setBold(false);
cellStyle1.setFont(font1);
//创建新的工作表sheet
Sheet sheet = workbook.createSheet("POI报表测试");
//设置sheet的列宽
sheet.setColumnWidth(0,4200); //1代表一个字母宽度的256分之一
sheet.setColumnWidth(1,26*256); //1代表一个字母宽度的256分之一
sheet.setColumnWidth(2,16*256); //1代表一个字母宽度的256分之一
sheet.setColumnWidth(3,26*256); //1代表一个字母宽度的256分之一
sheet.setColumnWidth(4,16*256); //1代表一个字母宽度的256分之一
sheet.setColumnWidth(5,16*256); //1代表一个字母宽度的256分之一
sheet.setColumnWidth(6,16*256); //1代表一个字母宽度的256分之一
sheet.setColumnWidth(7,16*256); //1代表一个字母宽度的256分之一
sheet.setColumnWidth(8,16*256); //1代表一个字母宽度的256分之一
Row bigTitleRow = sheet.createRow(0);
//创建单元格
for (int i = 1; i <= 8; i++) {
bigTitleRow.createCell(i);
}
//设置行高
bigTitleRow.setHeightInPoints(36);
//合并单元格
sheet.addMergedRegion(new CellRangeAddress(0,0,1,8));
//向合并后的单元格中写入一句话:2021年1月份出货表
Cell cell = bigTitleRow.getCell(1);
cell.setCellValue("2021年1月份出货表");
cell.setCellStyle(cellStyle);
//创建小标题行
//客户
Row titleRow = sheet.createRow(1);
Cell cell1 = titleRow.createCell(1);
cell1.setCellValue("客户");
cell1.setCellStyle(cellStyle1);
//把excel输出到我的磁盘上
workbook.write(new FileOutputStream("d://test.xlsx"));
}
}
3 EasyExcel
3.1 概述
EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel.
快速简单,避免OOM的Excel工具.
3.2 写Excel
@Data
public class DemoData {
@ExcelProperty("字符串标题")
private String string;
@ExcelProperty("日期标题")
private Date date;
@ExcelProperty("数字标题")
private Double doubleData;
/**
* 忽略这个字段
*/
@ExcelIgnore
private String ignore;
}
//伪数据, 真实场景为业务数据
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;
}
// 最简单的写
@Test
public void simpleWrite() {
// 写法1
String fileName = path+"EasyExcel.xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
// 如果这里想使用03 则 传入excelType参数即可
EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
// 写法2
fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
// 这里 需要指定写用哪个class去写
ExcelWriter excelWriter = null;
try {
excelWriter = EasyExcel.write(fileName, DemoData.class).build();
WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
excelWriter.write(data(), writeSheet);
} finally {
// 千万别忘记finish 会帮忙关闭流
if (excelWriter != null) {
excelWriter.finish();
}
}
}
3.3 读Excel
// 最简单的读
@Test
public void simpleRead() {
//写法一
String fileName = path+"EasyExcel.xlsx";
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
// 写法2:
fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
ExcelReader excelReader = null;
try {
excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build();
ReadSheet readSheet = EasyExcel.readSheet(0).build();
excelReader.read(readSheet);
} finally {
if (excelReader != null) {
// 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
excelReader.finish();
}
}
}
写一个监听类, 读取时,一行一行的读取数据,批量保存.
// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
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>();
/**
* 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
*/
private DemoDAO demoDAO;
public DemoDataListener() {
// 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
demoDAO = new DemoDAO();
}
/**
* 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
*
* @param demoDAO
*/
public DemoDataListener(DemoDAO demoDAO) {
this.demoDAO = demoDAO;
}
/**
* 这个每一条数据解析都会来调用
*
* @param data
* one row value. Is is same as {@link AnalysisContext#readRowHolder()}
* @param context
*/
@Override
public void invoke(DemoData data, AnalysisContext context) {
System.out.println(JSON.toJSONString(data));
list.add(data);
System.out.println(list);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (list.size() >= BATCH_COUNT) {
saveData();
// 存储完成清理 list
list.clear();
}
}
/**
* 所有数据解析完成了 都会来调用
*
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
saveData();
LOGGER.info("所有数据解析完成!");
}
/**
* 加上存储数据库
*/
private void saveData() {
demoDAO.save(list);
}
}