POI和EasyExcel
POI和easyExcel
场景:
1、将用户信息导出为excel表格
2、将excel表信息录入到网上数据库(习题上传),大大减轻录入量
内存问题:
poi=先把所有数据加载到内存再写入
easyExcel=一条一条写
excel元素
1、工作簿
2、工作表
3、行
4、列
1、POI方式实现
POI-Excel写
POI依赖
<!-- xls(03)-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.9</version>
</dependency>
<!-- xls(07)-->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.9</version>
</dependency>
<!-- 日期格式化工具-->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10.1</version>
</dependency>
03版本代码HSSF
//03版本导出
@Test
public void testWrite03() throws Exception {
String PATH="D:\\ideaProtect\\excel\\src\\main\\java\\com\\lim";
//1、创建工作簿03版
Workbook workbook=new HSSFWorkbook();
//2、创建一个工作表
Sheet sheet=workbook.createSheet("单选题");
//3、创建一个行(1,1)
Row row1=sheet.createRow(0);
//3、创建一个单元格
Cell cell11=row1.createCell(0);
cell11.setCellValue("题目内容");
...
//生成一张表(IO流)
FileOutputStream fileOutputStream=new FileOutputStream(PATH+"试题导入模板.xls");
workbook.write(fileOutputStream);
//关闭流
fileOutputStream.close();
System.out.println("excel生成完毕");
}
07版本代码XSSF
@Test
public void testWrite07() throws Exception {
String PATH="D:\\ideaProtect\\excel\\src\\main\\java\\com\\lim";
//1、创建工作簿07版
Workbook workbook=new XSSFWorkbook();
//2、创建一个工作表
Sheet sheet=workbook.createSheet("单选题");
//3、创建一个行(1,1)
Row row1=sheet.createRow(0);
//3、创建一个单元格
Cell cell11=row1.createCell(0);
cell11.setCellValue("题目内容");
//生成一张表(IO流)
FileOutputStream fileOutputStream=new FileOutputStream(PATH+"试题导入模板.xlsx");
workbook.write(fileOutputStream);
//关闭流
fileOutputStream.close();
System.out.println("excel生成完毕");
}
03和07的区别
工作簿对象不同,生成文件后缀不同。
数据批量导入
大文件写入HSSF
缺点:最多只能处理65536行,否则会抛出异常
优点:过程中写入缓存,不操作磁盘,最后一次性写入磁盘,速度快
//开始时间
long begin=System.currentTimeMillis();
//1、创建一个工作簿
Workbook workbook=new HSSFWorkbook();
//2、创建一个工作表
Sheet sheet=workbook.createSheet();
for (int i = 0; i < 65536; i++) {
//3、创建行
Row row1=sheet.createRow(i);
for (int j = 0; j < 10; j++) {
//4、创建单元格
Cell cell11=row1.createCell(j);
cell11.setCellValue(j);
}
}
//结束时间
long end=System.currentTimeMillis();
System.out.println("over");
FileOutputStream fileOutputStream=new FileOutputStream(PATH+"testWrite03BigData.xls");
workbook.write(fileOutputStream);
fileOutputStream.close();
double time=(double)end-begin;
System.out.println("time = " + time);
大文件写入XSSF
缺点:写数据是速度非常慢,非常耗内存,也会发生内存溢出,如100万条
有点:可以写入较大的数据量,如20万条
//开始时间
long begin=System.currentTimeMillis();
//1、创建一个工作簿
Workbook workbook=new XSSFWorkbook();
//2、创建一个工作表
Sheet sheet=workbook.createSheet();
for (int i = 0; i < 65536; i++) {
//3、创建行
Row row1=sheet.createRow(i);
for (int j = 0; j < 10; j++) {
//4、创建单元格
Cell cell11=row1.createCell(j);
cell11.setCellValue(j);
}
}
//结束时间
long end=System.currentTimeMillis();
System.out.println("over");
FileOutputStream fileOutputStream=new FileOutputStream(PATH+"testWrite07BigData.xlsx");
workbook.write(fileOutputStream);
fileOutputStream.close();
double time=(double)end-begin;
System.out.println("time = " + time);
相较于HSSF来说,慢了很多,
大文件写入SXSSF
XSSF的升级
优点:可以写非常大的数据量,如100万条甚至更多,写数据速度快占用更少内存
注意:
过程中会产生临时文件,需要清理临时文件
默认由100条记录被保存在内存中,如果超过这数量,则最前面的数据被写入临时文件
如果想自定义内存中数据的数量,可以使用new SXSSFWorkbook(数量)
public void testWrite07BigDataS() throws IOException {
//开始时间
long begin=System.currentTimeMillis();
//1、创建一个工作簿
Workbook workbook=new SXSSFWorkbook();
//2、创建一个工作表
Sheet sheet=workbook.createSheet();
for (int i = 0; i < 65536; i++) {
//3、创建行
Row row1=sheet.createRow(i);
for (int j = 0; j < 10; j++) {
//4、创建单元格
Cell cell11=row1.createCell(j);
cell11.setCellValue(j);
}
}
//结束时间
long end=System.currentTimeMillis();
System.out.println("over");
FileOutputStream fileOutputStream=new FileOutputStream(PATH+"testWrite07BigDataS.xlsx");
workbook.write(fileOutputStream);
fileOutputStream.close();
//清理临时文件
((SXSSFWorkbook) workbook).dispose();
double time=(double)end-begin;
System.out.println("time = " + time);
}
SXSSFWorkbook来自官方的解释:实现“BigGridDemo”策略的流式XSSFWorkbook版本,这允许写入非常大的文件而不会耗尽内存,因为任何时候只有可配置的行部分被保存在内存中。
请注意,仍然可能会消耗大量内存,这些内存基于您正在使用的功能,例如合并区域,注释......仍然只存储在内存中,因此如果广泛使用,可能需要大量内存。
再使用POI的时候内存问题Jprofile监控。
POI-Excel读
03版本代码HSSF
public void testRead03() throws Exception {
//获取文件流
FileInputStream fileInputStream=new FileInputStream(PATH+"lim试题导入模板.xls");
//1、创建一个工作簿,使Excel能操作的我们这边都能操作
Workbook workbook=new HSSFWorkbook(fileInputStream);
//2、获取一个工作表
Sheet sheet=workbook.getSheetAt(0);
//3、获取行
Row row=sheet.getRow(0);
//4、获取单元格
Cell cell11=row.getCell(0);
System.out.println(cell11.getStringCellValue());
fileInputStream.close();
}
07版本代码XSSF
public void testRead07() throws Exception {
//获取文件流
FileInputStream fileInputStream=new FileInputStream(PATH+"lim试题导入模板.xlsx");
//1、创建一个工作簿,使Excel能操作的我们这边都能操作
Workbook workbook=new XSSFWorkbook(fileInputStream);
//2、获取一个工作表
Sheet sheet=workbook.getSheetAt(0);
//3、获取行
Row row=sheet.getRow(0);
//4、获取单元格
Cell cell11=row.getCell(0);
System.out.println(cell11.getStringCellValue());
fileInputStream.close();
}
类型转换
public void testCellType() throws Exception{
//获取文件流
FileInputStream fileInputStream=new FileInputStream(PATH+"lim试题导入模板.xlsx");
Workbook workbook=new XSSFWorkbook(fileInputStream);
Sheet sheet=workbook.getSheetAt(0);
//获取标题内容,标题行
Row rowTitle=sheet.getRow(0);
if (rowTitle!=null){
//获取列数量
int cellsCount=rowTitle.getPhysicalNumberOfCells();
for (int cellNum=0;cellNum<cellsCount;cellNum++){
Cell cell=rowTitle.getCell(cellNum);
if (cell!=null){
int cellType=cell.getCellType();
String cellValue=cell.getStringCellValue();
System.out.print(cellType+cellValue+"|");
}
}
System.out.println();
}
//获取下面的数据
int rowCount=sheet.getPhysicalNumberOfRows();
for (int rowNum = 1; rowNum <rowCount ; rowNum++) {
Row rowData=sheet.getRow(rowNum);
if (rowData!=null){
int cellsCount=rowTitle.getPhysicalNumberOfCells();
for (int cellNum = 0; cellNum <cellsCount ; cellNum++) {
System.out.print("["+(rowNum+1)+","+(cellNum+1)+"]");
Cell cell=rowData.getCell(cellNum);
if (cell!=null){
int cellType=cell.getCellType();
String cellValue="";
switch (cellType){
case XSSFCell
.CELL_TYPE_STRING://字符串
System.out.print("[String]");
cellValue=cell.getStringCellValue();
break;
case XSSFCell.CELL_TYPE_BOOLEAN://布尔
System.out.print("[boolean]");
cellValue=String.valueOf(cell.getBooleanCellValue());
break;
case XSSFCell.CELL_TYPE_BLANK://空
System.out.print("[blank]");
break;
case XSSFCell.CELL_TYPE_NUMERIC:
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(XSSFCell.CELL_TYPE_STRING);
cellValue=cell.toString();
}
break;
case XSSFCell.CELL_TYPE_ERROR://布尔
System.out.print("[数据类型错误]");
cellValue=String.valueOf(cell.getBooleanCellValue());
break;
}
System.out.println(cellValue);
}
}
System.out.println();
}
}
fileInputStream.close();
}
POI方式小结
2、EasyExcel
依赖
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.0.0-beta2</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.62</version>
</dependency>
</dependencies>
写入
1、新建导入模板类
@Data
public class SingleChoiceQuestion {
//忽略
@ExcelIgnore
private Integer quesId;
@ExcelProperty("题目内容")
private String quesContext;
@ExcelProperty("知识点")
private String knowledge;
...
}
public class easyTest {
String PATH="D:\\exam\\src\\main\\java\\com\\lim\\exam\\";
private List<singleChoiceQuestion> questions(){
List<singleChoiceQuestion> list =new ArrayList<singleChoiceQuestion>();
singleChoiceQuestion singleChoiceQuestion =new singleChoiceQuestion();
singleChoiceQuestion.setQuesContext("1+1=");
singleChoiceQuestion.setKnowledge("数学");
...
list.add(singleChoiceQuestion);
return list;
}
//根据list写入
// @org.junit.Test
public void simpleWrite() {
// 写法1 JDK8+
// since: 3.0.0-beta1
String fileName = PATH+"easyTest.xlsx";
// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
// 如果这里想使用03 则 传入excelType参数即可
//方法一:单个sheet写入
EasyExcel.write(fileName, Question.class).sheet(0,"单选").doWrite(questions());
//方法二:多个sheet写入
ExcelWriter excelWriter = null;
try {
excelWriter = EasyExcel.write(fileName, SingleChoiceQuestion.class).build();
WriteSheet mainSheet = EasyExcel.writerSheet(0, "表1").head(SingleChoiceQuestion.class).build();
excelWriter.write(questions(), mainSheet);
WriteSheet mainSheet1= EasyExcel.writerSheet(1, "表2").head(SingleChoiceQuestion.class).build();
excelWriter.write(questions(), mainSheet1);
} finally {
// 千万别忘记finish 会帮忙关闭流
if (excelWriter != null) {
excelWriter.finish();
}
}
}
}
写出(并插入到数据库)
1、新建一个读取data
@Getter
@Setter
@EqualsAndHashCode
public class JudgeQuestionData {
private String quesContext;
private String knowledge;
...
}
2、监听器
@Slf4j
public class DemoDataListener extends AnalysisEventListener<SingleChoiceQuestionData> {
/**
* 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收
*/
private static final int BATCH_COUNT = 100;
/**
* 缓存的数据
*/
List<SingleChoiceQuestionData> list = new ArrayList<SingleChoiceQuestionData>();
/**
* 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。
*/
private QuestionDAO questionDAO;
public DemoDataListener() {
// 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数
questionDAO = new QuestionDAO();
}
public List<SingleChoiceQuestionData> getDatas() {
return list;
}
public void setDatas(List<SingleChoiceQuestionData> singleChoiceQuestionData) {
this.list = singleChoiceQuestionData;
}
/**
* 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来
*
* @param questionDAO
*/
public DemoDataListener(QuestionDAO questionDAO) {
this.questionDAO = questionDAO;
}
/**
* 这个每一条数据解析都会来调用
*
* @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()}
* @param context
*/
@Override
public void invoke(SingleChoiceQuestionData data, AnalysisContext context) {
System.out.println(JSON.toJSONString(data));
list.add(data);
// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM
if (list.size() >= BATCH_COUNT) {
saveData();
// 存储完成清理 list
list.clear();
}
}
/**
* 所有数据解析完成了 都会来调用
*
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
// 这里也要保存数据,确保最后遗留的数据也存储到数据库
saveData();
log.info("所有数据解析完成!");
}
/**
* 加上存储数据库
*/
private void saveData() {
log.info("{}条数据,开始存储数据库!", list.size());
System.out.println(list);
//questionDAO.save(list);
log.info("存储数据库成功!");
}
}
3、mapper层
@Repository
public interface QuestionMapper extends BaseMapper<Question> {
@Insert("<script>" +
"INSERT INTO Question" +
" (ques_context,knowledge)" +
" VALUES" +
" <foreach collection ='list' item='questions' separator =','>" +
" (#{questions.quesContext}, #{questions.knowledge})" +
" </foreach >" +
"</script>")
void insertQuestion(@Param("list") List<SingleChoiceQuestionData> questions);
}
public void simpleRead(String path) {
// 写法1:JDK8+ ,不用额外写一个DemoDataListener
// since: 3.0.0-beta1
String fileName = path+"easyTest.xlsx";
// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
// 这里每次会读取3000条数据 然后返回过来 直接调用使用数据就行
//EasyExcel.read(fileName, QuestionData.class, new DemoDataListener()).sheet().doRead();
//监视器
DemoDataListener listener = new DemoDataListener();
ExcelReader excelReader = EasyExcel.read(fileName, listener).build();
// 第一个sheet读取类型
ReadSheet readSheet1 = EasyExcel.readSheet(0).head(QuestionData.class).build();
// 第二个sheet读取类型
ReadSheet readSheet2 = EasyExcel.readSheet(1).head(QuestionData.class).build();
// 开始读取第一个sheet
excelReader.read(readSheet1);
//excel sheet0 信息
List<QuestionData> list = listener.getDatas();
System.out.println("questionMapper = " + questionMapper);
System.out.println("userMapper = " + userMapper);
//插入数据库
questionMapper.insertQuestion(list);
// 清空之前的数据
listener.getDatas().clear();
// 开始读取第二个sheet
excelReader.read(readSheet2);
//excel sheet1 信息
List<QuestionData> entry = listener.getDatas();
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!