1. Apache POI介绍
需求场景:
- 将一些数据库信息导出为Excel表格
- 将Excel表格数据导入数据库
- 大量数据的导入导出操作
1.1 需求背景
常用的解决方案:
- Apache POI:Apache POI是Apache软件基金会的一个开源项目,它提供了一组Java API,用于操作Microsoft Office文件格式,包括Excel、Word和PowerPoint等文件
- EasyExcel:EasyExcel是阿里巴巴的一个开源项目,它是一个基于注解的Excel读写库,能够快速地读写Excel文件,支持读取大型Excel文件、写入数据到模板中等操作
Apache POI和EasyExcel的简单对比:
- 功能丰富程度:Apache POI提供了更加全面的功能,支持读取、写入、格式化、样式、图表、公式等等,而EasyExcel则主要提供了基本的读写和样式功能,但EasyExcel在大数据量处理方面更有优势
- 学习成本:Apache POI使用相对复杂,需要深入理解Excel文件格式和API,而EasyExcel则提供了更加简便易用的API和基于注解的方式,使得学习成本相对较低
- 性能表现:在处理大数据量时,EasyExcel的性能表现优于Apache POI
技术选项:
- 如果需要简便易用的API和处理大数据量 --> 推荐使用EasyExcel
- 如果需要对Execl更加自定义和个性化的处理和对doc进行操作 --> 推荐使用Apache POI
1.2 Apache POI基础介绍
Apache POI是基于Office Open XML标准(OOXML)和Microsoft的OLE 2复合文档格式(OLE2)处理各种文件格式的开源项目,它提供了丰富的功能,包括读取、写入、格式化、样式、图表、公式等等
模块:
- HSSF:提供了对Excel 97-2003文件格式(.xls)的支持,包括读取、写入、格式化、样式、公式、图表等等
- XSSF:提供了对Excel 2007及以上文件格式(.xlsx)的支持,包括读取、写入、格式化、样式、公式、图表等等
- SXSSF:提供了一种基于流的方式处理大型Excel文件,可以避免OOM(Out of Memory)错误
- SS:提供了一个通用的接口,可以在不同的Excel文件格式之间切换,包括HSSF、XSSF、SXSSF等等
- HWPF:提供读写Microsoft word DOC97格式(Microsoft word 97-(2003))档案的功能
- XWPF:提供读写Microsoft word DOC2003格式(WordprocessingML(2007+))档案的功能
- …………
POI存在的问题:
java解析,生成Excel比较有名的框架有POI、JXL。但它们都存在一个严重的问题,就是非常耗损内存,也就是数据量比较大的时候有可能出现OOM问题,但是POI有一套SAX模式的API可以在一定程度上解决一些内存溢出问题,但是依旧没有完全的解决内存销耗过大的问题
POI操作Execl具体使用时需要了解的对象:
- Workbook(工作簿):就是一个xls或xlsx文件
- sheet(工作表)
- Row(行)
- Cell(单元格-列)
Excel表03与07版本区别:POI支持不同版本Excel表格的操作,对应的版本有03和07+版本
1)03版本:最大行数支持 65536 后缀为.xls
2)07版本:理论上没有限制,但实际最大行数支持为1048576 后缀为.xlsx
2. POI的Execl数据写入
2.1 POI简单数据写入
导入依赖:
<!--03版本--> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>4.1.2</version> </dependency> <!--07版本--> <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi-ooxml</artifactId> <version>4.1.2</version> </dependency>
完成通过POI写入Execl表格数据的操作步骤:
- 创建工作簿 Workbook
- 创建工作表 sheet
- 创建行 Row
- 创建列(单元格) Cell
- 具体的数据写入
① 03版本测试写入:
//1.创建工作簿(03版本) Workbook workbook = new HSSFWorkbook(); //2.创建工作表 Sheet sheet = workbook.createSheet("03版本测试"); //3.创建行(第一行) Row row1 = sheet.createRow(0); //4.创建单元格并写入数据 //(1,1) Cell cell1 = row1.createCell(0); cell1.setCellValue("空想家ID"); //(1,2) Cell cell2 = row1.createCell(1); cell2.setCellValue("空想家姓名"); //5.创建行(第二行) Row row2 = sheet.createRow(1); //(2,1) Cell cell3 = row2.createCell(0); cell3.setCellValue("155"); //(2,2) Cell cell4 = row2.createCell(1); cell4.setCellValue("晴天"); //6.创建输出流将workbook进行输出到本地 FileOutputStream fileOutputStream = new FileOutputStream("./03版本测试.xls"); workbook.write(fileOutputStream); //7.关闭流 fileOutputStream.close(); System.out.println("xls文件保存成功!");
② 07版本测试写入(和03版本使用方式是一样的,唯一不同的就是调用的模块不一样,07版本使用XSSF模块)
//1.创建工作簿(03版本) Workbook workbook = new XSSFWorkbook(); //2.创建工作表 Sheet sheet = workbook.createSheet("07版本测试"); //3.创建行(第一行) Row row1 = sheet.createRow(0); //4.创建单元格并写入数据 //(1,1) Cell cell1 = row1.createCell(0); cell1.setCellValue("商品ID"); //(1,2) Cell cell2 = row1.createCell(1); cell2.setCellValue("商品姓名"); //5.创建行(第二行) Row row2 = sheet.createRow(1); //(2,1) Cell cell3 = row2.createCell(0); cell3.setCellValue("1"); //(2,2) Cell cell4 = row2.createCell(1); cell4.setCellValue("鼠标"); //6.创建输出流将workbook进行输出到本地 FileOutputStream fileOutputStream = new FileOutputStream("./07版本测试.xlsx"); workbook.write(fileOutputStream); //7.关闭流 fileOutputStream.close(); System.out.println("xlsx文件保存成功!");
2.2 POI批量数据写入
不管使用03版本还是07+版本进行数据写入,在实际的开发中都会涉及到批量数据操作,尤其是数据量比较多的时候,在批量数据处理上03版本和07版本处理逻辑是不同的
- 03版本HSSF:
- 最多支撑65536行数据的写入,超出时会抛出异常
- 操作方式为,先将所有的数据放入缓存中,最后一次性写入磁盘,写入速度快
- 07+版本XSSF:
- 数据的写入速度比较慢,因为XSSF是获取全部行的数据,因此会销耗大量内存,数据量庞大时就可能出现内存溢出(OOM)
- 可以写入较大的数据,比如10w+条数据
1)03版本批量数据写入65536条数据
//开始时间 long start = System.currentTimeMillis(); //1.创建工作簿 Workbook workbook = new HSSFWorkbook(); //2.创建表 Sheet sheet = workbook.createSheet("03版本数据批量写入"); //3.批量写入数据 for(int rownum =0;rownum<65536;rownum++){ Row row = sheet.createRow(rownum); for(int cellnum=0;cellnum<20;cellnum++){ Cell cell = row.createCell(cellnum); cell.setCellValue(cellnum+1); } } //4.写文件 FileOutputStream fileOutputStream = new FileOutputStream("./03版本数据批量写入.xls"); workbook.write(fileOutputStream); //5.释放 fileOutputStream.close(); //结束时间 long end = System.currentTimeMillis(); System.out.println(end-start);
2)07版本批量数据写入65536条数据
//开始时间 long start = System.currentTimeMillis(); //1.创建工作簿 Workbook workbook = new XSSFWorkbook(); //2.创建表 Sheet sheet = workbook.createSheet("07版本数据批量写入"); //3.批量写入数据 for(int rownum =0;rownum<65536;rownum++){ Row row = sheet.createRow(rownum); for(int cellnum=0;cellnum<20;cellnum++){ Cell cell = row.createCell(cellnum); cell.setCellValue(cellnum+1); } } //4.写文件 FileOutputStream fileOutputStream = new FileOutputStream("./07版本数据批量写入.xlsx"); workbook.write(fileOutputStream); //5.释放 fileOutputStream.close(); //结束时间 long end = System.currentTimeMillis(); System.out.println(end-start);
2.3 POI大数据量写入
由于使用XSSF进行批量数据写入效率非常低,为了解决大数据量写入的问题,官方提供了SXSSF来解决大文件写入问题,它可以写入非常大量的数据,比如上百万条,并且写入的速度更快,占用内存更少。
SXSSF:在兼容XSSF的同时,能够应对大数据量和内存空间有限的情况。SXSSF每次获取的行数是在一个数值范围内,这个范围被称作"滑动窗口",在这个窗口内的数据均存在于内存中,超出这个窗口大小时,数据会被写入磁盘,因此控制内存使用,先比较而言,XSSF每次都是获取全部行
注:窗口大小默认为100(可自定义),如果数据为101行,那么此时超出了窗口限制,索引值最小的行会被"刷入"磁盘,SXSSF会自动分配临时文件,这些临时文件需要手动清除,清除的方式是使用dospose()方法
演示:写法其实和XSSF类似,只需要改一下对象和清除临时文件
//开始时间 long start = System.currentTimeMillis(); //1.创建工作簿 Workbook workbook = new SXSSFWorkbook(); //2.创建表 Sheet sheet = workbook.createSheet("大数据量写入"); //3.批量写入数据 for(int rownum =0;rownum<65536;rownum++){ Row row = sheet.createRow(rownum); for(int cellnum=0;cellnum<20;cellnum++){ Cell cell = row.createCell(cellnum); cell.setCellValue(cellnum+1); } } //4.写文件 FileOutputStream fileOutputStream = new FileOutputStream("./大数据量写入.xls"); workbook.write(fileOutputStream); //5.释放 fileOutputStream.close(); //结束时间 long end = System.currentTimeMillis(); System.out.println(end-start);
耗时:2360(比XSSF快了几倍)
3. POI的Execl数据读取
3.1 POI简单读取数据
读取和写入的对象都是一样的:
- 工作簿(WorkBoot):所有的Excel工作簿的操作通过此类型都可以来进行设置
- 03 HSSF
- 07 XSSF
- 数据表(Sheet):所有关于数据表的操作通过Sheet都可以进行设置
- 行(Row)
- 列(Cell)
读取步骤:利用文件流进行读取
- 获取工作簿
- 获取表
- 获取行
- 获取单元格
- 获取数据
workbook API:
返回值
|
方法
|
说明
|
Sheet
|
getSheet(String name)
|
通过名称获取工作表对象
|
Sheet
|
getSheetAt(int index)
|
通过索引获取工作表对象
|
int
|
getNumberOfNames()
|
获取工作簿中的电子表格数量
|
String
|
getSheetName(int sheet)
|
获取工作表的名称
|
Sheet API:
返回值
|
方法
|
说明
|
Row
|
getRow()
|
按照索引获取Row对象
|
int
|
getPhysicalNumberOfRows()
|
返回物理定义的行数
|
boolean
|
getProtect()
|
回答是启用还是禁用
|
返回值 | 方法 | 说明 |
Cell | getCell(int cellnum) | 通过索引获取指导单元格 |
short | getLastCellNum() | 获取此行中最后一个单元格的索引 |
int | getPhysicalNumberOfCells() | 获取定义的单元格数 |
int | getRowNum() | 获取此行表示的行号 |
Cell API:
返回值 | 方法 | 说明 |
int | getRowIndex() | 返回工作表包含此单元格的行索引 |
Sheet | getSheet() | 返回此单元格所属的工作表 |
String | getStringCellValue() | 以字符串形式获取单元格的值 |
double | getNumericCellValue() | 以数字形式获取单元格的值 |
Date | getDateCellValue() | 以日期格式获取单元格的值 |
Celltype | getCellType() | 获取所有单元格的数据类型 |
演示:
//1.获取Excel文件流 File file = new File("G:\\Java-Dome\\apachePoi-dome\\bk.xlsx"); FileInputStream fileInputStream = new FileInputStream(file); //2.获取工作簿 Workbook workbook = new XSSFWorkbook(fileInputStream); //3.获取表(通过下标进行获取也可以通过表名来获取) Sheet sheet = workbook.getSheetAt(0); //4.获取行(通过下标进行获取第一行) Row row = sheet.getRow(1); //5.获取单元格(通过下标获取低一个单元格) Cell cell = row.getCell(0); //6.读取数据(需要注意不同数据类型) String stringCellValue = cell.getStringCellValue(); System.out.println(stringCellValue); //7.关闭源 fileInputStream.close();
3.2 POI批量读取数据
批量的Excel数据读取就是一次性获取所有的Excel数据,在操作层面主要分两步:
- 读取第一行的Excel标题内容
- 读取数据内容
Excel操作表:
演示批量读取:
//1.获取Excel文件流 File file = new File("G:\\Java-Dome\\apachePoi-dome\\bk.xlsx"); FileInputStream fileInputStream = new FileInputStream(file); //2.获取工作簿 Workbook workbook = new XSSFWorkbook(fileInputStream); //3.获取表 Sheet sheet = workbook.getSheetAt(0); //4.获取标题内容(获取表中的第一行的表头数据) Row title = sheet.getRow(0); //非空判断 if(title!=null){ //获取标题的单元格,用于遍历获取所有单元格 int cellNum = title.getPhysicalNumberOfCells(); //遍历获取标题内容 for (int i=0;i<cellNum;i++){ //获取单元格 Cell cell = title.getCell(i); //单元格非空 if(cell!=null){ String stringCellValue = cell.getStringCellValue(); System.out.print(stringCellValue+"\t"); } } } //打印换行 System.out.println(); /*5.获取表数据 获取所有表中的所有行数*/ int physicalNumberOfRows = sheet.getPhysicalNumberOfRows(); //循环遍历行 for(int i=1;i<physicalNumberOfRows;i++){ //获取行对象 Row rowData = sheet.getRow(i); //获取行中的所有表格数据数 int physicalNumberOfCells = rowData.getPhysicalNumberOfCells(); //循环遍历表格数据 for(int a=0;a<physicalNumberOfCells;a++){ //获取表格对象 Cell cell = rowData.getCell(a); //获取表格内的数据类型 CellType cellType = cell.getCellType(); //类型判断 switch (cellType){ case STRING://字符类型 //获取字符串值 String stringCellValue = cell.getStringCellValue(); System.out.print(stringCellValue+"\t"); break; case NUMERIC://数值类型 //判断是否为日期类型 if(DateUtil.isCellDateFormatted(cell)){ //获取日期类型值 Date dateCellValue = cell.getDateCellValue(); String formatDate = new SimpleDateFormat("yyyy-MM-dd").format(dateCellValue); System.out.print(formatDate+"\t"); }else { //获取数字类型值 double numericCellValue = cell.getNumericCellValue(); System.out.print(numericCellValue+"\t"); } break; } } //数据换行 System.out.println(); }
3.3 POI封装读取数据
在使用POI读取Excel文件时,一般都会解析封装到具体的实体类中,通过实体类操作
使用自定义封装读取数据时涉及到反射,通过反射获取类中的属性并进行类型判断和赋值操作,最终将数据注入实体类然后添加到list
Excel操作表:
演示:读取数据进行封装
① Entity实体类
public class ExamEntity { //考试时间 Date examTime; //教室名称 String classRoomName; //课程名称 String courseName; //get/set方法省略 @Override public String toString() { return "ExamEntity{" + "examTime=" + examTime + ", classRoomName='" + classRoomName + '\'' + ", courseName='" + courseName + '\'' + '}'; } }
public class excelUtil { //获取表头数据 public <T> List<T> getExcelHead(FileInputStream fileInputStream, Class<ExamEntity> tClass) throws IOException { //存储实体类数据集合 ArrayList<T> result = new ArrayList<>(); //获取Workbook Workbook workbook = new XSSFWorkbook(fileInputStream); //通过下标获取第一张表 Sheet sheetAt = workbook.getSheetAt(0); //获取第一行 Row row = sheetAt.getRow(1); //创建list用于存储表头数据 ArrayList<String> arrayList = new ArrayList<>(); //遍历row中的头数据 for(Cell cell:row){ //cell不为空放入集合中 arrayList.add(cell.getStringCellValue()); } //获取具体数据 for(int a=2;a<sheetAt.getPhysicalNumberOfRows();a++){ //获取具体数据行 Row row1 = sheetAt.getRow(a); //row行不为空 if(row1!=null){ //标记 int j =0; //存储数据map HashMap<String,String> map = new HashMap<>(); //遍历单元格数据 for(Cell cell:row1){ //获取单元格数据 map.put(arrayList.get(j),getExcelData(cell)); j++; } //将map转换成为实体类 T t = (T) mapToEntity(map, tClass); result.add(t); } } return result; } //cell数值类型格式化对象 public static NumberFormat nf = NumberFormat.getNumberInstance(); static { //去掉小数点 nf.setGroupingUsed(false); } //获取具体数据 public String getExcelData(Cell cell) throws IOException { //获取表格内的数据类型 CellType cellType = cell.getCellType(); //值 String value = ""; //类型判断 switch (cellType){ case STRING://字符类型 //获取字符串值 value = cell.getStringCellValue(); break; case NUMERIC://数值类型 value = nf.format(cell.getNumericCellValue()); break; } return value; } //map转换成实体类 private <T> T mapToEntity(Map<String,String> map, Class<T> entity){ T t = null; try{ t = entity.newInstance(); //遍历获取类中的属性 for(Field field:entity.getDeclaredFields()){ if(map.containsKey(field.getName())){ //属性是否可读写 boolean accessible = field.isAccessible(); //设置可读 field.setAccessible(true); //获取Map中的属性对应的值 String str = map.get(field.getName()); //获取实体类属性的类型 String type = field.getGenericType().toString(); //不同类型的判断赋值 if(str!=null){ if(type.equals("class java.lang.String")){ field.set(t,str); }else if(type.equals("class java.lang.Double")){ field.set(t,Double.parseDouble(String.valueOf(str))); }else if(type.equals("class java.util.Date")){ Date date = new SimpleDateFormat("yyyy年MM月dd日 HH:mm-HH:mm").parse(str); field.set(t,date); } } field.setAccessible(!accessible); } } } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); } catch (ParseException e) { e.printStackTrace(); } return t; } }
③ main测试类
public static void main(String[] args) throws IOException { FileInputStream fileInputStream = new FileInputStream(new File("G:\\Java-Dome\\apachePoi-dome\\bk.xlsx")); excelUtil excelUtil = new excelUtil(); List<ExamEntity> excelHead = excelUtil.getExcelHead(fileInputStream, ExamEntity.class); System.out.println(excelHead.toString()); }
结果:
Comments | NOTHING
Warning: Undefined variable $return_smiles in /www/wwwroot/wql_luoqin_ltd/wp-content/themes/Sakura/functions.php on line 1109