项目需求需要读取excel,接触到poi,使用的话也入了很多坑,自我总结一下
这里呢写了小demo,是一个SpringBoot项目,主要功能就是读取一个总课表,然后生成我们需要的指定班级的课表
先来看看我们的总课表的样子
easyexcel
对于excel的数据量比较大的时候,速度有着明显优势
关键代码:
导包:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.1.1</version>
</dependency>
<!--xls-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.17</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.17</version>
</dependency>
数据实体类(给需要参与导入导出的属性添加@ExcelProperty注解,其中value属性的值代表要导出的时候的列名,index的值是列的下标,从0开始,这个课表上午和下午中间有间隔,所以为了能准确读取到,这里必须设置):
import com.alibaba.excel.annotation.ExcelProperty;
import lombok.Data;
import lombok.experimental.Accessors;
@Data
//@Accessors(chain = true)
public class DemoData {
//设置excel表头名称
@ExcelProperty(value = "日期",index = 0)
private String DateTime;
@ExcelProperty(value = "星期",index = 1)
private String week;
@ExcelProperty(value = "地点",index = 2)
private String AMClassroom;
@ExcelProperty(value = "1,2课时",index = 3)
private String hourOneAndTwo;
@ExcelProperty(value = "3,4课时",index = 4)
private String hourThreeAndFour;
@ExcelProperty(value = "教员",index = 5)
private String AMTeacher;
@ExcelProperty(value = "班级",index = 6)
private String AMGrade;
@ExcelProperty(value = "地点",index = 8)
private String PMClassroom;
@ExcelProperty(value = "5,6课时",index = 9)
private String hourFiveAndSix;
@ExcelProperty(value = "7,8课时",index = 10)
private String hourSevenAndEight;
@ExcelProperty(value = "教员",index = 11)
private String PMTeacher;
@ExcelProperty(value = "班级",index = 12)
private String PMGrade;
@ExcelProperty(value = "晚(18:30-20:10)",index = 13)
private String night;
}
创建excel事件分析器:
import javax.swing.filechooser.FileSystemView;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ExcelListener extends AnalysisEventListener<DemoData> {
//要打印的班级的课表的集合
private List<DemoData> list = new ArrayList<>();
//第一行必保存的标识,是列头
private boolean flag = true;
//要生成课表的课表中的班级名称
public static String grade;
//表头
private String head;
//星期暂存
private String week;
//该班级晚自习教室暂存
private Map<String,String> night=new HashMap<>();
//读取表头内容
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
//表头,这里课表的表头就一个
System.out.println("表头:" + headMap);
head = headMap.get(0).substring(0, headMap.get(0).length() - 3);
}
//一行一行读取excel内容
@Override
public void invoke(DemoData data, AnalysisContext analysisContext) {
System.out.println("****" + data);
//列头的读取
if ((!StringUtils.isEmpty(data.getWeek())) && flag) {
list.add(data);
flag = false;
return;
}
//星期的暂存和数据中星期的赋值
if (StringUtils.isEmpty(data.getWeek())) {
data.setWeek(week);
} else {
week = data.getWeek();
}
//读取这一周的这一天的这个班级的晚自习教室
if (!StringUtils.isEmpty(data.getNight())) {
if (data.getNight().contains(grade) || grade.contains(data.getNight())) {
night.put(data.getWeek(), data.getAMClassroom());
}
}
data.setNight(null);//读取完之后就没有必要存在了,到整体读取完的方法中再整体填充
//只去找我们需要的班级的课表
if (grade.equals(data.getAMGrade()) || grade.equals(data.getPMGrade())) {
//半天半天的区分,如果这一天有这个班级,但是某个半天不是这个班级,就直接清空这半天
if (!grade.equals(data.getAMGrade())) {
data.setAMClassroom(null);
data.setHourOneAndTwo(null);
data.setHourThreeAndFour(null);
data.setAMTeacher(null);
data.setAMGrade(null);
}
if (!grade.equals(data.getPMGrade())) {
data.setPMClassroom(null);
data.setHourFiveAndSix(null);
data.setHourSevenAndEight(null);
data.setPMTeacher(null);
data.setPMGrade(null);
}
//如果和上一条数据的日期相同,那么和上条数据进行合并
if (list.get(list.size()-1).getWeek().equals(data.getWeek())) {
DemoData oldData = list.get(list.size() - 1);//合并的目标数据
//合并到上午
if (StringUtils.isEmpty(oldData.getAMGrade())) {
oldData.setAMClassroom(data.getAMClassroom());
oldData.setHourOneAndTwo(data.getHourOneAndTwo());
oldData.setHourThreeAndFour(data.getHourThreeAndFour());
oldData.setAMTeacher(data.getAMTeacher());
oldData.setAMGrade(data.getAMGrade());
}else {//合并到下午
oldData.setPMClassroom(data.getPMClassroom());
oldData.setHourFiveAndSix(data.getHourFiveAndSix());
oldData.setHourSevenAndEight(data.getHourSevenAndEight());
oldData.setPMTeacher(data.getPMTeacher());
oldData.setPMGrade(data.getPMGrade());
}
list.set(list.size() - 1, oldData);
return;
}
list.add(data);
}
}
//读取完成之后
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
//读完之后开始写入
// 获取桌面路径
FileSystemView fsv = FileSystemView.getFileSystemView();
String desktop = fsv.getHomeDirectory().getPath();
String filePath = desktop + "\\" + grade + " " + head + ".xls";//C:\Users\fuck\Desktop\T119开9.13-9.18.xls
//填充晚自习
for (String wk : night.keySet()) {
for (DemoData data : list) {
if (data.getWeek().equals(wk)) {
data.setNight(night.get(wk));
}
}
}
System.out.println(filePath);
list.forEach(list -> System.out.println(list));
//写入
EasyExcel.write(filePath, DemoData.class).sheet("学生列表").doWrite(list);
}
}
总结:excel文件表头的读取,常规行的读取,excel文件的写入都是在这个类中重写的方法中实现,读取数据的特点:一行一行的读取
excelpoi
实现方式
导入的依赖改动,使用的注解也改动了
需要指定列名,和总课表中的列头名字需要一模一样,很难受
而且不知道什么原因,读取不到某些列的数据,使用模板来进行生成的话,如果有一对多的关系,牵扯到单元格的合并,最后导出的表格连打开都报错,最后放弃了,这里就省略了,抱歉
poi
最原生的方式,自由度高,对于这种比较复杂的表格,应该是最合适的选择
导入依赖:
<!-- poi -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.17</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.17</version>
</dependency>
读取(思路:获取工作表,获取sheet,遍历sheet的row和cell):
/**
* 读取总课表
*
* @param schedule
* @param list
* @throws Exception
*/
public String timeTableRead(Schedule schedule, List<ScheduleVo> list) throws Exception {
//读取
if (!ObjectUtils.isEmpty(schedule.getMultipartFile())) {
//某一天的课程
ScheduleVo scheduleVo = new ScheduleVo();
//文件名
String fileName = "";
//MultipartFile 转 inputStream
InputStream inputStream = schedule.getMultipartFile().getInputStream();
Workbook sheets = new XSSFWorkbook(inputStream);//创建工作表格
//获取一个工作表(sheet页),下标从0开始
Sheet sheet = sheets.getSheetAt(0);
//遍历行,sheet,row,cell的下标都是从0开始,行数313,sheet.getLastRowNum()也的得到313,所以i可以等于rowNum
for (int i = 0, rowNum = sheet.getLastRowNum(); i <= rowNum; i++) {
// 获取行内容
Row row = sheet.getRow(i);
if (ObjectUtils.isEmpty(row)) {
//如果知识单纯有一行为null就继续下一行的读取
continue;
}
//防止行缺失列 行!=null,但是读取指定列却空指针,有row缺失cell
if (row.getPhysicalNumberOfCells() < 13) {
continue;
}
//读取列
//读取表头
if (i == 0) {
String title = row.getCell(0).getStringCellValue();
fileName = schedule.getName() + " " + title.substring(0, title.length() - 3) + "课表";
} else if (i > 1) {
//第二行列头没有什么好读的,直接第三行开始读取
//如果有时间,代表这是新的一天的开始,就把课表对象重写实例化
String dateTime = row.getCell(0).getStringCellValue();
if (!StringUtils.isEmpty(dateTime)) {
if (!StringUtils.isEmpty(scheduleVo.getDateTime())) {//新的一天开始把旧的一天读到的数据添加到集合中
list.add(scheduleVo);
}
scheduleVo = new ScheduleVo();
scheduleVo.setDateTime(dateTime.substring(5));//设置时间
scheduleVo.setWeek(row.getCell(1).getStringCellValue());//设置星期
}
//如果第7列是要搜索的班级就把这一行中指定列的数据添加到对象中
String clazz = row.getCell(6).getStringCellValue();
if (clazz.equals(schedule.getName())) {
Cell cell = row.getCell(2);
cell.setCellType(CellType.STRING);
scheduleVo.setAmClassroom(cell.getStringCellValue());//读取上午的地点
scheduleVo.setAmCourse(row.getCell(3).getStringCellValue());//读取上午的课程
scheduleVo.setAmTeacher(row.getCell(5).getStringCellValue());//读取上午的教员
}
//如果第13列是要搜索的班级就把这一行中指定列的数据添加到对象中
clazz = row.getCell(12).getStringCellValue();
if (clazz.equals(schedule.getName())) {
Cell cell = row.getCell(8);
cell.setCellType(CellType.STRING);
scheduleVo.setPmClassroom(cell.getStringCellValue());//读取下午的地点
scheduleVo.setPmCourse(row.getCell(9).getStringCellValue());//读取下午的课程
scheduleVo.setPmTeacher(row.getCell(11).getStringCellValue());//读取下午的教员
}
//如果第14列是要搜索的班级就把这一行中指定列的数据添加到对象中
clazz = row.getCell(13).getStringCellValue();
if ((!StringUtils.isEmpty(clazz)) && schedule.getName().contains(clazz)) {//这天有晚自习
Cell cell = row.getCell(2);
cell.setCellType(CellType.STRING);
scheduleVo.setNight(cell.getStringCellValue());//保存晚自习地点到晚自习字段中
}
//如果是最后一行了,读取完之后就直接添加到集合中
if (i == rowNum) {
list.add(scheduleVo);
}
}
}
//关流
sheets.close();
inputStream.close();
return fileName;
} else {
throw new Exception("表格为空,请选择表格!");
}
}
注意:像这种比较复杂的表格的读取,需要注意,读取到的row对象可能缺少列,正常是这样:
表格中是这样的,本来是隔开周一周二教室安排为了美观:
但是我遇到的表格不知道表格进行了什么操作,竟然导致缺少cell
这是遇到的一个坑,不知产生的原因,有知道的大佬欢迎指教。
暂时解决办法判断row的cell数,如果缺斤少两就读取下一行:
//防止行缺失列 行!=null,但是读取指定列却空指针,有row缺失cell
if (row.getPhysicalNumberOfCells() < 13) {
continue;
}
写入(思路:创建工作表,创建sheet,创建sheet的row,创建row的cell):
/**
* 在桌面生成课表
*
* @param schedule
* @param list
*/
public void timeTableWrite(Schedule schedule, List<ScheduleVo> list) throws IOException {
//创建工作簿 类似于创建Excel文件
HSSFWorkbook workbook = new HSSFWorkbook();
//创建 sheetname页名 直接用的课表集合的时间做一个拼接做页名
HSSFSheet sheet = workbook.createSheet(list.get(0).getDateTime() + "-" + list.get(list.size() - 1).getDateTime());
//创建标题行,下标从0开始
HSSFRow row = sheet.createRow(0);
//单元格的合并————合并列(合并并不需要创建行或列作为前提)
CellRangeAddress region = new CellRangeAddress(0, 0, 0, 9);
sheet.addMergedRegion(region);
//创建这行中的列,并给该列直接赋值,标题行就只有一列
row.createCell(0).setCellValue(schedule.getName() + " 课表");
//但是设置单元格样式需要单元格创建了作为前提,否则报错
for (int i = 1; i <= 9; i++) {
row.createCell(i);
}
//创建课表标题行的样式
CellStyle cellStyle = workbook.createCellStyle();
cellStyle.setAlignment(HorizontalAlignment.CENTER);//水平居中
cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);//垂直居中
cellStyle.setBorderBottom(BorderStyle.THIN);//上下左右一个个设置边框,细线
cellStyle.setBorderLeft(BorderStyle.THIN);
cellStyle.setBorderTop(BorderStyle.THIN);
cellStyle.setBorderRight(BorderStyle.THIN);//单元格的边框颜色默认黑色
//设置背景颜色
// cellStyle.setFillForegroundColor(IndexedColors.BLUE1.getIndex());
// cellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);//填充模式,如果不设置会导致背景色上不去
//给列添加样式,不单单需要第一个单元格需要边框,参与合并的所有单元格都需要
for (Cell cell : row) {
cell.setCellStyle(cellStyle);
}
//创建列头行
row = sheet.createRow(1);
row.createCell(0).setCellValue("日期");
row.createCell(1).setCellValue("星期");
row.createCell(2).setCellValue("地点");
row.createCell(3).setCellValue("上午");
row.createCell(4).setCellValue("教员");
row.createCell(5).setCellValue("");
row.createCell(6).setCellValue("地点");
row.createCell(7).setCellValue("下午");
row.createCell(8).setCellValue("教员");
row.createCell(9).setCellValue("晚自习教室");
//设置列头样式
cellStyle = workbook.createCellStyle();
cellStyle.setAlignment(HorizontalAlignment.CENTER);//水平居中
cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);//垂直居中
cellStyle.setBorderBottom(BorderStyle.THIN);//上下左右一个个设置边框,细线
cellStyle.setBorderLeft(BorderStyle.THIN);
cellStyle.setBorderTop(BorderStyle.THIN);
cellStyle.setBorderRight(BorderStyle.THIN);//单元格的边框颜色默认黑色
//给列添加样式,不单单需要第一个单元格需要边框,参与合并的所有单元格都需要
for (Cell cell : row) {
cell.setCellStyle(cellStyle);
}
//开始遍历课表信息集合来创建列
for (int i = 0; i < list.size(); i++) {
row = sheet.createRow(i + 2);//标题行为0,列头为1,所以i+2
row.createCell(0).setCellValue(list.get(i).getDateTime());
row.createCell(1).setCellValue(list.get(i).getWeek());
row.createCell(2).setCellValue(list.get(i).getAmClassroom());
row.createCell(3).setCellValue(list.get(i).getAmCourse());
row.createCell(4).setCellValue(list.get(i).getAmTeacher());
//如果是第一次遍历课程集合,就需要合并行,也就是课表中的午休列
if (i == 0) {
region = new CellRangeAddress(2, list.size() + 1, 5, 5);
sheet.addMergedRegion(region);
row.createCell(5).setCellValue("午休");
}
row.createCell(6).setCellValue(list.get(i).getPmClassroom());
row.createCell(7).setCellValue(list.get(i).getPmCourse());
row.createCell(8).setCellValue(list.get(i).getPmTeacher());
row.createCell(9).setCellValue(list.get(i).getNight());
//添加特殊样式,遍历列,不同的列需要设置不同的样式
//样式,所有列有的公共样式
cellStyle = workbook.createCellStyle();
cellStyle.setAlignment(HorizontalAlignment.CENTER);//水平居中
cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);//垂直居中
cellStyle.setBorderBottom(BorderStyle.THIN);//上下左右一个个设置边框,细线
cellStyle.setBorderLeft(BorderStyle.THIN);
cellStyle.setBorderTop(BorderStyle.THIN);
cellStyle.setBorderRight(BorderStyle.THIN);//单元格的边框颜色默认黑色
for (Cell cell : row) {
cell.setCellStyle(cellStyle);
}
}
//设定 路径
// 获取桌面路径
FileSystemView fsv = FileSystemView.getFileSystemView();
String desktop = fsv.getHomeDirectory().getPath();
String filePath = desktop + "/" + fileName + ".xls";
FileOutputStream stream = new FileOutputStream(new File(filePath));
// 写入工作表
workbook.write(stream);
//关流
stream.close();
}
}
最后的效果: