EasyExcel实现Excel文件导入导出 - 很详细
EasyPoi实现excel文件导入导出 - 很详细
Excel文件导入导出 - 专栏
【狂神说Java】POI技术详解
在企业级应用开发中,Excel报表是一种最常见的报表需求。Excel报表开发一般分为两种形式:
目前世面上的Excel分为两个大的版本Excel2003和Excel2007及以上两个版本,两者之间的区别如下:
Excel2003是一个特有的二进制格式,其核心结构是复合文档类型的结构,存储数据量较小;Excel2007 的核心结构是 XML 类型的结构,采用的是基于 XML 的压缩方式,使其占用的空间更小,操作效率更高
Java中常见的用来操作Excl的方式一般有2种:JXL和POI。
Apache POI是Apache软件基金会的开源项目,由Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java语言操作Microsoft Office的功能。
导入依赖
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>cn.itcastgroupId>
<artifactId>poi-demoartifactId>
<version>1.0-SNAPSHOTversion>
<dependencies>
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poiartifactId>
<version>4.0.1version>
dependency>
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poi-ooxmlartifactId>
<version>4.0.1version>
dependency>
<dependency>
<groupId>org.apache.poigroupId>
<artifactId>poi-ooxml-schemasartifactId>
<version>4.0.1version>
dependency>
dependencies>
project>
/**
* 使用POI创建excel
*/
public class PoiTest01 {
public static void main(String[] args) throws Except ion {
//1.创建工作簿 HSSFWorkbook -- 2003
Workbook wb = new XSSFWorkbook(); //2007版本
//2.创建表单sheet
Sheet sheet = wb.createSheet("test");
//3.文件流
FileOutputStream fos = new FileOutputStream("E:\\excel\\poi\\test.xlsx");
//4.写入文件
wb.write(fos);
fos.close();
}
}
/**
* 创建单元格写入内容
*/
public class PoiTest02 {
public static void main(String[] args) 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();
}
}
/**
* 单元格样式处理
*/
public class PoiTest03 {
public static void main(String[] args) 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();
}
}
/**
* 插入图片
*/
public class PoiTest04 {
public static void main(String[] args) throws Exception {
//创建工作簿 HSSFWorkbook -- 2003
Workbook wb = new XSSFWorkbook(); //2007版本
//创建表单sheet
Sheet sheet = wb.createSheet("test");
//读取图片流
FileInputStream stream = new FileInputStream("E:\\excel\\poi\\logo.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);
/*
// 如下设置可以让图片限制在指定的单元格内
// 位置 后四个参数: 前两个: 图片左上角的X,Y坐标 后两个:图片右下左上角的X,Y坐标
XSSFClientAnchor anchor = new XSSFClientAnchor(0, 0, 0, 0, 1, 1, 5, 8);
anchor.setAnchorType(ClientAnchor.AnchorType.DONT_MOVE_AND_RESIZE);
*/
//绘制图片
Picture picture = patriarch.createPicture(anchor, index);//图片位置,图片的索引
picture.resize();//自适应渲染图片
//文件流
FileOutputStream fos = new FileOutputStream("E:\\excel\\poi\\test3.xlsx");
//写入文件
wb.write(fos);
fos.close();
}
}
/**
* 读取excel并解析
* sheet.getLastRowNum() : 最后一行的索引(从0开始数)
* row.getLastCellNum() : 最后一个单元格的号码(从1开始数)
*/
public class PoiTest05 {
public static void main(String[] args) throws Exception {
// 如果excel文件过大报错,可使用下面这句代码
ZipSecureFile.setMinInflateRatio(-1.0d);
//1.根据Excel文件创建工作簿
Workbook wb = new XSSFWorkbook("E:\\excel\\poi\\demo.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();
// (这里返回的最后一列不是按索引,是从1开始)
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;
}
}
// poi将会把单元格内的数字都会认为是double类型,所以需要转换
public static void main(String[] args) {
DecimalFormat decimalFormat1 = new DecimalFormat("#");
DecimalFormat decimalFormat2 = new DecimalFormat("#.#");
DecimalFormat decimalFormat3 = new DecimalFormat("#.##");
DecimalFormat decimalFormat4 = new DecimalFormat("#.###");
String format1 = decimalFormat1.format(4.25); // 这里接的参数是Object类型
String format2 = decimalFormat2.format(4.25);
String format3 = decimalFormat3.format(4.25);
String format4 = decimalFormat4.format(4.25);
System.out.println(format1); // 4
System.out.println(format2); // 4.2
System.out.println(format3); // 4.25
System.out.println(format4); // 4.25
System.out.println(((Double)4.25).intValue()); // 4
}
模板如下
代码如下
// 读取类路径下的hr-demo.xlsx的模板Excel文件的样式,然后读取原有的样式并在写入数据的时候应用原有的样式
@Test
public void test_template() throws IOException, InvalidFormatException {
// 读取模板的数据样式
ClassPathResource resource = new ClassPathResource("hr-demo.xlsx");
XSSFWorkbook workbook = new XSSFWorkbook(resource.getFile());
XSSFSheet sheet = workbook.getSheetAt(0);
XSSFRow row = sheet.getRow(2);
short defaultRowHeight = row.getHeight(); // 获取行高
CellStyle[] cellStyles = new CellStyle[row.getLastCellNum()];
for (int i = 0; i < cellStyles.length; i++) {
XSSFCell cell = row.getCell(i);
cellStyles[i] = cell.getCellStyle();
}
// 使用读取到的模板样式,写入数据
// 第一步:修改标题
XSSFRow row0 = sheet.getRow(0);
XSSFCell cell00 = row0.getCell(0);
cell00.setCellValue("xx年xx月xx日人事报表");
ArrayList<User> users = new ArrayList<>();
users.add(new User("zj", 25));
users.add(new User("zzhua", 23));
int startRow = 2;
for (User user : users) {
XSSFRow r = sheet.createRow(startRow++);
r.setHeight(defaultRowHeight);
XSSFCell cell = r.createCell(0);
for (int i = 0; i < 3; i++) {
XSSFCell iCell = r.createCell(i);
iCell.setCellStyle(cellStyles[i]);
if (i == 0) {
iCell.setCellValue(startRow-1);
} else if (i == 1) {
iCell.setCellValue(user.getName());
} else {
iCell.setCellValue(user.getAge());
}
}
}
FileOutputStream fos = new FileOutputStream("D:\\Projects\\practice\\poi\\poi"
+"demo\\src\\main\\resources\\test.xlsx");
workbook.write(fos);
fos.flush();
fos.close();
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface ExcelAttribute {
/** 对应的列名称 */
String name() default "";
/** 列序号 */
int sort();
/** 字段类型对应的格式 */
String format() default "";
}
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import javax.servlet.http.HttpServletResponse;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.URLEncoder;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
public class ExcelExportUtil<T> {
private int rowIndex; // 数据开始行
private int styleIndex; // 数据的样式开始行
private String templatePath; // excel模板的路径
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 {
// 拿到workbook,并获取到第一页
XSSFWorkbook workbook = new XSSFWorkbook(is);
Sheet sheet = workbook.getSheetAt(0);
// 获取样式行,并提取所有样式
Row styleRow = sheet.getRow(styleIndex);
CellStyle[] styles = getTemplateStyles(styleRow);
// 获取样式行的行高
short defaultHeight = styleRow.getHeight();
// 从要写的数据行开始
AtomicInteger datasAi = new AtomicInteger(rowIndex);
// 遍历所有的数据
for (T t : objs) {
// 拿到一条待写入的对象,并且遍历样式就行了
Row row = sheet.createRow(datasAi.getAndIncrement()); // 相当于后++
// 设置行高
row.setHeight(defaultHeight);
for(int i=0;i<styles.length;i++) {
Cell cell = row.createCell(i);
// 设置提取的样式
cell.setCellStyle(styles[i]);
// 根据每一列的索引匹配字段(后面可以优化下,将字段放入map,然后去找字段)
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;
}
}
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.DateUtil;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
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 {
// 获取文件,包装成WorkBook
XSSFWorkbook workbook = new XSSFWorkbook(is);
// 获取第一页
Sheet sheet = workbook.getSheetAt(0);
// 获取到最后有数据的一行的索引(从0开始)
int rowLength = sheet.getLastRowNum();
System.out.println(sheet.getLastRowNum());
// rowIndex 读取数据的开始行
for (int rowNum = rowIndex; rowNum <= sheet.getLastRowNum(); rowNum++) {
// 获取到所在行数据
Row row = sheet.getRow(rowNum);
// 反射创建对象
entity = (T) clazz.newInstance();
// 当前行最后有数据的列数(从1开始)
System.out.println(row.getLastCellNum());
// cellIndex读起的列索引(因为下面用的getCell(index))
for (int j = cellIndex; j < row.getLastCellNum(); j++) {
// 根据列拿到当行对应列的单元格
Cell cell = row.getCell(j);
// 遍历所有字段,找到sort与列匹配的字段,获取值,并通过反射设置给对象
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 "";
}
}
}
我们都知道Excel可以分为早期的Excel2003版本(使用POI的HSSF对象操作)和Excel2007版本(使用POI的XSSF操作),两者对百万数据的支持如下:
没有性能监控工具一切推论都只能停留在理论阶段,我们可以使用Java的性能监控工具来监视程序的运行情况,包括CUP,垃圾回收,内存的分配和使用情况,这让程序的运行阶段变得更加可控,也可以用来证明我们的推测。这里我们使用JDK提供的性能工具Jvisualvm来监控程序运行。
VisualVM 是Netbeans的profile子项目,已在JDK6.0 update 7 中自带,能够监控线程,内存情况,查看方法的CPU时间和内存中的对 象,已被GC的对象,反向查看分配的堆栈
Jvisualvm位于JAVA_HOME/bin目录下,直接双击就可以打开该程序。如果只是监控本地的java进程,是不需要配置参数的,直接打开就能够进行监控。首先我们需要在本地打开一个Java程序,例如我打开员工微服务进程,这时在jvisualvm界面就可以看到与IDEA相关的Java进程了
Jvisualvm使用起来比较简单,双击点击当前运行的进程即可进入到程序的监控界面
对于百万数据量的Excel导入导出,只讨论基于Excel2007的解决方法。在ApachePoi 官方提供了对操作大数据量的导入导出的工具和解决办法,操作Excel2007使用XSSF对象,可以分为三种模式:
用户模式:用户模式有许多封装好的方法操作简单,但创建太多的对象,非常耗内存(之前使用的方法)
事件模式:基于SAX方式解析XML,SAX全称Simple API for XML,它是一个接口,也是一个软件包。它是一种XML解析的替代方法,不同于DOM解析XML文档时把所有内容一次性加载到内存中的方式,它逐行扫描文档,一边扫描,一边解析。
SXSSF对象:是用来生成海量excel数据文件,主要原理是借助临时存储空间生成excel
这是一张Apache POI官方提供的图片,描述了基于用户模式,事件模式,以及使用SXSSF三种方式操作Excel的特性以及CUP和内存占用情况。
1.从java安装的bin目录下打开jvisualvm.exe程序
使用XSSFWorkBook写入一百万条数据,查看堆内存变化
将XSSFWorkBook改为SXSSFWorkBook,启动并查看临时文件夹,有数据写入到了文件中,并且是以xml格式写入的
基于XSSFWork导出Excel报表,是通过将所有单元格对象保存到内存中,当所有的Excel单元格全部创建完成之后一次性写入到Excel并导出。当百万数据级别的Excel导出时,随着表格的不断创建,内存中对象越来越多,直至内存溢出。Apache Poi提供了SXSSFWork对象,专门用于处理大数据量Excel报表导出。
在实例化SXSSFWork这个对象时,可以指定在内存中所产生的POI导出相关对象的数量(默认100),一旦内存中的对象的个数达到这个指定值时,就将内存中的这些对象的内容写入到磁盘中(XML的文件格式),就可以将这些对象从内存中销毁,以后只要达到这个值,就会以类似的处理方式处理,直至Excel导出完成。
在原有代码的基础上替换之前的XSSFWorkbook,使用SXSSFWorkbook完成创建过程即可
(百万数据导出不支持模板,有待测试,因为避免创建过多的对象)
//1.构造数据
List<EmployeeReportResult> list =
userCompanyPersonalService.findByReport(companyId,month+"%");
//2.创建工作簿
SXSSFWorkbook workbook = new SXSSFWorkbook(); // 默认100
//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(int i=0;i<10000;i++) {
for (EmployeeReportResult report : list) {
Row dataRow = sheet.createRow(datasAi.getAndIncrement());
//编号
cell = dataRow.createCell(0);
cell.setCellValue(report.getUserId());
//姓名
cell = dataRow.createCell(1);
cell.setCellValue(report.getUsername());
//手机
cell = dataRow.createCell(2);
cell.setCellValue(report.getMobile());
//最高学历
cell = dataRow.createCell(3);
cell.setCellValue(report.getTheHighestDegreeOfEducation());
//国家地区
cell = dataRow.createCell(4);
cell.setCellValue(report.getNationalArea());
//护照号
cell = dataRow.createCell(5);
cell.setCellValue(report.getPassportNo());
//籍贯
cell = dataRow.createCell(6);
cell.setCellValue(report.getNativePlace());
//生日
cell = dataRow.createCell(7);
cell.setCellValue(report.getBirthday());
//属相
cell = dataRow.createCell(8);
cell.setCellValue(report.getZodiac());
//入职时间
cell = dataRow.createCell(9);
cell.setCellValue(report.getTimeOfEntry());
//离职类型
cell = dataRow.createCell(10);
cell.setCellValue(report.getTypeOfTurnover());
//离职原因
cell = dataRow.createCell(11);
cell.setCellValue(report.getReasonsForLeaving());
//离职时间
cell = dataRow.createCell(12);
cell.setCellValue(report.getResignationTime());
}
}
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());
XSSFWorkbook生成百万数据报表
使用XSSFWorkbook生成Excel报表,时间较长,随着时间推移,内存占用原来越多,直至内存溢出
SXSSFWorkbook生成百万数据报表
使用SXSSFWorkbook生成Excel报表,内存占用比较平缓
使用POI基于事件模式解析案例提供的Excel文件
用户模式:加载并读取Excel时,是通过==一次性的将所有数据加载到内存中再去解析每个单元格内容==。当Excel数据量较大时,由于不同的运行环境可能会造成内存不足甚至OOM异常。
事件模式:它逐行扫描文档,一边扫描一边解析。由于应用程序只是在读取数据时检查数据,因此不需要将数据存储在内存中,这对于大型文档的解析是个巨大优势。
1、设置POI的事件模式
根据Excel获取文件流
根据文件流创建OPCPackage
创建XSSFReader对象
2、Sax解析
自定义Sheet处理器
创建Sax的XmlReader对象
设置Sheet的事件处理器
逐行读取
我们都知道对于Excel2007的实质是一种特殊的XML存储数据,那就可以使用基于SAX的方式解析XML完成Excel的读取。SAX提供了一种从XML文档中读取数据的机制。它逐行扫描文档,一边扫描一边解析。由于应用程序只是在读取数据时检查数据,因此不需要将数据存储在内存中,这对于大型文档的解析是个巨大优势
读取到某行时,调用注册的事件处理器处理数据,然后再处理下一行,同样继续调用注册的事件处理器。
实体类
@Data
public class PoiEntity {
private String id;
private String breast;
private String adipocytes;
private String negative;
private String staining;
private String supportive;
}
自定义事件处理器
/**
* 自定义的事件处理器
* 处理每一行数据读取
* 实现接口
*/
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报表
/**
* 使用事件模型解析百万数据excel报表
*/
public class MassivePoiTest {
public static void main(String[] args) throws Exception {
String path = "C:\\Users\\ThinkPad\\Desktop\\ihrm\\day8\\资源\\百万数据报表\\demo.xlsx";
//1.根据excel报表获取OPCPackage
OPCPackage opcPackage = OPCPackage.open(path, PackageAccess.READ);
//2.创建XSSFReader
XSSFReader reader = new XSSFReader(opcPackage);
//3.获取SharedStringTable对象
SharedStringsTable table = reader.getSharedStringsTable();
//4.获取styleTable对象
StylesTable stylesTable = reader.getStylesTable();
//5.创建Sax的xmlReader对象
XMLReader xmlReader = XMLReaderFactory.createXMLReader();
//6.注册事件处理器
XSSFSheetXMLHandler xmlHandler = new XSSFSheetXMLHandler(stylesTable,table,
new SheetHandler(),
false);
xmlReader.setContentHandler(xmlHandler);
//7.逐行读取
XSSFReader.SheetIterator sheetIterator = (XSSFReader.SheetIterator) reader.getSheetsData();
while (sheetIterator.hasNext()) {
InputStream stream = sheetIterator.next(); //每一个sheet的流数据
InputSource is = new InputSource(stream);
xmlReader.parse(is);
}
}
}
用户模式下读取测试Excel文件直接内存溢出,测试Excel文件映射到内存中还是占用了不少内存;事件模式下可以流畅的运行
(1)使用用户模型解析
(2)使用事件模型解析
通过简单的分析以及运行两种模式进行比较,可以看到用户模式下使用更简单的代码实现了Excel读取,但是在读取大文件时CPU和内存都不理想;而事件模式虽然代码写起来比较繁琐,但是在读取大文件时CPU和内存更加占优势。