POI、EasyExcel操作Excel表格详解

POI、EasyExcel

文章目录

    • POI、EasyExcel
      • 1.简介
      • 2.结构
      • 3.POI-Excel写
        • 1.基本操作
        • 2.大文件写入HSSF
        • 3.大文件写入XSSF
        • 4.大文件写入SXSSF
      • 4.POI-Excel读
        • 1.基本操作
        • 2.数据类型
        • 3.计算公式类型数据读取
      • 5.EasyExcel写
        • 1.基本写入操作
        • 2.日期、数字或者自定义格式转换
        • 3.列宽、行高
      • 6.EasyExcel读
        • 1.基本操作
        • 2.指定列的下标或者列名
        • 3.读多个sheet
        • 4.日期、数字或者自定义格式转换
        • 5.读取公式和单元格类型
        • 6.数据转换等异常处理
        • 7.web中的读
      • 7.EasyExcel中常用操作
        • 1.数据批量导入监听
        • 2.导出注解方式复杂表头设置
        • 3.导出动态复杂表头设置
        • 4.导出文件增加序列号(自定义行处理器)
      • 8.Poi工具类

1.简介

Apache POI [1] 是用Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java程序对[Microsoft Office](https://baike.baidu.com/item/Microsoft Office/481476?fromModule=lemma_inlink)格式档案读和写的功能。POI为“Poor Obfuscation Implementation”的首字母缩写,意为“简洁版的模糊实现”。

2.结构

  • HSSF - 提供读写[Microsoft Excel](https://baike.baidu.com/item/Microsoft Excel?fromModule=lemma_inlink) XLS格式档案的功能。
  • XSSF - 提供读写Microsoft Excel OOXML XLSX格式档案的功能。
  • HWPF - 提供读写Microsoft Word DOC格式档案的功能。
  • HSLF - 提供读写Microsoft PowerPoint格式档案的功能。
  • HDGF - 提供读[Microsoft Visio](https://baike.baidu.com/item/Microsoft Visio?fromModule=lemma_inlink)格式档案的功能。
  • HPBF - 提供读[Microsoft Publisher](https://baike.baidu.com/item/Microsoft Publisher?fromModule=lemma_inlink)格式档案的功能。
  • HSMF - 提供读[Microsoft Outlook](https://baike.baidu.com/item/Microsoft Outlook?fromModule=lemma_inlink)格式档案的功能。

3.POI-Excel写

注意:03版本的xls结尾使用HSSFWorkbook对象,07版本的xlsx结尾使用XSSFWorkbook对象

1.基本操作

1.导入pom

        <!--xls(03)--><dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>3.9</version></dependency><!--xlsx(07)--><dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>3.9</version></dependency>

2.excel写入测试

/*** excel写入测试*/
public class ExcelWriteTest {static final String filePath="F:\\java\\IDEAproject2\\springboot-poi\\";/*** 03版本excel写入测试*/@Testpublic void ExcelWrite03() throws Exception {//1.创建一个工作簿Workbook workbook = new HSSFWorkbook();//2.创建一个工作表(sheet)Sheet sheet = workbook.createSheet("excel03写入测试表");//3.创建一个行Row row = sheet.createRow(0);//4.创建一个单元格(第一行第一个单元格)Cell cell = row.createCell(0);cell.setCellValue("excel03写入测试数据");//5.创建写出文件流对象FileOutputStream os = new FileOutputStream(filePath + "excel03写入测试.xls");//6.写出工作簿workbook.write(os);os.close();System.out.println("excel03写出完成");}/*** 07版本excel写入测试*/@Testpublic void ExcelWrite07() throws Exception {//1.创建一个工作簿Workbook workbook = new XSSFWorkbook();//2.创建一个工作表(sheet)Sheet sheet = workbook.createSheet("excel07写入测试表");//3.创建一个行Row row = sheet.createRow(0);//4.创建一个单元格(第一行第一个单元格)Cell cell = row.createCell(0);cell.setCellValue("excel03写入测试数据");//5.创建写出文件流对象FileOutputStream os = new FileOutputStream(filePath + "excel07写入测试.xlsx");//6.写出工作簿workbook.write(os);os.close();System.out.println("excel07写出完成");}}
2.大文件写入HSSF

缺点:最多只能处理65536行,超出会报异常

java.lang.IllegalArgumentException: Invalid row number (65536) outside allowable range (0..65535)

优点:直接操作缓存,不操作磁盘,最后一次性写入,速度快

 @Testpublic void ExcelWriteBigData03() throws Exception{long begin = System.currentTimeMillis();//1.创建一个工作簿Workbook workbook = new HSSFWorkbook();//2.创建一个表Sheet sheet = workbook.createSheet("sheet1");//写入数据for (int rowNum = 0;rowNum<65536;rowNum++){//3.创建行Row row = sheet.createRow(rowNum);for (int CellNum = 0;CellNum<10;CellNum++){Cell cell = row.createCell(CellNum);cell.setCellValue(CellNum);}}//获取io流FileOutputStream fos = new FileOutputStream(filePath+"excel03大数据写入测试.xls");//生成一张表workbook.write(fos);fos.close();long end = System.currentTimeMillis();System.out.println("耗时:"+(end-begin));}
3.大文件写入XSSF

缺点:写速度很慢,耗内存,也会发生内存溢出,如100w条数据

优点:可以写较大数据,如20w条

   /*** 大数据07写入测试* @throws Exception*/@Testpublic void ExcelWriteBigData07() throws Exception{long begin = System.currentTimeMillis();//1.创建一个工作簿Workbook workbook = new XSSFWorkbook();//2.创建一个表Sheet sheet = workbook.createSheet("sheet1");//写入数据for (int rowNum = 0;rowNum<65536;rowNum++){//3.创建行Row row = sheet.createRow(rowNum);for (int CellNum = 0;CellNum<10;CellNum++){Cell cell = row.createCell(CellNum);cell.setCellValue(CellNum);}}//获取io流FileOutputStream fos = new FileOutputStream(filePath+"excel07大数据写入测试.xls");//生成一张表workbook.write(fos);fos.close();long end = System.currentTimeMillis();System.out.println("耗时:"+(end-begin));}
4.大文件写入SXSSF

优点:可以写非常大数据量,相比XSSF速度更快,占用更少内存

注意:

  1. 过程中会产生临时文件,需要清理临时文件
  2. 默认由100条记录被保存在内存中,如果超过这数量,则最前面的数据被写入临时文件
  3. 如果想自定义内存中数据的数量,可以使用new SXSSFWorkbook(数量)
  /*** 大数据07的SXSSF写入测试* @throws Exception*/@Testpublic void ExcelWriteBigDataSXSSF07() throws Exception{long begin = System.currentTimeMillis();//1.创建一个工作簿SXSSFWorkbook workbook = new SXSSFWorkbook();//2.创建一个表Sheet sheet = workbook.createSheet("sheet1");//写入数据for (int rowNum = 0;rowNum<100000;rowNum++){//3.创建行Row row = sheet.createRow(rowNum);for (int CellNum = 0;CellNum<10;CellNum++){Cell cell = row.createCell(CellNum);cell.setCellValue(CellNum);}}//获取io流FileOutputStream fos = new FileOutputStream(filePath+"excel07的SXSSF大数据写入测试.xls");//生成一张表workbook.write(fos);//清除临时文件workbook.dispose();fos.close();long end = System.currentTimeMillis();System.out.println("耗时:"+(end-begin));}

SXSSFWorkbook-来自官方的解释︰实现"BigGridDemo"策略的流式XSSFWorkbook版本。这允许写入非常大的文件而不会耗尽内存,因为任何时候只有可配置的行部分被保存在内存中。

请注意,仍然可能会消耗大量内存,这些内存基于您正在使用的功能,例如合并区域,注.…….当然只存储在内存中,因此如果广泛使用,可能需要大量内存。
再使用POI的时候! 内存问题 可以用Jprofile监控 !

4.POI-Excel读

1.基本操作
/*** excel读取测试*/
public class ExcelReaderTest {/*** 03版本读取* @throws Exception*/@Testpublic void ExcelReaderTest03() throws Exception {String filePath = "F:\\java\\IDEAproject2\\springboot-poi\\excel03写入测试.xls";//获取文件流对象FileInputStream is = new FileInputStream(filePath);//读取流Workbook workbook = new HSSFWorkbook(is);//获取表Sheet sheet = workbook.getSheetAt(0);//获取行Row row = sheet.getRow(0);//获取单元格Cell cell = row.getCell(0);//获取String类型的值String value = cell.getStringCellValue();System.out.println(value);is.close();}/*** 07版本读取* @throws Exception*/@Testpublic void ExcelReaderTest07() throws Exception {String filePath = "F:\\java\\IDEAproject2\\springboot-poi\\excel07写入测试.xlsx";//获取文件流对象FileInputStream is = new FileInputStream(filePath);//读取流Workbook workbook = new XSSFWorkbook(is);//获取表Sheet sheet = workbook.getSheetAt(0);//获取行Row row = sheet.getRow(0);//获取单元格Cell cell = row.getCell(0);//获取String类型的值String value = cell.getStringCellValue();System.out.println(value);is.close();}}
2.数据类型

1. 3.9版本的pom依赖

    /*** 不同格式处理* @throws Exception*/@Testpublic void  ExcelReaderCellType03() throws Exception{String filePath = "F:\\java\\IDEAproject2\\springboot-poi\\excel03写入测试.xls";//获取文件流FileInputStream fis = new FileInputStream(filePath);//获取一个工作簿Workbook workbook = new HSSFWorkbook(fis);//获取一个工作表Sheet sheet = workbook.getSheetAt(0);//获取第一行内容Row row = sheet.getRow(0);if (row != null){//获取所有的列int Cells = row.getPhysicalNumberOfCells();for (int col = 0;col < Cells;col++){//获取当前列Cell cell = row.getCell(col);if (cell != null){//获取当前行的第 col 列的值String cellValue = cell.getStringCellValue();System.out.print(cellValue+" | ");}}}//获取内容//获取有多少行int rowCount = sheet.getPhysicalNumberOfRows();//从1开始,第一行是标题for (int rowNum = 1;rowNum < rowCount;rowNum++){Row rowData = sheet.getRow(rowNum);if (rowData != null){//获取当前行的列数int cellCount = rowData.getPhysicalNumberOfCells();for (int col = 0;col < cellCount;col++){//获取当前列的值Cell cellData = rowData.getCell(col);//匹配列的类型if (cellData != null){//获取列的类型int cellType = cellData.getCellType();String cellValue = "";switch (cellType){case  Cell.CELL_TYPE_STRING://字符串System.out.print("[string]");cellValue = cellData.getStringCellValue();break;case  Cell.CELL_TYPE_BOOLEAN://布尔System.out.print("[boolean]");cellValue = String.valueOf(cellData.getBooleanCellValue());break;case  Cell.CELL_TYPE_BLANK://System.out.print("[blank]");break;case  Cell.CELL_TYPE_NUMERIC://数字(日期、普通数字)System.out.print("[numeric]");if (HSSFDateUtil.isCellDateFormatted(cellData)){//如果是日期System.out.print("[日期] ");Date date = cellData.getDateCellValue();cellValue = new DateTime(date).toString("yyyy-MM-dd HH:mm:ss");}else {//不是日期格式,防止数字过长System.out.print("[转换字符串输出] ");//转为字符串cellData.setCellType(HSSFCell.CELL_TYPE_STRING);cellValue = cellData.toString();}break;case  Cell.CELL_TYPE_ERROR://错误System.out.print("[error]");break;}System.out.print("["+cellValue+"]");}}}}fis.close();}

2. 高版本4.12依赖

 /*** 不同格式处理* @throws Exception*/@Testpublic void  ExcelReaderCellType03() throws Exception{String filePath = "F:\\java\\IDEAproject2\\springboot-poi\\excel03写入测试.xls";//获取文件流FileInputStream fis = new FileInputStream(filePath);//获取一个工作簿Workbook workbook = new HSSFWorkbook(fis);//获取一个工作表Sheet sheet = workbook.getSheetAt(0);//获取第一行内容Row row = sheet.getRow(0);if (row != null){//获取所有的列int Cells = row.getPhysicalNumberOfCells();for (int col = 0;col < Cells;col++){//获取当前列Cell cell = row.getCell(col);if (cell != null){//获取当前行的第 col 列的值String cellValue = cell.getStringCellValue();System.out.print(cellValue+" | ");}}}//获取内容//获取有多少行int rowCount = sheet.getPhysicalNumberOfRows();//从1开始,第一行是标题for (int rowNum = 1;rowNum < rowCount;rowNum++){Row rowData = sheet.getRow(rowNum);if (rowData != null){//获取当前行的列数int cellCount = rowData.getPhysicalNumberOfCells();for (int col = 0;col < cellCount;col++){//获取当前列的值Cell cellData = rowData.getCell(col);//匹配列的类型if (cellData != null){//获取列的类型CellType cellType = cellData.getCellType();String cellValue = "";switch (cellType){case STRING://字符串System.out.print("[string]");cellValue = cellData.getStringCellValue();break;case BOOLEAN://布尔System.out.print("[boolean]");cellValue = String.valueOf(cellData.getBooleanCellValue());break;case  BLANK://System.out.print("[blank]");break;case NUMERIC://数字(日期、普通数字)System.out.print("[numeric]");if (HSSFDateUtil.isCellDateFormatted(cellData)){//如果是日期System.out.print("[日期] ");Date date = cellData.getDateCellValue();cellValue = new DateTime(date).toString("yyyy-MM-dd HH:mm:ss");}else {//不是日期格式,防止数字过长System.out.print("[转换字符串输出] ");//转为字符串cellData.setCellType(CellType.STRING);cellValue = cellData.toString();}break;case ERROR://错误System.out.print("[error]");break;}System.out.print("["+cellValue+"]");}}}}fis.close();}
3.计算公式类型数据读取
 public void poiFormula(FileInputStream inputStream) throws IOException {Workbook workbook = new HSSFWorkbook(inputStream);Sheet sheet = workbook.getSheetAt(0);Row row = sheet.getRow(1);Cell cell = row.getCell(0);// 拿到计算公式 evalFormulaEvaluator formulaEvaluator = new HSSFFormulaEvaluator((HSSFWorkbook)workbook);// 输出单元格的内容int cellType = cell.getCellType();switch (cellType) {case Cell.CELL_TYPE_FORMULA:    // 公式String formula = cell.getCellFormula();System.out.println(formula);// 计算CellValue evaluate = formulaEvaluator.evaluate(cell);String cellValue = evaluate.formatAsString();System.out.println(cellValue);break;}}

5.EasyExcel写

官方文档:https://easyexcel.opensource.alibaba.com/docs/current/quickstart/read

3+版本不允许写方法为空

pom依赖

<!--easyExcel-->
<dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.1.0</version><exclusions><exclusion><artifactId>poi-ooxml-schemas</artifactId><groupId>org.apache.poi</groupId></exclusion></exclusions>
</dependency>
       <dependency><groupId>com.alibaba</groupId><artifactId>easyexcel</artifactId><version>3.3.1</version></dependency>

内部含有poi依赖。该版本集成为4.12

数据准备

   private List<DemoData> data() {List<DemoData> list = new ArrayList<>();for (int i = 0; i < 10; i++) {DemoData data = new DemoData();data.setString("字符串" + i);data.setDate(new Date());data.setDoubleData(0.56);list.add(data);}return list;}
1.基本写入操作
@Getter
@Setter
@EqualsAndHashCode
public class DemoData {@ExcelProperty("字符串标题")private String string;@ExcelProperty("日期标题")private Date date;@ExcelProperty("数字标题")private Double doubleData;/*** 忽略这个字段*/@ExcelIgnoreprivate String ignore;
}

public class EasyWriterTest {static final String filePath="F:\\java\\IDEAproject2\\springboot-poi\\";//生成数据集合对象private List<DemoData> data() {List<DemoData> list = new ArrayList<>();for (int i = 0; i < 10; i++) {DemoData data = new DemoData();data.setString("字符串" + i);data.setDate(new Date());data.setDoubleData(0.56);list.add(data);}return list;}/*** 最简单的写* <p>* 1. 创建excel对应的实体对象 参照{@link DemoData}* <p>* 2. 直接写即可*/@Testpublic void simpleWrite() {// 注意 simpleWrite在数据量不大的情况下可以使用(5000以内,具体也要看实际情况),数据量大参照 重复多次写入// 写法1 JDK8+// since: 3.0.0-beta1String fileName = filePath + "easy写入测试1.xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭// 如果这里想使用03 则 传入excelType参数即可EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(() -> {// 分页查询数据return data();});// 写法2fileName = filePath + "easy写入测试2.xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭// 如果这里想使用03 则 传入excelType参数即可EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());// 写法3fileName = filePath + "easy写入测试3.xlsx";// 这里 需要指定写用哪个class去写try (ExcelWriter excelWriter = EasyExcel.write(fileName, DemoData.class).build()) {WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();excelWriter.write(data(), writeSheet);}}}
2.日期、数字或者自定义格式转换
@Getter
@Setter
@EqualsAndHashCode
public class ConverterData {/*** 我想所有的 字符串起前面加上"自定义:"三个字*/@ExcelProperty(value = "字符串标题", converter = CustomStringStringConverter.class)private String string;/*** 我想写到excel 用年月日的格式*/@DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")@ExcelProperty("日期标题")private Date date;/*** 我想写到excel 用百分比表示*/@NumberFormat("#.##%")@ExcelProperty(value = "数字标题")private Double doubleData;
}
3.列宽、行高
@Getter
@Setter
@EqualsAndHashCode
@ContentRowHeight(10)
@HeadRowHeight(20)
@ColumnWidth(25)
public class WidthAndHeightData {@ExcelProperty("字符串标题")private String string;@ExcelProperty("日期标题")private Date date;/*** 宽度为50*/@ColumnWidth(50)@ExcelProperty("数字标题")private Double doubleData;
}

6.EasyExcel读

1.基本操作
@Getter
@Setter
@EqualsAndHashCode
public class DemoData {private String string;private Date date;private Double doubleData;
}
/*** 假设这个是你的DAO存储。当然还要这个类让spring管理,当然你不用需要存储,也不需要这个类。**/
public class DemoDAO {public void save(List<DemoData> list) {// 如果是mybatis,尽量别直接调用多次insert,自己写一个mapper里面新增一个方法batchInsert,所有数据一次性插入}
}

监听

// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去
@Slf4j
public class DemoDataListener implements ReadListener<DemoData> {/*** 每隔5条存储数据库,实际使用中可以100条,然后清理list ,方便内存回收*/private static final int BATCH_COUNT = 100;/*** 缓存的数据*/private List<DemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);/*** 假设这个是一个DAO,当然有业务逻辑这个也可以是一个service。当然如果不用存储这个对象没用。*/private DemoDAO demoDAO;public DemoDataListener() {// 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数demoDAO = new DemoDAO();}/*** 如果使用了spring,请使用这个构造方法。每次创建Listener的时候需要把spring管理的类传进来** @param demoDAO*/public DemoDataListener(DemoDAO demoDAO) {this.demoDAO = demoDAO;}/*** 这个每一条数据解析都会来调用** @param data    one row value. Is is same as {@link AnalysisContext#readRowHolder()}* @param context*/@Overridepublic void invoke(DemoData data, AnalysisContext context) {log.info("解析到一条数据:{}", JSON.toJSONString(data));cachedDataList.add(data);// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOMif (cachedDataList.size() >= BATCH_COUNT) {saveData();// 存储完成清理 listcachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);}}/*** 所有数据解析完成了 都会来调用** @param context*/@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {// 这里也要保存数据,确保最后遗留的数据也存储到数据库saveData();log.info("所有数据解析完成!");}/*** 加上存储数据库*/private void saveData() {log.info("{}条数据,开始存储数据库!", cachedDataList.size());demoDAO.save(cachedDataList);log.info("存储数据库成功!");}
}

读取测试

package com.li.easyexcel;import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.read.listener.PageReadListener;
import com.alibaba.excel.read.listener.ReadListener;
import com.alibaba.excel.read.metadata.ReadSheet;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;import java.io.File;
import java.util.List;@Slf4j
public class EasyReaderTest {static final String filePath="F:\\java\\IDEAproject2\\springboot-poi\\";/*** 最简单的读* <p>* 1. 创建excel对应的实体对象 参照{@link DemoData}* <p>* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}* <p>* 3. 直接读即可*/@Testpublic void simpleRead() {// 写法1:JDK8+ ,不用额外写一个DemoDataListener// since: 3.0.0-beta1String fileName = filePath + "easy写入测试1.xlsx";// 这里默认每次会读取100条数据 然后返回过来 直接调用使用数据就行// 具体需要返回多少行可以在`PageReadListener`的构造函数设置EasyExcel.read(fileName, DemoData.class, new PageReadListener<DemoData>(dataList -> {for (DemoData demoData : dataList) {log.info("读取到一条数据{}", JSON.toJSONString(demoData));System.out.println("读取到一条数据"+JSON.toJSONString(demoData));}})).sheet().doRead();// 写法2:// 匿名内部类 不用额外写一个DemoDataListenerfileName = filePath + "easy写入测试1.xlsx";// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭EasyExcel.read(fileName, DemoData.class, new ReadListener<DemoData>() {/*** 单次缓存的数据量*/public static final int BATCH_COUNT = 100;/***临时存储*/private List<DemoData> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);@Overridepublic void invoke(DemoData data, AnalysisContext context) {cachedDataList.add(data);if (cachedDataList.size() >= BATCH_COUNT) {saveData();// 存储完成清理 listcachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);}}@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {saveData();}/*** 加上存储数据库*/private void saveData() {log.info("{}条数据,开始存储数据库!", cachedDataList.size());System.out.println( cachedDataList.size()+"条数据,开始存储数据库!");log.info("存储数据库成功!");System.out.println("存储数据库成功!");}}).sheet().doRead();// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去// 写法3:fileName = filePath + "easy写入测试1.xlsx";// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();// 写法4fileName = filePath + "easy写入测试1.xlsx";// 一个文件一个readertry (ExcelReader excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build()) {// 构建一个sheet 这里可以指定名字或者noReadSheet readSheet = EasyExcel.readSheet(0).build();// 读取一个sheetexcelReader.read(readSheet);}}
}
2.指定列的下标或者列名
@Getter
@Setter
@EqualsAndHashCode
public class IndexOrNameData {/*** 强制读取第三个 这里不建议 index 和 name 同时用,要么一个对象只用index,要么一个对象只用name去匹配*/@ExcelProperty(index = 2)private Double doubleData;/*** 用名字去匹配,这里需要注意,如果名字重复,会导致只有一个字段读取到数据*/@ExcelProperty("字符串标题")private String string;@ExcelProperty("日期标题")private Date date;
}
    /*** 指定列的下标或者列名** <p>1. 创建excel对应的实体对象,并使用{@link ExcelProperty}注解. 参照{@link IndexOrNameData}* <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link IndexOrNameDataListener}* <p>3. 直接读即可*/@Testpublic void indexOrNameRead() {String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";// 这里默认读取第一个sheetEasyExcel.read(fileName, IndexOrNameData.class, new IndexOrNameDataListener()).sheet().doRead();}
3.读多个sheet
    /*** 读多个或者全部sheet,这里注意一个sheet不能读取多次,多次读取需要重新读取文件* <p>* 1. 创建excel对应的实体对象 参照{@link DemoData}* <p>* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoDataListener}* <p>* 3. 直接读即可*/@Testpublic void repeatedRead() {String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";// 读取全部sheet// 这里需要注意 DemoDataListener的doAfterAllAnalysed 会在每个sheet读取完毕后调用一次。然后所有sheet都会往同一个DemoDataListener里面写EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).doReadAll();// 读取部分sheetfileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";// 写法1try (ExcelReader excelReader = EasyExcel.read(fileName).build()) {// 这里为了简单 所以注册了 同样的head 和Listener 自己使用功能必须不同的ListenerReadSheet readSheet1 =EasyExcel.readSheet(0).head(DemoData.class).registerReadListener(new DemoDataListener()).build();ReadSheet readSheet2 =EasyExcel.readSheet(1).head(DemoData.class).registerReadListener(new DemoDataListener()).build();// 这里注意 一定要把sheet1 sheet2 一起传进去,不然有个问题就是03版的excel 会读取多次,浪费性能excelReader.read(readSheet1, readSheet2);}}
4.日期、数字或者自定义格式转换
@Getter
@Setter
@EqualsAndHashCode
public class ConverterData {/*** 我自定义 转换器,不管数据库传过来什么 。我给他加上“自定义:”*/@ExcelProperty(converter = CustomStringStringConverter.class)private String string;/*** 这里用string 去接日期才能格式化。我想接收年月日格式*/@DateTimeFormat("yyyy年MM月dd日HH时mm分ss秒")private String date;/*** 我想接收百分比的数字*/@NumberFormat("#.##%")private String doubleData;
}

自定义转换器

public class CustomStringStringConverter implements Converter<String> {@Overridepublic Class<?> supportJavaTypeKey() {return String.class;}@Overridepublic CellDataTypeEnum supportExcelTypeKey() {return CellDataTypeEnum.STRING;}/*** 这里读的时候会调用** @param context* @return*/@Overridepublic String convertToJavaData(ReadConverterContext<?> context) {return "自定义:" + context.getReadCellData().getStringValue();}/*** 这里是写的时候会调用 不用管** @return*/@Overridepublic WriteCellData<?> convertToExcelData(WriteConverterContext<String> context) {return new WriteCellData<>(context.getValue());}}
    /*** 日期、数字或者自定义格式转换* <p>* 默认读的转换器{@link DefaultConverterLoader#loadDefaultReadConverter()}* <p>1. 创建excel对应的实体对象 参照{@link ConverterData}.里面可以使用注解{@link DateTimeFormat}、{@link NumberFormat}或者自定义注解* <p>2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link ConverterDataListener}* <p>3. 直接读即可*/@Testpublic void converterRead() {String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";// 这里 需要指定读用哪个class去读,然后读取第一个sheet EasyExcel.read(fileName, ConverterData.class, new ConverterDataListener())// 这里注意 我们也可以registerConverter来指定自定义转换器, 但是这个转换变成全局了, 所有java为string,excel为string的都会用这个转换器。// 如果就想单个字段使用请使用@ExcelProperty 指定converter// .registerConverter(new CustomStringStringConverter())// 读取sheet.sheet().doRead();}
5.读取公式和单元格类型
@Getter
@Setter
@EqualsAndHashCode
public class CellDataReadDemoData {private CellData<String> string;// 这里注意 虽然是日期 但是 类型 存储的是number 因为excel 存储的就是numberprivate CellData<Date> date;private CellData<Double> doubleData;// 这里并不一定能完美的获取 有些公式是依赖性的 可能会读不到 这个问题后续会修复private CellData<String> formulaValue;
}
       /*** 读取公式和单元格类型** <p>* 1. 创建excel对应的实体对象 参照{@link CellDataReadDemoData}* <p>* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoHeadDataListener}* <p>* 3. 直接读即可** @since 2.2.0-beat1*/@Testpublic void cellDataRead() {String fileName = TestFileUtil.getPath() + "demo" + File.separator + "cellDataDemo.xlsx";// 这里 需要指定读用哪个class去读,然后读取第一个sheetEasyExcel.read(fileName, CellDataReadDemoData.class, new CellDataDemoHeadDataListener()).sheet().doRead();}
6.数据转换等异常处理
@Getter
@Setter
@EqualsAndHashCode
public class ExceptionDemoData {*/***** 用日期去接字符串 肯定报错***/*private Date date;
}

监听器增加onException方法

    /*** 在转换异常 获取其他异常下会调用本接口。抛出异常则停止读取。如果这里不抛出异常则 继续读取下一行。** @param exception* @param context* @throws Exception*/@Overridepublic void onException(Exception exception, AnalysisContext context) {log.error("解析失败,但是继续解析下一行:{}", exception.getMessage());// 如果是某一个单元格的转换异常 能获取到具体行号// 如果要获取头的信息 配合invokeHeadMap使用if (exception instanceof ExcelDataConvertException) {ExcelDataConvertException excelDataConvertException = (ExcelDataConvertException)exception;log.error("第{}行,第{}列解析异常,数据为:{}", excelDataConvertException.getRowIndex(),excelDataConvertException.getColumnIndex(), excelDataConvertException.getCellData());}}
    /*** 数据转换等异常处理** <p>* 1. 创建excel对应的实体对象 参照{@link ExceptionDemoData}* <p>* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link DemoExceptionListener}* <p>* 3. 直接读即可*/@Testpublic void exceptionRead() {String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";// 这里 需要指定读用哪个class去读,然后读取第一个sheetEasyExcel.read(fileName, ExceptionDemoData.class, new DemoExceptionListener()).sheet().doRead();}
7.web中的读
    /*** 文件上传* <p>* 1. 创建excel对应的实体对象 参照{@link UploadData}* <p>* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link UploadDataListener}* <p>* 3. 直接读即可*/@PostMapping("upload")@ResponseBodypublic String upload(MultipartFile file) throws IOException {EasyExcel.read(file.getInputStream(), UploadData.class, new UploadDataListener(uploadDAO)).sheet().doRead();return "success";}

7.EasyExcel中常用操作

1.数据批量导入监听
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.util.ArrayList;
import java.util.List;/*** excel读取监听导入数据库* @author lijia*/
public class ExcelListener extends AnalysisEventListener<Demo> {protected final Logger logger = LoggerFactory.getLogger(getClass());private static final int BATCH_COUNT = 100;private  List<Demo> cachedDataList = new ArrayList<>(BATCH_COUNT);private  DemoService demoService;public ExcelListener(DemoService demoService) {this.demoService = demoService;}/*** 读取数据会执行invoke 方法* VehicleSecureTrainRecord 类型* AnalysisContext 分析上下文** @param data* @param context*/@Overridepublic void invoke(Demo data, AnalysisContext context) {cachedDataList.add(data);// 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOMif (cachedDataList.size() >= BATCH_COUNT) {saveData();// 存储完成清理 listcachedDataList.clear();}}/*** 数据解析完成回调** @param context*/@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {// 这里也要保存数据,确保最后遗留的数据也存储到数据库saveData();logger.info("数据解析完成");}/*** 加上存储数据库*/private void saveData() {logger.info("{}条数据,开始存储数据库!",cachedDataList.size());demoService.insertDataByExcel(cachedDataList);logger.info("存储数据库成功");}}
2.导出注解方式复杂表头设置

复杂表头@ExcelProperty需写多级,从最顶级父标题开始,easyexcel会自动映射

如果涉及动态变化的表头,则需要写head方法进行自定义配置,不使用注解


import com.alibaba.excel.annotation.ExcelIgnoreUnannotated;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.HeadFontStyle;
import com.alibaba.excel.annotation.write.style.HeadStyle;
import com.alibaba.excel.enums.BooleanEnum;
import com.alibaba.excel.enums.poi.FillPatternTypeEnum;import java.math.BigDecimal;/*** @author li*/
@ColumnWidth(value = 15)
@HeadFontStyle(fontName = "宋体", fontHeightInPoints = 11,bold = BooleanEnum.TRUE)
@HeadStyle(fillPatternType = FillPatternTypeEnum.SOLID_FOREGROUND,fillForegroundColor = 22)
@ExcelIgnoreUnannotated
public class Demo {@ExcelProperty("code")private String code;@ExcelProperty({"标题1", "测试"})private String title;@ExcelProperty({"标题1", "测试2"})private String title2;@ExcelProperty({"标题2", "测试"})private BigDecimal title3;@ExcelProperty({"标题2", "测试2"})private BigDecimal title4; }
3.导出动态复杂表头设置

跟注解方式相似,只是将数据换成自己生成拼接数据

EasyExcel.write(fos,Demo.class).head(head(year)).sheet("sheet1").doWrite(items);
/*** 动态复杂表头设置* @param year* @return*/
private List<List<String>> head(String year) {//初始化表头String bigTitle = year;List<String> title = new ArrayList<>(Arrays.asList(bigTitle, "标题1"));List<String> title0 = new ArrayList<>(Arrays.asList(bigTitle, "标题2"));List<String> title1 = new ArrayList<>(Arrays.asList(bigTitle, "复杂头1","子级1"));List<String> title2 = new ArrayList<>(Arrays.asList(bigTitle, "复杂头1","子级2"));List<String> title3 = new ArrayList<>(Arrays.asList(bigTitle, "复杂头2",year+"年"));List<String> title4 = new ArrayList<>(Arrays.asList(bigTitle, "复杂头2",(Integer.parseInt(year)+1)+"年"));List<String> title5 = new ArrayList<>(Arrays.asList(bigTitle, "备注"));return new ArrayList<>(Arrays.asList(title,title0,title1,title2,title3,title4,title5));
}
4.导出文件增加序列号(自定义行处理器)
@GetMapping("/year/export")
public void xxx( HttpServletResponse response) throws PortalException {//获取文件OutputStream fos = null;try {String fileName = URLEncoder.encode("xxx.xlsx", "utf-8");fos = response.getOutputStream();//设置文件类型以及文件头response.setContentType("application/vnd.ms-excel");response.setCharacterEncoding("utf-8");response.setHeader("Content-disposition", "attachment;filename=" + fileName);//写数据到输出流List<Demo> result = xxService.selectData();EasyExcel.write(fos, Demo.class).registerWriteHandler(new CustomSequenceHandler()).sheet("sheet1").doWrite(result);} catch (Exception ex) {ex.printStackTrace();}}
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.write.handler.RowWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import com.alibaba.excel.write.property.ExcelWriteHeadProperty;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;import java.util.Map;/*** 自定义 excel 行处理器, 增加序号列* @author li*/
public class CustomSequenceHandler implements RowWriteHandler {private boolean init = true;private static final String COLUM_TITLE="序号";@Overridepublic void beforeRowCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder,Integer rowIndex, Integer relativeRowIndex, Boolean isHead) {if (init) {// 修改存储头部及对应字段信息的 map, 将其中的内容均右移一位, 给新增的序列号预留为第一列ExcelWriteHeadProperty excelWriteHeadProperty = writeSheetHolder.excelWriteHeadProperty();Map<Integer, Head> headMap = excelWriteHeadProperty.getHeadMap();int size = headMap.size();for (int current = size; current > 0; current--) {int previous = current - 1;headMap.put(current, headMap.get(previous));}// 空出第一列headMap.remove(0);// 只需要修改一次 map 即可, 故使用 init 变量进行控制init = false;}}@Overridepublic void afterRowCreate(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row,Integer relativeRowIndex, Boolean isHead) {// 设置首列单元格内容Cell cell = row.createCell(0);int rowNum = row.getRowNum();if (rowNum == 0) {cell.setCellValue(COLUM_TITLE);}else {cell.setCellValue(rowNum);}}@Overridepublic void afterRowDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, Row row,Integer relativeRowIndex, Boolean isHead) {if (row.getLastCellNum() > 1) {// 将自定义新增的序号列的样式设置与默认的样式一致row.getCell(0).setCellStyle(row.getCell(1).getCellStyle());}}}

8.Poi工具类


/*** excel读写工具类*/
public class POIUtil {private static Logger logger  = Logger.getLogger(POIUtil.class);private final static String xls = "xls";private final static String xlsx = "xlsx";/*** 读入excel文件,解析后返回* @param file* @throws IOException*/public static List<String[]> readExcel(MultipartFile file) throws IOException{//检查文件checkFile(file);//获得Workbook工作薄对象Workbook workbook = getWorkBook(file);//创建返回对象,把每行中的值作为一个数组,所有行作为一个集合返回List<String[]> list = new ArrayList<String[]>();if(workbook != null){for(int sheetNum = 0;sheetNum < workbook.getNumberOfSheets();sheetNum++){//获得当前sheet工作表Sheet sheet = workbook.getSheetAt(sheetNum);if(sheet == null){continue;}//获得当前sheet的开始行int firstRowNum  = sheet.getFirstRowNum();//获得当前sheet的结束行int lastRowNum = sheet.getLastRowNum();//循环除了第一行的所有行for(int rowNum = firstRowNum;rowNum <= lastRowNum;rowNum++){//获得当前行Row row = sheet.getRow(rowNum);Row firstRow = sheet.getRow(firstRowNum);if(row == null){continue;}//获得当前行的开始列int firstCellNum = firstRow.getFirstCellNum();//获得首行的列数int lastCellNum = firstRow.getPhysicalNumberOfCells();String[] cells = new String[firstRow.getPhysicalNumberOfCells()];//循环当前行for(int cellNum = firstCellNum; cellNum < lastCellNum;cellNum++){Cell cell = row.getCell(cellNum);cells[cellNum] = getCellValue(cell);}list.add(cells);}}// workbook.close();}return list;}public static void checkFile(MultipartFile file) throws IOException{//判断文件是否存在if(null == file){logger.error("文件不存在!");throw new FileNotFoundException("文件不存在!");}//获得文件名String fileName = file.getOriginalFilename();//判断文件是否是excel文件if(!fileName.endsWith(xls) && !fileName.endsWith(xlsx)){logger.error(fileName + "不是excel文件");throw new IOException(fileName + "不是excel文件");}}public static Workbook getWorkBook(MultipartFile file) {//获得文件名String fileName = file.getOriginalFilename();//创建Workbook工作薄对象,表示整个excelWorkbook workbook = null;try {//获取excel文件的io流InputStream is = file.getInputStream();//根据文件后缀名不同(xls和xlsx)获得不同的Workbook实现类对象if(fileName.endsWith(xls)){//2003workbook = new HSSFWorkbook(is);}else if(fileName.endsWith(xlsx)){//2007 及2007以上workbook = new XSSFWorkbook(is);}} catch (IOException e) {logger.info(e.getMessage());}return workbook;}public static String getCellValue(Cell cell){String cellValue = "";if(cell == null){return cellValue;}//把数字当成String来读,避免出现1读成1.0的情况if(cell.getCellType() == Cell.CELL_TYPE_NUMERIC){cell.setCellType(Cell.CELL_TYPE_STRING);}//判断数据的类型switch (cell.getCellType()){case Cell.CELL_TYPE_NUMERIC: //数字cellValue = String.valueOf(cell.getNumericCellValue());break;case Cell.CELL_TYPE_STRING: //字符串cellValue = String.valueOf(cell.getStringCellValue());break;case Cell.CELL_TYPE_BOOLEAN: //BooleancellValue = String.valueOf(cell.getBooleanCellValue());break;case Cell.CELL_TYPE_FORMULA: //公式cellValue = String.valueOf(cell.getCellFormula());break;case Cell.CELL_TYPE_BLANK: //空值cellValue = "";break;case Cell.CELL_TYPE_ERROR: //故障cellValue = "非法字符";break;default:cellValue = "未知类型";break;}return cellValue;}}

参考资料:https://www.bilibili.com/video/BV1Ua4y1x7BK/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/791399.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

软件杯 深度学习中文汉字识别

文章目录 0 前言1 数据集合2 网络构建3 模型训练4 模型性能评估5 文字预测6 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习中文汉字识别 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xf…

【Linux】从零认识文件操作

送给大家一句话&#xff1a; 要相信&#xff0c;所有的不美好都是为了迎接美好&#xff0c;所有的困难都会为努力让道。 —— 简蔓《巧克力色微凉青春》 开始理解基础 IO 吧&#xff01; 1 前言2 知识回顾3 理解文件3.1 进程和文件的关系3.2 文件的系统调用openwrite文件 fd 值…

OpenHarmony实战:小型系统平台驱动移植

在这一步&#xff0c;我们会在源码目录//device/vendor_name/soc_name/drivers目录下创建平台驱动。 建议的目录结构&#xff1a; device ├── vendor_name │ ├── drivers │ │ │ ├── common │ │ │ ├── Kconfig # 厂商驱动内核菜单入口 │ …

七大开源基金会联合制定符合 CRA 法案的共同标准

欧洲议会上个月通过的《欧洲网络弹性法案》(CRA) 制定通用规范和标准 Apache 软件基金会、Blender 基金会、Eclipse 基金会、OpenSSL 软件基金会、PHP 基金会、Python 软件基金会 和 Rust 基金会 这项工作由 Eclipse 基金会牵头&#xff0c;旨在建立基于现有开源最佳实践的安全…

yolov8训练流程

训练代码 from ultralytics import YOLO# Load a model model YOLO(yolov8n.yaml) # build a new model from YAML model YOLO(yolov8n.pt) # load a pretrained model (recommended for training) model YOLO(yolov8n.yaml).load(yolov8n.pt) # build from YAML and tr…

巧用lambda表达式构建各种“树”

简述 利用jdk8 lambda表达式分组函数&#xff0c;可巧妙构建各种树&#xff0c;比如地区树&#xff0c;可以利用其多线程特性充分利用CPU提高性能。 分组函数&#xff1a;Collectors.groupingBy() 开起多线程&#xff1a;list.parallelStream() 实例 以地区为例&#xff1…

9.图像中值腐蚀膨胀滤波的实现

1 简介 在第七章介绍了基于三种卷积前的图像填充方式&#xff0c;并生成了3X3的图像卷积模板&#xff0c;第八章运用这种卷积模板进行了均值滤波的FPGA实现与MATLAB实现&#xff0c;验证了卷积模板生成的正确性和均值滤波算法的MATLAB算法实现。   由于均值滤波、中值滤波、腐…

leet hot 100-13 最大子数组和

53. 最大子数组和 原题链接思路代码 原题链接 leet hot 100-10 53. 最大子数组和 思路 生成一个数字来记录last 表示前面数字全部之和与0取最大值 如果大于0 就加上如果不大于0 就不管 从当前位置从新开始遍历计算 时间复杂度O(n) 空间复杂度(1) 代码 class Solution {…

JVM剖析

0.前言 Java 是当今世界使用最广泛的技术平台之一。使用 Java 或 JVM 的一些技术包括&#xff1a; Apache spark用于大数据处理&#xff0c;数据分析在JVM上运行;用于数据流的Apache NiFi在内部使用的也是 JVM;现代 Web 和移动应用程序开发中使用的React native使用 的也包含…

Boost编译使用

Boost编译使用 文章目录 Boost编译使用写在前面测试环境 下载编译目录结构直接使用手动编译Boost.Build编译 参考 使用Vs中使用Qt中使用CMake中使用代码中使用 写在前面 Boost 是一个非常强大的 C 开源库,提供了许多高质量的、经过良好测试的 C 组件。 Boost 的主要特点包括广…

HTTPS RSA 握手解析(计算机网络)

传统的 TLS 握手基本都是使用 RSA 算法来实现密钥交换的&#xff0c;在将 TLS 证书部署服务端时&#xff0c;证书文件其实就是服务端的公钥&#xff0c;会在 TLS 握手阶段传递给客户端&#xff0c;而服务端的私钥则一直留在服务端。 在 RSA 密钥协商算法中&#xff0c;客户端会…

佳能打印机E568扫描书和文件方法

官方网站; Canon : Inkjet 手册 : IJ Scan Utility : 启动IJ Scan Utility 打开打印机电源 扫描一个文件&#xff0c;翻页后盖好盖子。再点击扫描。 所有扫描结束之后点退出 点击保存

easyexcel-获取文件资源和导入导出excel

1、获取本地资源文件&#xff0c;根据模板填充数据导出 public void exportExcel(HttpServletResponse httpResponse, RequestBody AssayReportDayRecordQuery query) {AssayReportDayRecordDTO dto this.queryByDate(query);ExcelWriter excelWriter null;ExcelUtil.config…

【无限列车1】SpringCloudAlibaba 与 SpringBoot后端架构的搭建

【无限列车1】SpringCloudAlibaba 与 SpringBoot后端架构的搭建 1、版本说明二、日志相关配置3、AOP 打印日志4、下载开源前端后台管理系统5、添加网关模块6、集成数据库和mp(1) 添加驱动和mp依赖(2) 数据库配置(3) 使用MybatisPlus 7、加密 yaml 文件中的内容(1) 依赖(2) 敏感…

SVG XML 格式定义图形入门介绍

SVG SVG means Scalable Vector Graphics. SVG 使用 XML 格式定义图形SVG 图像在放大或改变尺寸的情况下其图形质量不会有所损失SVG 是万维网联盟的标准 Hello World Use SVG in html and you can see: Link to the SVG file You can use <a> tag to link to the svg…

02---webpack基础用法

01 entry打包的入口文件&#xff1a; 单入口entry是一个字符串:适用于单页面项目module.exports {entry:./src/index.js}多入口entry是一个对象module.exports {entry:{index:./src/index.js,app:./src/app.js}} 02 output打包的出口文件&#xff1a; 单入口配置module.ex…

基于SSM的网络视频播放器

目录 背景 技术简介 系统简介 界面预览 背景 互联网的迅猛发展彻底转变了全球各类组织的管理策略。自20世纪90年代起&#xff0c;中国政府和企业便开始探索利用网络系统进行信息管理。然而&#xff0c;早期的网络覆盖不广泛、用户接受度不高、相关法律法规不完善以及技术开…

git 提交一个pr

为了向一个项目提交一个PR&#xff08;Pull Request&#xff09;&#xff0c;你需要遵循一系列步骤&#xff0c;这通常包括&#xff1a; Fork项目&#xff1a;首先&#xff0c;你需要在GitHub上找到项目&#xff0c;并点击“Fork”按钮。这将会创建项目的一个副本到你的GitHub账…

QT之单例模式

一.概述 1.什么是单例模式 单例模式&#xff08;Singleton Pattern&#xff09;是一种设计模式&#xff0c;用于确保一个类只有一个实例&#xff0c;并提供全局访问点以访问该实例。 在应用程序的整个生命周期内&#xff0c;只能创建一个特定类的对象。 单例模式常用于需要…

C#实现只保存2天的日志文件

文章目录 业务需求代码运行效果 欢迎讨论&#xff01; 业务需求 在生产环境中&#xff0c;控制台窗口不便展示出来。 为了在生产环境中&#xff0c;完整记录控制台应用的输出&#xff0c;选择将其输出到文件中。 但是&#xff0c;存储所有输出的话会占用很多空间&#xff0c;…