Apache POI
Apache POI是Apache软件基金会的开源项目,POI提供API给Java程序对Microsoft Office格式档案读和写的功能。
依赖
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.2</version>
</dependency>
<!-- xls-07-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.2</version>
</dependency>
<!-- 日期格式化工具-->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10.4</version>
</dependency>
批量数据写入
-
03版本HSSF:
最多支持65536行数据的写入,超出时抛异常
操作方式:先将所有数据放入缓存,最后一次性写入磁盘,速度快
-
07+版本XSSF:
写入数据满,获取全部行的数据,消耗大量内存,数据量庞大时有可能发生内存溢出
可以写入较大的数据
//03写入
public void writeBatchData03() throws IOException {
//开始时间
long start = System.currentTimeMillis();
//创建工作表
Workbook workbook = new HSSFWorkbook();
Sheet sheet = workbook.createSheet("03");
//写入数据
for (int rowNum = 0;rowNum<=65535;rowNum++){
Row row = sheet.createRow(rowNum);
for(int cellNum = 0;cellNum<20;cellNum++){
Cell cell = row.createCell(cellNum);
cell.setCellValue(cellNum+1);
}
}
//生成表
FileOutputStream outputStream = new FileOutputStream("./03BatchData.xls");
workbook.write(outputStream);
System.out.println("表格生成完成");
//结束时间
long end = System.currentTimeMillis();
System.out.println("生成时间:"+(end-start)/1000+"s");//秒
}
//07写入
public void writeBatchData07() throws IOException {
//开始时间
long start = System.currentTimeMillis();
//创建工作表
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("03");
//写入数据
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);
}
}
//生成表
FileOutputStream outputStream = new FileOutputStream("./07BatchData.xlsx");
workbook.write(outputStream);
System.out.println("表格生成完成");
//结束时间
long end = System.currentTimeMillis();
System.out.println("生成时间:"+(end-start)/1000+"s");//秒
}
POI操作Excel读
具体步骤:
利用文件流来进行读取
1.获取工作簿
2.获取表
3.获取行
4.获取单元格
5.读取数据
public void readExcel03() throws IOException {
//1.通过文件流读取Excel工作簿
FileInputStream inputStream = new FileInputStream("D:\\myfiles\\办公室\\加分模板\\11月21日前加分\\软件学院2023-2024学年第1学期11月劳动学时统计表.xls");
//2.获取工作簿
Workbook workbook = new HSSFWorkbook(inputStream);
//3.获取表(通过下标的方式)
Sheet sheet = workbook.getSheetAt(0);
//4.获取行
Row row = sheet.getRow(0);
Cell cell = row.getCell(0);
//6.读取数据
String data = cell.getStringCellValue();
System.out.println(data);
//7.关闭流
inputStream.close();
}
public void readExcel07() throws IOException {
//1.通过文件流读取Excel工作簿
FileInputStream inputStream = new FileInputStream("D:\\myfiles\\办公室\\加分模板\\11月21日前加分\\(D)软件学院2023年11月“校园反诈宣传活动”参与人员.xlsx");
//2.获取工作簿
Workbook workbook = new XSSFWorkbook(inputStream);
//3.获取表(通过下标的方式)
Sheet sheet = workbook.getSheetAt(0);
//4.获取行
Row row = sheet.getRow(0);
Cell cell = row.getCell(0);
//6.读取数据
String data = cell.getStringCellValue();
System.out.println(data);
//7.关闭流
inputStream.close();
}
批量读取数据
首先读取标题部分,然后再来读取具体数据部分,两步分开!
public void readExcelCellType() throws IOException {
//1.通过文件流读取Excel工作簿
FileInputStream inputStream = new FileInputStream("D:\\myfiles\\临时文件\\用户.xlsx");
//2.获取工作簿
Workbook workbook = new XSSFWorkbook(inputStream);
//3.获取表(通过下标的方式)
Sheet sheet = workbook.getSheetAt(0);
//获取标题内容
//获取表中的第一行的数据
Row title = sheet.getRow(0);
//非空判断
if(title!=null){
//获取标题的单元格数量,用于遍历,获取所有单元格
int cellNum = title.getPhysicalNumberOfCells(); //所有单元格的数量
System.out.println(cellNum);
//遍历第一行所有单元格 只适用于所有单元格是同一种数据类型的时候
for (int i = 0; i < cellNum; i++) {
Cell cell= title.getCell(i);
if(cell!=null){
//获取单元格中的数据内容
String value = cell.getStringCellValue();
System.out.println(value);
}
}
}
//获取标题以下的具体内容
//获取一共多少行数据
int rowNum = sheet.getPhysicalNumberOfRows();
//读取时跳过第一行表头的数据
for (int i = 1; i < rowNum; i++) {
Row row = sheet.getRow(i);//获取每一行数据
if(row!=null){
//获取每一行里面有多少单元格
int cellNum = row.getPhysicalNumberOfCells();
//遍历每一行单元格里的数据
for (int j = 0; j < cellNum; j++) {
Cell cell = row.getCell(j);
if(cell!=null){
//每个单元格里的数据类型不同
// String val = cell.getStringCellValue();//会出问题
//拿到数据先判断数据类型
CellType cellType = cell.getCellType();
//读取出的单元格数据最后都会存成String数据
String cellval="";
//根据不同的类型读取数据
switch (cellType){
case STRING://字符串
cellval = cell.getStringCellValue();
break;
case NUMERIC://数值类型
//判断是否为日期
if(DateUtil.isCellDateFormatted(cell)){
// System.out.println("当前单元格是日期类型");
Date date = cell.getDateCellValue();
cellval = new SimpleDateFormat("yyyy-MM-dd").format(date);
}else {
//普通数值类型直接转String
cellval = cell.toString();
}
break;
case BLANK:
System.out.println("空白字符");
break;
case BOOLEAN://布尔类型
cellval = String.valueOf(cell.getBooleanCellValue());
System.out.println(cellval);
break;
case ERROR:
System.out.println("该单元格格式错误");
break;
}
System.out.println(cellval);
}
}
}
}
inputStream.close();
}
ExcelUtil封装
封装一个工具类
思路:
1.目的是将表格中的数据转换成实体类
2.实体类中字段和表格中的每一列应该如何映射?
一个简单的方法:
如图,可以在创建表时,在第二行增加一列与数据的字段相对应,在读出第二行数据时,将其存入map中的key
我们可以将第二行隐藏 ,不影响表格数据,同时也能读出数据
import org.apache.poi.ss.usermodel.*;
import java.io.FileInputStream;
import java.lang.reflect.Field;
import java.text.NumberFormat;
import java.text.SimpleDateFormat;
import java.util.*;
/**
* 解析Excel表格
* 用户只需传入对应版本的WorkBook对象,需要匹配版本 文件的输入流 对应实体类class
* 得到解析表格以后的结果同时通过传入的实体类类型集合方式返回
*/
public class ExcelUtil {
public static <T> List<T> readExcel(Workbook workbook, FileInputStream inputStream ,Class<T> clazz)throws Exception{
//给用户返回的实体类集合
List<T> result = new ArrayList<>();
//在工作簿中获取目标工作表
//如果有多张工作表则遍历(目前默认只有一张表)
Sheet sheet = workbook.getSheetAt(0);
//获取工作表中的行数
int rowNum = sheet.getPhysicalNumberOfRows();
//拿到第一行被隐藏的数据
Row row = sheet.getRow(1);
//遍历第一行数据,遍历出的数据就是当前实体类对应的所有属性,把数据放入map中的key
List<String> key = new ArrayList<>();
//开始遍历
for (Cell cell: row) {
if(cell!=null){
String value = cell.getStringCellValue();
key.add(value);
System.out.println(value);
}
}
//从第三行开始遍历,以下就是正式数据
for (int i = 2; i < rowNum; i++) {
//获取属性以下的数据
row = sheet.getRow(i);
if(row!=null){
//计数器 j 用于映射数据使用
int j = 0;
//用于保存数据的Map,在Map中建立 属性
Map<String,String> excelMap = new HashMap<>();
for (Cell cell:row){
if(cell!=null){
//把所有单元格中的数据设置为String
String value = getCellValue(cell);
if(value!=null && !value.equals("")){
//将每个单元格的数据储存到集合中
excelMap.put(key.get(j),value);
j++;
}
}
}
//map 转换成为实体类 创建对应实体类类型,把读取到的数据转换为实体类对象
T t = mapToEntity(excelMap,clazz);
result.add(t);
}
}
inputStream.close();
return result;
}
public static NumberFormat nf = NumberFormat.getNumberInstance();
static{
nf.setGroupingUsed(false);
}
//用于将单元格格式转换为String的方法
public static String getCellValue(Cell cell){
String cellval="";
if(cell!=null){
//每个单元格里的数据类型不同
// String val = cell.getStringCellValue();//会出问题
//拿到数据先判断数据类型
//所有读取到的数据的类型
CellType cellType = cell.getCellType();
//读取出的单元格数据最后都会存成String数据
//根据不同的类型读取数据
switch (cellType){
case STRING://字符串
cellval = cell.getStringCellValue();
// System.out.println(cellval);
break;
case NUMERIC://数值类型
//判断是否为日期
if(DateUtil.isCellDateFormatted(cell)){
// System.out.println("当前单元格是日期类型");
Date date = cell.getDateCellValue();
cellval = new SimpleDateFormat("yyyy-MM-dd").format(date);
}else {
//普通数值类型直接转String
cellval = nf.format(cell.getNumericCellValue());
}
break;
case BLANK:
// System.out.println("空白字符");
break;
case BOOLEAN://布尔类型
cellval = String.valueOf(cell.getBooleanCellValue());
System.out.println(cellval);
break;
case ERROR:
// System.out.println("该单元格格式错误");
break;
}
System.out.println(cellval);
}
return cellval;
}
private static <T> T mapToEntity(Map<String,String> map,Class<T> entity) throws InstantiationException, IllegalAccessException {
T t = null;
try{
t = entity.newInstance();
for(Field field:entity.getDeclaredFields()){
//map中是否包含拿到的属性名称
if(map.containsKey(field.getName())){
boolean flag = field.isAccessible();
field.setAccessible(true);
//获取Map中的属性对应的值
String str = map.get(field.getName()); //key值
//获取实体类属性的类型
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.lang.Integer")) {
field.set(t,Integer.parseInt(String.valueOf(str)));
} else if (type.equals("class java.util.Date")) {
Date date = new SimpleDateFormat("yyyy-MM-dd").parse(str);
field.set(t,date);
}
}
field.setAccessible(flag);
}
}
return t;
}catch (Exception e){
e.printStackTrace();
}
return t;
}
}
CellType定义了不同的枚举类型,来作为表格数据的接收类型
_NONE
未知类型NUMERIC
数值类型 包含整数、小数、日期!STRING
字符串FORMULA
公式BLANK
空字符串 没有值,但是有单元格样式BOOLEAN
布尔值ERROR
错误单元格
所以为了统一将拿出来的数据用String 来接收,在取数据时需要做一个数据转换。
测试
新建一个接收的实体类ExcelUser
@Data
public class ExcelUser {
private String userName;
private Integer sex;
private String phone;
}
public class ExcelReadTest {
public static void main(String[] args) {
try{
new ExcelReadTest().getEntity();
}catch (Exception e){
e.printStackTrace();;
}
}
public void getEntity() throws Exception {
FileInputStream fileInputStream = new FileInputStream("D:\\myfiles\\临时文件\\用户07.xlsx");
Workbook workbook = new XSSFWorkbook(fileInputStream);
List<ExcelUser> list = ExcelUtil.readExcel(workbook,fileInputStream, ExcelUser.class);
System.out.println(list);
}
}