EasyExcel 让Excel导入导出更简单
- 一、什么是EasyExcel?
- 二、对比ApachePoi 其他Excel框架 优势在于哪里
- 三、本质上对于原生做了哪些优化?
- 四、有哪些功能
- 五、如何实践?
- 5.1 常规文件读取
- 5.2 常规文件写入
- 5.3 常规WEB的上传和下载
一、什么是EasyExcel?
EasyExcel是对07版POI的提升和优化,能够有效解决内存占用大的问题,将内存处理部分转移到磁盘。
从功能上,就是poi 07版本能做的,它都能做,并且效率更高,门槛更低。
在互联网平台上获得广泛使用,在github上面star已经达到26700,可见人气之旺。
二、对比ApachePoi 其他Excel框架 优势在于哪里
- 内存优化
EasyExcel出现的原因主要是为了优化内存的占用,官方提供的数据就能够了解到 —> 16M内存23秒读取75M的Excel, 这个Excel包含46W行25列。对于极速模式,也会牺牲内存换取更快的速度,要看取舍了。
- 读写行数
针对Excel03版本,只能读取小于65535行。从Excel07版本开始,读写行数都能够超过65535行,但是由于框架性质的原因,读取数据越多,占用的内存就会增长迅速。速度也是较慢。
- jar引用
对于POI这种通常都要引入几个相关的jar包,jar包多了内部的版本就会和别的组件的依赖会出现更多冲突的可能。easyexcel目前只有一个jar。
三、本质上对于原生做了哪些优化?
以上都是描述了EasyExcel从内存的角度,对于解析Exccel07版本做了优化,具体的点有哪些呢?(待完善)
四、有哪些功能
- 简单读
- 模板读
- 注解表头读
- 注解自定义读
- 处理器自定义读
- 模板读
- 无对象读
- 固定多层表头读
- 自定义多层表头读
- 超链接、标注读
- 图片读
- 写也都有类似的写功能
- 写入有自适应列宽的实现
有大量Handler可扩展,非常灵活
五、如何实践?
简单使用官方教程的测试用例,来跟着代码一起了解功能。对于测试样例和文档都挺完备的,非常好理解。
简单操作也可以直接移步官网:https://easyexcel.opensource.alibaba.com/docs/current/
5.1 常规文件读取
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.alibaba.excel.annotation.format.NumberFormat;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.converters.DefaultConverterLoader;
import com.alibaba.excel.enums.CellExtraTypeEnum;
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.fastjson2.JSON;
import com.learning.easyexcel.common.CellDataUser;
import com.learning.easyexcel.common.User;
import com.learning.easyexcel.common.UserExtra;
import com.learning.easyexcel.common.UserFormat;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.compress.utils.Lists;import java.util.List;
import java.util.Map;/*** 功能简述* 〈读取测试〉** @author claire* @date 2023/2/20 - 11:40 上午* @since 1.0.0*/
@Slf4j
public class ReadTest {/*** 最简单的读* <p>* 1. 创建excel对应的实体对象 参照{@link User}* <p>* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link }* <p>* 3. 直接读即可*/public void simpleRead1() {// 写法1:JDK8+ ,不用额外写一个DemoDataListener// since: 3.0.0-beta1String fileName = "demo.xlsx";// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭// 这里默认每次会读取100条数据 然后返回过来 直接调用使用数据就行// 具体需要返回多少行可以在`PageReadListener`的构造函数设置EasyExcel.read(fileName, User.class, new PageReadListener<User>(dataList -> {for (User user : dataList) {log.info("读取到一条数据{}", JSON.toJSONString(user));}})).sheet().doRead();}public void simpleRead2() {// 写法2:// 匿名内部类 不用额外写一个DemoDataListenerString fileName = "demo.xlsx";// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭EasyExcel.read(fileName, User.class, new ReadListener<User>() {/*** 单次缓存的数据量*/public static final int BATCH_COUNT = 100;/*** 临时存储*/private List<User> cachedDataList = ListUtils.newArrayListWithExpectedSize(BATCH_COUNT);@Overridepublic void invoke(User data, AnalysisContext context) {cachedDataList.add(data);if (cachedDataList.size() >= BATCH_COUNT) {saveData();// 存储完成清理 listcachedDataList = Lists.newArrayList();}}@Overridepublic void doAfterAllAnalysed(AnalysisContext context) {saveData();}/*** 加上存储数据库*/private void saveData() {log.info("{}条数据,开始存储数据库!", cachedDataList.size());log.info("存储数据库成功!");}}).sheet().doRead();}public void simpleRead3() {// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去// 写法3:String fileName = "demo.xlsx";// 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭EasyExcel.read(fileName, User.class, new UserDataListener()).sheet().doRead();}public void simpleRead4() {// 写法4String fileName = "demo.xlsx";// 一个文件一个readertry (ExcelReader excelReader = EasyExcel.read(fileName, User.class, new UserDataListener()).build()) {// 构建一个sheet 这里可以指定名字或者noReadSheet readSheet = EasyExcel.readSheet(0).build();// 读取一个sheetexcelReader.read(readSheet);}}/*** 指定列的下标或者列名** <p>* 1. 创建excel对应的实体对象,并使用{@link ExcelProperty}注解. 参照{@link }* <p>* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link }* <p>* 3. 直接读即可*/public void indexOrNameRead() {String fileName ="demo.xlsx";// 这里默认读取第一个sheetEasyExcel.read(fileName, User.class, new UserWithAnnoDataListener()).sheet().doRead();}/*** 读多个或者全部sheet,这里注意一个sheet不能读取多次,多次读取需要重新读取文件* <p>* 1. 创建excel对应的实体对象 参照{@link User}* <p>* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link UserDataListener}* <p>* 3. 直接读即可*/public void multiSheetRead(){String fileName = "demo.xlsx";// 读取全部sheet// 这里需要注意 DemoDataListener的doAfterAllAnalysed 会在每个sheet读取完毕后调用一次。然后所有sheet都会往同一个DemoDataListener里面写EasyExcel.read(fileName, User.class, new UserDataListener()).doReadAll();// 读取部分sheetfileName = "demo.xlsx";// 写法1try (ExcelReader excelReader = EasyExcel.read(fileName).build()) {// 这里为了简单 所以注册了 同样的head 和Listener 自己使用功能必须不同的ListenerReadSheet readSheet1 =EasyExcel.readSheet(0).head(User.class).registerReadListener(new UserDataListener()).build();ReadSheet readSheet2 =EasyExcel.readSheet(1).head(User.class).registerReadListener(new UserDataListener()).build();// 这里注意 一定要把sheet1 sheet2 一起传进去,不然有个问题就是03版的excel 会读取多次,浪费性能excelReader.read(readSheet1, readSheet2);}}/*** 日期、数字或者自定义格式转换* <p>* 默认读的转换器{@link DefaultConverterLoader#loadDefaultReadConverter()}* <p>* 1. 创建excel对应的实体对象 参照{@link UserFormat}.里面可以使用注解{@link DateTimeFormat}、{@link NumberFormat}或者自定义注解* <p>* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link }* <p>* 3. 直接读即可*/public void contentFormatWithAnnoRead(){String fileName = "demo.xlsx";// 这里 需要指定读用哪个class去读,然后读取第一个sheetEasyExcel.read(fileName, UserFormat.class, new UserFormatDataListener())// 这里注意 我们也可以registerConverter来指定自定义转换器, 但是这个转换变成全局了, 所有java为string,excel为string的都会用这个转换器。// 如果就想单个字段使用请使用@ExcelProperty 指定converter// .registerConverter(new CustomStringStringConverter())// 读取sheet.sheet().doRead();}/*** 多行头** <p>* 1. 创建excel对应的实体对象 参照{@link User}* <p>* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link UserDataListener}* <p>* 3. 设置headRowNumber参数,然后读。 这里要注意headRowNumber如果不指定, 会根据你传入的class的{@link ExcelProperty#value()}里面的表头的数量来决定行数,* 如果不传入class则默认为1.当然你指定了headRowNumber不管是否传入class都是以你传入的为准。*/public void groupHeaderRead(){String fileName = "demo.xlsx";// 这里 需要指定读用哪个class去读,然后读取第一个sheetEasyExcel.read(fileName, User.class, new UserDataListener()).sheet()// 这里可以设置1,因为头就是一行。如果多行头,可以设置其他值。不传入也可以,因为默认会根据DemoData 来解析,他没有指定头,也就是默认1行.headRowNumber(3).doRead();}/*** 读取表头数据** <p>* 1. 创建excel对应的实体对象 参照{@link User}* <p>* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link UserDataListener}* <p>* 3. 直接读即可*/public void headerRead() {String fileName ="demo.xlsx";// 这里 需要指定读用哪个class去读,然后读取第一个sheetEasyExcel.read(fileName, User.class, new UserDataListener()).sheet().doRead();}/*** 额外信息(批注、超链接、合并单元格信息读取)* <p>* 由于是流式读取,没法在读取到单元格数据的时候直接读取到额外信息,所以只能最后通知哪些单元格有哪些额外信息** <p>* 1. 创建excel对应的实体对象 参照{@link UserExtra}* <p>* 2. 由于默认异步读取excel,所以需要创建excel一行一行的回调监听器,参照{@link UserExtraListener}* <p>* 3. 直接读即可** @since 2.2.0-beat1*/public void extraRead() {String fileName ="extra.xlsx";// 这里 需要指定读用哪个class去读,然后读取第一个sheetEasyExcel.read(fileName, UserExtra.class, new UserExtraListener())// 需要读取批注 默认不读取.extraRead(CellExtraTypeEnum.COMMENT)// 需要读取超链接 默认不读取.extraRead(CellExtraTypeEnum.HYPERLINK)// 需要读取合并单元格信息 默认不读取.extraRead(CellExtraTypeEnum.MERGE).sheet().doRead();}/*** 读取公式和单元格类型** <p>* 1. 创建excel对应的实体对象 参照{@link CellDataUser}* <p>* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link CellDataUserHeadListener}* <p>* 3. 直接读即可** @since 2.2.0-beat1*/public void cellDataRead() {String fileName = "cellDataDemo.xlsx";// 这里 需要指定读用哪个class去读,然后读取第一个sheetEasyExcel.read(fileName, CellDataUser.class, new CellDataUserHeadListener()).sheet().doRead();}/*** 数据转换等异常处理** <p>* 1. 创建excel对应的实体对象 参照{@link }* <p>* 2. 由于默认一行行的读取excel,所以需要创建excel一行一行的回调监听器,参照{@link }* <p>* 3. 直接读即可*/public void exceptionConvertRead(){//遇到异常转换为有含义的数据,在listen中转换
// @Override
// public 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());
// }
// }}/*** 同步的返回,不推荐使用,如果数据量大会把数据放到内存里面*/public void synchronousRead() {String fileName ="demo.xlsx";// 这里 需要指定读用哪个class去读,然后读取第一个sheet 同步读取会自动finishList<User> list = EasyExcel.read(fileName).head(User.class).sheet().doReadSync();for (User data : list) {log.info("读取到数据:{}", JSON.toJSONString(data));}// 这里 也可以不指定class,返回一个list,然后读取第一个sheet 同步读取会自动finishList<Map<Integer, String>> listMap = EasyExcel.read(fileName).sheet().doReadSync();for (Map<Integer, String> data : listMap) {// 返回每条数据的键值对 表示所在的列 和所在列的值log.info("读取到数据:{}", JSON.toJSONString(data));}}/*** 不创建对象的读,逻辑里面入DB或入缓存,用MAP来承载数据*/public void noModelRead() {String fileName = "demo.xlsx";// 这里 只要,然后读取第一个sheet 同步读取会自动finish
// EasyExcel.read(fileName, new NoModelDataListener()).sheet().doRead();}}
5.2 常规文件写入
import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.format.DateTimeFormat;
import com.alibaba.excel.annotation.format.NumberFormat;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.alibaba.excel.annotation.write.style.ContentRowHeight;
import com.alibaba.excel.annotation.write.style.HeadRowHeight;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.data.*;
import com.alibaba.excel.util.BooleanUtils;
import com.alibaba.excel.util.FileUtils;
import com.alibaba.excel.util.ListUtils;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.handler.context.CellWriteHandlerContext;
import com.alibaba.excel.write.merge.LoopMergeStrategy;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.WriteTable;
import com.alibaba.excel.write.metadata.style.WriteCellStyle;
import com.alibaba.excel.write.metadata.style.WriteFont;
import com.alibaba.excel.write.style.HorizontalCellStyleStrategy;
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
import com.learning.easyexcel.LongestMatchColumnWidthData;
import com.learning.easyexcel.common.*;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.streaming.SXSSFSheet;import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.util.*;/*** 功能简述* 〈x写Excel〉** @author claire* @date 2023/2/20 - 2:53 下午* @since 1.0.0*/
public class WriteTest {/*** 最简单的写* <p>* 1. 创建excel对应的实体对象 参照{@link User}* <p>* 2. 直接写即可*/public void simpleWrite1() {// 注意 simpleWrite在数据量不大的情况下可以使用(5000以内,具体也要看实际情况),数据量大参照 重复多次写入// 写法1 JDK8+// since: 3.0.0-beta1String fileName = "simpleWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭// 如果这里想使用03 则 传入excelType参数即可EasyExcel.write(fileName, User.class).sheet("模板").doWrite(() -> {// 分页查询数据return data();});}public void simpleWrite2() {// 写法2String fileName = "simpleWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭// 如果这里想使用03 则 传入excelType参数即可EasyExcel.write(fileName, User.class).sheet("模板").doWrite(data());}public void simpleWrite3() {// 写法3String fileName = "simpleWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写try (ExcelWriter excelWriter = EasyExcel.write(fileName, User.class).build()) {WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();excelWriter.write(data(), writeSheet);}}/*** 根据参数只导出指定列* <p>* 1. 创建excel对应的实体对象 参照{@link User}* <p>* 2. 根据自己或者排除自己需要的列* <p>* 3. 直接写即可** @since 2.1.1*/public void excludeOrIncludeWrite() {String fileName = "excludeOrIncludeWrite" + System.currentTimeMillis() + ".xlsx";// 这里需要注意 在使用ExcelProperty注解的使用,如果想不空列则需要加入order字段,而不是index,order会忽略空列,然后继续往后,而index,不会忽略空列,在第几列就是第几列。// 根据用户传入字段 假设我们要忽略 scoreSet<String> excludeColumnFieldNames = new HashSet<>();excludeColumnFieldNames.add("score");// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, User.class).excludeColumnFieldNames(excludeColumnFieldNames).sheet("模板").doWrite(data());fileName = "excludeOrIncludeWrite" + System.currentTimeMillis() + ".xlsx";// 根据用户传入字段 假设我们只要导出 scoreSet<String> includeColumnFieldNames = new HashSet<>();includeColumnFieldNames.add("score");// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, User.class).includeColumnFieldNames(includeColumnFieldNames).sheet("模板").doWrite(data());}/*** 指定写入的列* <p>* 1. 创建excel对应的实体对象 参照{@link UserWriteFormat}* <p>* 2. 使用{@link ExcelProperty}注解指定写入的列* <p>* 3. 直接写即可*/public void indexWrite() {String fileName = "indexWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, UserWriteFormat.class).sheet("模板").doWrite(data());}/*** 复杂头写入,这个很实用,快速导出,不用纠结于表头的设计* <p>* 1. 创建excel对应的实体对象 参照{@link ComplexHeadData}* <p>* 2. 使用{@link ExcelProperty}注解指定复杂的头* <p>* 3. 直接写即可*/public void complexHeadWrite() {String fileName = "complexHeadWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, ComplexHeadData.class).sheet("模板").doWrite(data());}/*** 重复多次写入* <p>* 1. 创建excel对应的实体对象 参照{@link ComplexHeadData}* <p>* 2. 使用{@link ExcelProperty}注解指定复杂的头* <p>* 3. 直接调用二次写入即可*/public void repeatedWrite1() {// 方法1: 如果写到同一个sheetString fileName = "repeatedWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写try (ExcelWriter excelWriter = EasyExcel.write(fileName, User.class).build()) {// 这里注意 如果同一个sheet只要创建一次WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();// 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来for (int i = 0; i < 5; i++) {// 分页去数据库查询数据 这里可以去数据库查询每一页的数据List<User> data = data();excelWriter.write(data, writeSheet);}}}public void repeatedWrite2() {// 方法2: 如果写到不同的sheet 同一个对象String fileName = "repeatedWrite" + System.currentTimeMillis() + ".xlsx";// 这里 指定文件try (ExcelWriter excelWriter = EasyExcel.write(fileName, User.class).build()) {// 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面for (int i = 0; i < 5; i++) {// 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).build();// 分页去数据库查询数据 这里可以去数据库查询每一页的数据List<User> data = data();excelWriter.write(data, writeSheet);}}}public void repeatedWrite3() {// 方法3 如果写到不同的sheet 不同的对象String fileName = "repeatedWrite" + System.currentTimeMillis() + ".xlsx";// 这里 指定文件try (ExcelWriter excelWriter = EasyExcel.write(fileName).build()) {// 去调用写入,这里我调用了五次,实际使用时根据数据库分页的总的页数来。这里最终会写到5个sheet里面for (int i = 0; i < 5; i++) {// 每次都要创建writeSheet 这里注意必须指定sheetNo 而且sheetName必须不一样。这里注意DemoData.class 可以每次都变,我这里为了方便 所以用的同一个class// 实际上可以一直变WriteSheet writeSheet = EasyExcel.writerSheet(i, "模板" + i).head(User.class).build();// 分页去数据库查询数据 这里可以去数据库查询每一页的数据List<User> data = data();excelWriter.write(data, writeSheet);}}}/*** 日期、数字或者自定义格式转换,这个是常见的指定格式导出Excel,自定义的convert可以做一些枚举转换、字段拆分等* <p>* 1. 创建excel对应的实体对象 参照{@link UserFormat}* <p>* 2. 使用{@link ExcelProperty}配合使用注解{@link DateTimeFormat}、{@link NumberFormat}或者自定义注解* <p>* 3. 直接写即可*/public void converterWrite() {String fileName = "converterWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, UserFormat.class).sheet("模板").doWrite(data());}/*** 图片导出* <p>* 1. 创建excel对应的实体对象 参照{@link ImageDemoData}* <p>* 2. 直接写即可*/public void imageWrite() throws Exception {String fileName = "imageWrite" + System.currentTimeMillis() + ".xlsx";// 这里注意下 所有的图片都会放到内存 暂时没有很好的解法,大量图片的情况下建议 2选1:// 1. 将图片上传到oss 或者其他存储网站: https://www.aliyun.com/product/oss ,然后直接放链接// 2. 使用: https://github.com/coobird/thumbnailator 或者其他工具压缩图片String imagePath ="converter" + File.separator + "img.jpg";try (InputStream inputStream = FileUtils.openInputStream(new File(imagePath))) {List<ImageDemoData> list = ListUtils.newArrayList();ImageDemoData imageDemoData = new ImageDemoData();list.add(imageDemoData);// 放入五种类型的图片 实际使用只要选一种即可imageDemoData.setByteArray(FileUtils.readFileToByteArray(new File(imagePath)));imageDemoData.setFile(new File(imagePath));imageDemoData.setString(imagePath);imageDemoData.setInputStream(inputStream);imageDemoData.setUrl(new URL("https://raw.githubusercontent.com/alibaba/easyexcel/master/src/test/resources/converter/img.jpg"));// 这里演示// 需要额外放入文字// 而且需要放入2个图片// 第一个图片靠左// 第二个靠右 而且要额外的占用他后面的单元格WriteCellData<Void> writeCellData = new WriteCellData<>();imageDemoData.setWriteCellDataFile(writeCellData);// 这里可以设置为 EMPTY 则代表不需要其他数据了writeCellData.setType(CellDataTypeEnum.STRING);writeCellData.setStringValue("额外的放一些文字");// 可以放入多个图片List<ImageData> imageDataList = new ArrayList<>();ImageData imageData = new ImageData();imageDataList.add(imageData);writeCellData.setImageDataList(imageDataList);// 放入2进制图片imageData.setImage(FileUtils.readFileToByteArray(new File(imagePath)));// 图片类型imageData.setImageType(ImageData.ImageType.PICTURE_TYPE_PNG);// 上 右 下 左 需要留空// 这个类似于 css 的 margin// 这里实测 不能设置太大 超过单元格原始大小后 打开会提示修复。暂时未找到很好的解法。imageData.setTop(5);imageData.setRight(40);imageData.setBottom(5);imageData.setLeft(5);// 放入第二个图片imageData = new ImageData();imageDataList.add(imageData);writeCellData.setImageDataList(imageDataList);imageData.setImage(FileUtils.readFileToByteArray(new File(imagePath)));imageData.setImageType(ImageData.ImageType.PICTURE_TYPE_PNG);imageData.setTop(5);imageData.setRight(5);imageData.setBottom(5);imageData.setLeft(50);// 设置图片的位置 假设 现在目标 是 覆盖 当前单元格 和当前单元格右边的单元格// 起点相对于当前单元格为0 当然可以不写imageData.setRelativeFirstRowIndex(0);imageData.setRelativeFirstColumnIndex(0);imageData.setRelativeLastRowIndex(0);// 前面3个可以不写 下面这个需要写 也就是 结尾 需要相对当前单元格 往右移动一格// 也就是说 这个图片会覆盖当前单元格和 后面的那一格imageData.setRelativeLastColumnIndex(1);// 写入数据EasyExcel.write(fileName, ImageDemoData.class).sheet().doWrite(list);}}/*** 超链接、备注、公式、指定单个单元格的样式、单个单元格多种样式* <p>* 1. 创建excel对应的实体对象 参照{@link WriteCellDemoData}* <p>* 2. 直接写即可** @since 3.0.0-beta1*/public void writeCellDataWrite() {String fileName = "writeCellDataWrite" + System.currentTimeMillis() + ".xlsx";WriteCellDemoData writeCellDemoData = new WriteCellDemoData();// 设置超链接WriteCellData<String> hyperlink = new WriteCellData<>("官方网站");writeCellDemoData.setHyperlink(hyperlink);HyperlinkData hyperlinkData = new HyperlinkData();hyperlink.setHyperlinkData(hyperlinkData);hyperlinkData.setAddress("https://github.com/alibaba/easyexcel");hyperlinkData.setHyperlinkType(HyperlinkData.HyperlinkType.URL);// 设置备注WriteCellData<String> comment = new WriteCellData<>("备注的单元格信息");writeCellDemoData.setCommentData(comment);CommentData commentData = new CommentData();comment.setCommentData(commentData);commentData.setAuthor("Jiaju Zhuang");commentData.setRichTextStringData(new RichTextStringData("这是一个备注"));// 备注的默认大小是按照单元格的大小 这里想调整到4个单元格那么大 所以向后 向下 各额外占用了一个单元格commentData.setRelativeLastColumnIndex(1);commentData.setRelativeLastRowIndex(1);// 设置公式WriteCellData<String> formula = new WriteCellData<>();writeCellDemoData.setFormulaData(formula);FormulaData formulaData = new FormulaData();formula.setFormulaData(formulaData);// 将 123456789 中的第一个数字替换成 2// 这里只是例子 如果真的涉及到公式 能内存算好尽量内存算好 公式能不用尽量不用formulaData.setFormulaValue("REPLACE(123456789,1,1,2)");// 设置单个单元格的样式 当然样式 很多的话 也可以用注解等方式。WriteCellData<String> writeCellStyle = new WriteCellData<>("单元格样式");writeCellStyle.setType(CellDataTypeEnum.STRING);writeCellDemoData.setWriteCellStyle(writeCellStyle);WriteCellStyle writeCellStyleData = new WriteCellStyle();writeCellStyle.setWriteCellStyle(writeCellStyleData);// 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色.writeCellStyleData.setFillPatternType(FillPatternType.SOLID_FOREGROUND);// 背景绿色writeCellStyleData.setFillForegroundColor(IndexedColors.GREEN.getIndex());// 设置单个单元格多种样式// 这里需要设置 inMomery=true 不然会导致无法展示单个单元格多种样式,所以慎用WriteCellData<String> richTest = new WriteCellData<>();richTest.setType(CellDataTypeEnum.RICH_TEXT_STRING);writeCellDemoData.setRichText(richTest);RichTextStringData richTextStringData = new RichTextStringData();richTest.setRichTextStringDataValue(richTextStringData);richTextStringData.setTextString("红色绿色默认");// 前2个字红色WriteFont writeFont = new WriteFont();writeFont.setColor(IndexedColors.RED.getIndex());richTextStringData.applyFont(0, 2, writeFont);// 接下来2个字绿色writeFont = new WriteFont();writeFont.setColor(IndexedColors.GREEN.getIndex());richTextStringData.applyFont(2, 4, writeFont);List<WriteCellDemoData> data = new ArrayList<>();data.add(writeCellDemoData);EasyExcel.write(fileName, WriteCellDemoData.class).inMemory(true).sheet("模板").doWrite(data);}/*** 根据模板写入,模板文件的方式可以动态变化模板,了解模板书写规范即可* <p>* 1. 创建excel对应的实体对象 参照{@link User}* <p>* 2. 使用{@link ExcelProperty}注解指定写入的列* <p>* 3. 使用withTemplate 写取模板* <p>* 4. 直接写即可*/public void templateWrite() {String templateFileName = "demo" + File.separator + "demo.xlsx";String fileName = "templateWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭// 这里要注意 withTemplate 的模板文件会全量存储在内存里面,所以尽量不要用于追加文件,如果文件模板文件过大会OOM// 如果要再文件中追加(无法在一个线程里面处理,可以在一个线程的建议参照多次写入的demo) 建议临时存储到数据库 或者 磁盘缓存(ehcache) 然后再一次性写入EasyExcel.write(fileName, User.class).withTemplate(templateFileName).sheet().doWrite(data());}/*** 列宽、行高* <p>* 1. 创建excel对应的实体对象 参照{@link WidthAndHeightData}* <p>* 2. 使用注解{@link ColumnWidth}、{@link HeadRowHeight}、{@link ContentRowHeight}指定宽度或高度* <p>* 3. 直接写即可*/public void widthAndHeightWrite() {String fileName ="widthAndHeightWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, WidthAndHeightData.class).sheet("模板").doWrite(data());}/*** 注解形式自定义样式* <p>* 1. 创建excel对应的实体对象 参照{@link FixStyleData}* <p>* 3. 直接写即可** @since 2.2.0-beta1*/public void annotationStyleWrite() {String fileName = "annotationStyleWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, FixStyleData.class).sheet("模板").doWrite(data());}/*** 拦截器形式自定义样式* <p>* 1. 创建excel对应的实体对象 参照{@link DemoData}* <p>* 2. 创建一个style策略 并注册* <p>* 3. 直接写即可*/public void handlerStyleWrite() {// 方法1 使用已有的策略 推荐// HorizontalCellStyleStrategy 每一行的样式都一样 或者隔行一样// AbstractVerticalCellStyleStrategy 每一列的样式都一样 需要自己回调每一页String fileName = "handlerStyleWrite" + System.currentTimeMillis() + ".xlsx";// 头的策略WriteCellStyle headWriteCellStyle = new WriteCellStyle();// 背景设置为红色headWriteCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex());WriteFont headWriteFont = new WriteFont();headWriteFont.setFontHeightInPoints((short) 20);headWriteCellStyle.setWriteFont(headWriteFont);// 内容的策略WriteCellStyle contentWriteCellStyle = new WriteCellStyle();// 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUND 不然无法显示背景颜色.头默认了 FillPatternType所以可以不指定contentWriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);// 背景绿色contentWriteCellStyle.setFillForegroundColor(IndexedColors.GREEN.getIndex());WriteFont contentWriteFont = new WriteFont();// 字体大小contentWriteFont.setFontHeightInPoints((short) 20);contentWriteCellStyle.setWriteFont(contentWriteFont);// 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现HorizontalCellStyleStrategy horizontalCellStyleStrategy =new HorizontalCellStyleStrategy(headWriteCellStyle, contentWriteCellStyle);// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, User.class).registerWriteHandler(horizontalCellStyleStrategy).sheet("模板").doWrite(data());// 方法2: 使用easyexcel的方式完全自己写 不太推荐 尽量使用已有策略// @since 3.0.0-beta2fileName = "handlerStyleWrite" + System.currentTimeMillis() + ".xlsx";EasyExcel.write(fileName, User.class).registerWriteHandler(new CellWriteHandler() {@Overridepublic void afterCellDispose(CellWriteHandlerContext context) {// 当前事件会在 数据设置到poi的cell里面才会回调// 判断不是头的情况 如果是fill 的情况 这里会==null 所以用not trueif (BooleanUtils.isNotTrue(context.getHead())) {// 第一个单元格// 只要不是头 一定会有数据 当然fill的情况 可能要context.getCellDataList() ,这个需要看模板,因为一个单元格会有多个 WriteCellDataWriteCellData<?> cellData = context.getFirstCellData();// 这里需要去cellData 获取样式// 很重要的一个原因是 WriteCellStyle 和 dataFormatData绑定的 简单的说 比如你加了 DateTimeFormat// ,已经将writeCellStyle里面的dataFormatData 改了 如果你自己new了一个WriteCellStyle,可能注解的样式就失效了// 然后 getOrCreateStyle 用于返回一个样式,如果为空,则创建一个后返回WriteCellStyle writeCellStyle = cellData.getOrCreateStyle();writeCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex());// 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUNDwriteCellStyle.setFillPatternType(FillPatternType.SOLID_FOREGROUND);// 这样样式就设置好了 后面有个FillStyleCellWriteHandler 默认会将 WriteCellStyle 设置到 cell里面去 所以可以不用管了}}}).sheet("模板").doWrite(data());// 方法3: 使用poi的样式完全自己写 不推荐// @since 3.0.0-beta2// 坑1:style里面有dataformat 用来格式化数据的 所以自己设置可能导致格式化注解不生效// 坑2:不要一直去创建style 记得缓存起来 最多创建6W个就挂了fileName = "handlerStyleWrite" + System.currentTimeMillis() + ".xlsx";EasyExcel.write(fileName, User.class).registerWriteHandler(new CellWriteHandler() {@Overridepublic void afterCellDispose(CellWriteHandlerContext context) {// 当前事件会在 数据设置到poi的cell里面才会回调// 判断不是头的情况 如果是fill 的情况 这里会==null 所以用not trueif (BooleanUtils.isNotTrue(context.getHead())) {Cell cell = context.getCell();// 拿到poi的workbookWorkbook workbook = context.getWriteWorkbookHolder().getWorkbook();// 这里千万记住 想办法能复用的地方把他缓存起来 一个表格最多创建6W个样式// 不同单元格尽量传同一个 cellStyleCellStyle cellStyle = workbook.createCellStyle();cellStyle.setFillForegroundColor(IndexedColors.RED.getIndex());// 这里需要指定 FillPatternType 为FillPatternType.SOLID_FOREGROUNDcellStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);cell.setCellStyle(cellStyle);// 由于这里没有指定dataformat 最后展示的数据 格式可能会不太正确// 这里要把 WriteCellData的样式清空, 不然后面还有一个拦截器 FillStyleCellWriteHandler 默认会将 WriteCellStyle 设置到// cell里面去 会导致自己设置的不一样context.getFirstCellData().setWriteCellStyle(null);}}}).sheet("模板").doWrite(data());}/*** 合并单元格* <p>* 1. 创建excel对应的实体对象 参照{@link User} {@link RowMergeData}* <p>* 2. 创建一个merge策略 并注册* <p>* 3. 直接写即可** @since 2.2.0-beta1*/public void mergeWrite() {// 方法1 注解String fileName = "mergeWrite" + System.currentTimeMillis() + ".xlsx";// 在DemoStyleData里面加上ContentLoopMerge注解// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, RowMergeData.class).sheet("模板").doWrite(data());// 方法2 自定义合并单元格策略fileName = "mergeWrite" + System.currentTimeMillis() + ".xlsx";// 每隔2行会合并 把eachColumn 设置成 3 也就是我们数据的长度,所以就第一列会合并。当然其他合并策略也可以自己写LoopMergeStrategy loopMergeStrategy = new LoopMergeStrategy(2, 0);// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, User.class).registerWriteHandler(loopMergeStrategy).sheet("模板").doWrite(data());}/*** 使用table去写入* <p>* 1. 创建excel对应的实体对象 参照{@link User}* <p>* 2. 然后写入table即可*/public void tableWrite() {String fileName = "tableWrite" + System.currentTimeMillis() + ".xlsx";// 方法1 这里直接写多个table的案例了,如果只有一个 也可以直一行代码搞定,参照其他案// 这里 需要指定写用哪个class去写try (ExcelWriter excelWriter = EasyExcel.write(fileName, User.class).build()) {// 把sheet设置为不需要头 不然会输出sheet的头 这样看起来第一个table 就有2个头了WriteSheet writeSheet = EasyExcel.writerSheet("模板").needHead(Boolean.FALSE).build();// 这里必须指定需要头,table 会继承sheet的配置,sheet配置了不需要,table 默认也是不需要WriteTable writeTable0 = EasyExcel.writerTable(0).needHead(Boolean.TRUE).build();WriteTable writeTable1 = EasyExcel.writerTable(1).needHead(Boolean.TRUE).build();// 第一次写入会创建头excelWriter.write(data(), writeSheet, writeTable0);// 第二次写如也会创建头,然后在第一次的后面写入数据excelWriter.write(data(), writeSheet, writeTable1);}}/*** 动态头,实时生成头写入* <p>* 思路是这样子的,先创建List<String>头格式的sheet仅仅写入头,然后通过table 不写入头的方式 去写入数据** <p>* 1. 创建excel对应的实体对象 参照{@link User}* <p>* 2. 然后写入table即可*/public void dynamicHeadWrite() {String fileName = "dynamicHeadWrite" + System.currentTimeMillis() + ".xlsx";EasyExcel.write(fileName)// 这里放入动态头.head(head()).sheet("模板")// 当然这里数据也可以用 List<List<String>> 去传入.doWrite(data());}/*** 自动列宽(不太精确)* <p>* 这个目前不是很好用,比如有数字就会导致换行。而且长度也不是刚好和实际长度一致。 所以需要精确到刚好列宽的慎用。 当然也可以自己参照 {@link LongestMatchColumnWidthStyleStrategy}* 重新实现.* <p>* poi 自带{@link SXSSFSheet#autoSizeColumn(int)} 对中文支持也不太好。目前没找到很好的算法。 有的话可以推荐下。** <p>* 1. 创建excel对应的实体对象 参照{@link LongestMatchColumnWidthData}* <p>* 2. 注册策略{@link LongestMatchColumnWidthStyleStrategy}* <p>* 3. 直接写即可*/public void longestMatchColumnWidthWrite() {String fileName = "longestMatchColumnWidthWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, LongestMatchColumnWidthData.class).registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()).sheet("模板").doWrite(dataLong());}/*** 下拉,超链接等自定义拦截器(上面几点都不符合但是要对单元格进行操作的参照这个)* <p>* demo这里实现2点。1. 对第一行第一列的头超链接到:https://github.com/alibaba/easyexcel 2. 对第一列第一行和第二行的数据新增下拉框,显示 测试1 测试2* <p>* 1. 创建excel对应的实体对象 参照{@link User}* <p>* 2. 注册拦截器 {@link CustomSheetWriteHandler}* <p>* 2. 直接写即可*/public void customHandlerWrite() {String fileName = "customHandlerWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, User.class).registerWriteHandler(new CustomSheetWriteHandler()).registerWriteHandler(new CustomCellWriteHandler()).sheet("模板").doWrite(data());}/*** 插入批注* <p>* 1. 创建excel对应的实体对象 参照{@link User}* <p>* 2. 注册拦截器 {@link CommentWriteHandler}* <p>* 2. 直接写即可*/public void commentWrite() {String fileName = "commentWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭// 这里要注意inMemory 要设置为true,才能支持批注。目前没有好的办法解决 不在内存处理批注。这个需要自己选择。EasyExcel.write(fileName, User.class).inMemory(Boolean.TRUE).registerWriteHandler(new CommentWriteHandler()).sheet("模板").doWrite(data());}/*** 可变标题处理(包括标题国际化等)* <p>* 简单的说用List<List<String>>的标题 但是还支持注解* <p>* 1. 创建excel对应的实体对象 参照{@link UserFormat}* <p>* 2. 直接写即可*/public void variableTitleWrite() {// 写法1String fileName = "variableTitleWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName, UserFormat.class).head(variableTitleHead()).sheet("模板").doWrite(data());}/*** 不创建对象的写*/public void noModelWrite() {// 写法1String fileName = "noModelWrite" + System.currentTimeMillis() + ".xlsx";// 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭EasyExcel.write(fileName).head(head()).sheet("模板").doWrite(dataList());}private List<LongestMatchColumnWidthData> dataLong() {List<LongestMatchColumnWidthData> list = ListUtils.newArrayList();for (int i = 0; i < 10; i++) {LongestMatchColumnWidthData data = new LongestMatchColumnWidthData();data.setString("测试很长的字符串测试很长的字符串测试很长的字符串" + i);data.setDate(new Date());data.setDoubleData(1000000000000.0);list.add(data);}return list;}private List<List<String>> variableTitleHead() {List<List<String>> list = ListUtils.newArrayList();List<String> head0 = ListUtils.newArrayList();head0.add("string" + System.currentTimeMillis());List<String> head1 = ListUtils.newArrayList();head1.add("number" + System.currentTimeMillis());List<String> head2 = ListUtils.newArrayList();head2.add("date" + System.currentTimeMillis());list.add(head0);list.add(head1);list.add(head2);return list;}private List<List<String>> head() {List<List<String>> list = ListUtils.newArrayList();List<String> head0 = ListUtils.newArrayList();head0.add("字符串" + System.currentTimeMillis());List<String> head1 = ListUtils.newArrayList();head1.add("数字" + System.currentTimeMillis());List<String> head2 = ListUtils.newArrayList();head2.add("日期" + System.currentTimeMillis());list.add(head0);list.add(head1);list.add(head2);return list;}private List<List<Object>> dataList() {List<List<Object>> list = ListUtils.newArrayList();for (int i = 0; i < 10; i++) {List<Object> data = ListUtils.newArrayList();data.add("字符串" + i);data.add(0.56);data.add(new Date());list.add(data);}return list;}private List<User> data() {List<User> list = ListUtils.newArrayList();for (int i = 0; i < 10; i++) {User data = new User();data.setCname("字符串" + i);data.setCreateTime(new Date());data.setScore(0.56);list.add(data);}return list;}
}
以上代码取自官方内的实现自测,具体实现类可详见:
https://github.com/CzyerChen/springboot-forall/tree/master/boot-for-easyexcel/src/main/java/com/learning/easyexcel
5.3 常规WEB的上传和下载
这个文件的接口和回写基本就是引用过Read和Write的具体方法,参照官方的书写方式:
https://github.com/alibaba/easyexcel/blob/master/easyexcel-test/src/test/java/com/alibaba/easyexcel/test/demo/web/WebTest.java
/*** 文件下载(失败了会返回一个有部分数据的Excel)* <p>* 1. 创建excel对应的实体对象 参照{@link DownloadData}* <p>* 2. 设置返回的 参数* <p>* 3. 直接写,这里注意,finish的时候会自动关闭OutputStream,当然你外面再关闭流问题不大*/@GetMapping("download")public void download(HttpServletResponse response) throws IOException {// 这里注意 有同学反应使用swagger 会导致各种问题,请直接用浏览器或者用postmanresponse.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");response.setCharacterEncoding("utf-8");// 这里URLEncoder.encode可以防止中文乱码 当然和easyexcel没有关系String fileName = URLEncoder.encode("测试", "UTF-8").replaceAll("\\+", "%20");response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + fileName + ".xlsx");EasyExcel.write(response.getOutputStream(), DownloadData.class).sheet("模板").doWrite(data());}/*** 文件上传* <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";}
相关文章:
EasyExcel 让Excel导入导出更简单
一、什么是EasyExcel?二、对比ApachePoi 其他Excel框架 优势在于哪里三、本质上对于原生做了哪些优化?四、有哪些功能五、如何实践? 5.1 常规文件读取5.2 常规文件写入5.3 常规WEB的上传和下载 一、什么是EasyExcel? EasyExcel是对07版POI的提升和优…...
华为OD机试 - 需要广播的服务器数量 | 机试题算法思路 【2023】
最近更新的博客 华为OD机试 - 简易压缩算法(Python) | 机试题算法思路 【2023】 华为OD机试题 - 获取最大软件版本号(JavaScript) 华为OD机试 - 猜字谜(Python) | 机试题+算法思路 【2023】 华为OD机试 - 删除指定目录(Python) | 机试题算法思路 【2023】 华为OD机试 …...

三次握手四次挥手详细解析面试常问
文章目录1.第2次握手传回了ACK,为什么还要传回SYN?2.断开连接-TCP 四次挥手3.为什么要四次挥手?4.为什么不能把服务器发送的 ACK 和 FIN 合并起来,变成三次挥手?5.如果第二次挥手时服务器的 ACK 没有送达客户端&#x…...

组合由于继承
目录 前言: 1.什么是继承? 2.继承的劣势、问题? 3.组合相比继承有哪些优势? 4、如何判断该用组合还是继承? 参考资料 前言: 我们在平时日常开发设计的过程中,经常会有人提到一条经典的设…...

大学计算机基础 知识点总结
一/ 计算机的发展、类型及其应用领域。 1. 计算机(computer)是一种能自动、高速进行大量算术运算和逻辑运算的电子设备。 其特点为:速度快、精度高、存储容量大、通用性强、具有逻辑判断和自动控制能力。 2. 第一台计算机:ENIAC,美国&#…...

手撸React组件库前必须清楚的9个问题
1. 组件库文档问题 以前常用的组件库文档storybook,包括现在也有用dumi、vitepress做组件库文档等。storybook缺点不美观、webpack的热更新太慢,虽然新版本支持了vite提高了速度但还不算稳定。好在各种文档、mdx、测试等组件第三方工具很多集成进去能很…...

试用国内及国外AI绘图软件后的总结
最近AI很火,所以这几天抱着试试看的角度试用了多款AI绘图软件,大概测试了市面上的3款工具吧,3款国外的,1款国内的。因为有对比,波哥也不是专业的评测机构出身,所以这些比对无论是从角度,还是从对…...

DJI 无人机 Onboard SDK ROS 功能包demo运行
DJI 无人机 Onboard SDK ROS 功能包demo运行demo功能准备测试环境运行 dji sdk 节点运行 demo 节点自动飞行任务航点自动飞行兴趣点环绕自动飞行飞行控制本地坐标位置控制搭建好 Onboard SDK ROS 的开发环境后,功能包自身具备一些写好的demo功能案例 dji sdk 的节点…...

揭开JavaWeb中Cookie与Session的神秘面纱
文章目录1,会话跟踪技术的概述2,Cookie2.1 Cookie的基本使用2.2 Cookie的原理分析2.3 Cookie的使用细节2.3.1 Cookie的存活时间2.3.2 Cookie存储中文3,Session3.1 Session的基本使用3.2 Session的原理分析3.3 Session的使用细节3.3.1 Session…...

2023-02-20 Qt 5.13.1 + OpenCV 4.5.4环境编译
引言 OpenCV图像处理在Qt中编译记录。 之前一直是在Python中使用OpenCV,Python中使用某些模块使用pip工具很容易将对应的模块安装在系统中。根据项目需求项目都要转移在国产化中使用,为了适应国产化需求,将代码转移到Qt开发环境中,…...

波次分拣系统
一、系统架构: v1.2基站软件管理系统仓库标签v1.4仓库标签二、系统简介: 标签系统主要由标签服务器,基站,电子标签前三部分组成,操作界面借助于京东仓库已有的作业电脑来实现,标签服务器与WMS进行数据对接。…...

【Servlet篇】Request请求转发详细解读
文章目录1. 前言2. 实战案例3. 特点1. 前言 请求转发是一种在服务器内部的资源跳转方式,如图: 上图的大致过程为,浏览器发送请求给服务器,服务器中 a 资源接收到请求,资源 a 处理完请求后将请求发送给资源 bÿ…...

vector
目录 vector的成员函数: at: 编辑 size: assign:赋值 insert find? erase swap shrink_to_fit 编辑 vector的模拟实现: vector的框架: 构造函数: size和capacity r…...

LeetCode——104. 二叉树的最大深度
一、题目 给定一个二叉树,找出其最大深度。 二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。 说明: 叶子节点是指没有子节点的节点。 来源:力扣(LeetCode) 链接:https://leetcode.cn/problems/maximum…...

漫画 | Python是一门烂语言?
这个电脑的主人是个程序员,他相继学习了C、Java、Python、Go, 但是似乎总是停留在Hello World的水平。 每天晚上,夜深人静的时候,这些Hello World程序都会热火朝天地聊天但是,这一天发生了可怕的事情随着各个Hello wor…...

2023.2 新方案 java代码混淆 java加密 字符串加密
Java字节码可以反编译,特别是创业公司,很好的项目很容易被别人破解反编译,造成很严重的损失,所以本混淆方案能很好的保护源码,而且在不断迭代,增强混淆效果,异常问题处理,达到保护项目的目的: 本次升级包括: 2023年02年19日 : ht-confusion-project-1.8…...

Swift 周报 第二十三期
前言 本期是 Swift 编辑组自主整理周报的第十四期,每个模块已初步成型。各位读者如果有好的提议,欢迎在文末留言。 欢迎投稿或推荐内容。目前计划每两周周一发布,欢迎志同道合的朋友一起加入周报整理。 勇敢是即便知道好结局不会每每降临在…...
android系统屏幕旋转角度,应用界面横竖屏,设备旋转角度,三者的区别以及使用。
注意区分以下三种概念的区别!!!。以及使用这三种方式判断横竖屏的方式。系统屏幕旋转角度fun getSystemRotation(): Int {val angle (getSystemService(WINDOW_SERVICE) as WindowManager).defaultDisplay.rotation//系统屏幕旋转的角度值re…...

【华为云-开发者专属集市】DevCloud+ECS、MySQL搭建WordPress
文章目录AppBazaar官网选择与购买项目项目概况操作过程购买DevCloud服务创建项目添加制品库应用部署购买ECS添加部署模板并执行任务故障排除安装及访问WordPress登录网站管理后台访问网站完善部署模板资源释放使用总结AppBazaar官网 首先,我们来到AppBazaar的官网&…...

Milvus 群星闪耀时|又一个小目标达成 :社区正式突破 15,000 星!
如果把 Milvus 看作开源世界中的一束微光,那用户便是无垠宇宙中点点闪烁的星光。用户每一次点亮 star 之时,Milvus 就会迸发出更加耀眼的光芒。不知不觉,已有数以万计的 star 为 Milvus 而亮。2022 年 4 月,Milvus 在 GitHub 的 …...

Linux 文件类型,目录与路径,文件与目录管理
文件类型 后面的字符表示文件类型标志 普通文件:-(纯文本文件,二进制文件,数据格式文件) 如文本文件、图片、程序文件等。 目录文件:d(directory) 用来存放其他文件或子目录。 设备…...

springboot 百货中心供应链管理系统小程序
一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...
大语言模型(LLM)中的KV缓存压缩与动态稀疏注意力机制设计
随着大语言模型(LLM)参数规模的增长,推理阶段的内存占用和计算复杂度成为核心挑战。传统注意力机制的计算复杂度随序列长度呈二次方增长,而KV缓存的内存消耗可能高达数十GB(例如Llama2-7B处理100K token时需50GB内存&a…...
Caliper 配置文件解析:fisco-bcos.json
config.yaml 文件 config.yaml 是 Caliper 的主配置文件,通常包含以下内容: test:name: fisco-bcos-test # 测试名称description: Performance test of FISCO-BCOS # 测试描述workers:type: local # 工作进程类型number: 5 # 工作进程数量monitor:type: - docker- pro…...
【LeetCode】3309. 连接二进制表示可形成的最大数值(递归|回溯|位运算)
LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 题目描述解题思路Java代码 题目描述 题目链接:LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 给你一个长度为 3 的整数数组 nums。 现以某种顺序 连接…...
6个月Python学习计划 Day 16 - 面向对象编程(OOP)基础
第三周 Day 3 🎯 今日目标 理解类(class)和对象(object)的关系学会定义类的属性、方法和构造函数(init)掌握对象的创建与使用初识封装、继承和多态的基本概念(预告) &a…...
土建施工员考试:建筑施工技术重点知识有哪些?
《管理实务》是土建施工员考试中侧重实操应用与管理能力的科目,核心考查施工组织、质量安全、进度成本等现场管理要点。以下是结合考试大纲与高频考点整理的重点内容,附学习方向和应试技巧: 一、施工组织与进度管理 核心目标: 规…...
使用python进行图像处理—图像滤波(5)
图像滤波是图像处理中最基本和最重要的操作之一。它的目的是在空间域上修改图像的像素值,以达到平滑(去噪)、锐化、边缘检测等效果。滤波通常通过卷积操作实现。 5.1卷积(Convolution)原理 卷积是滤波的核心。它是一种数学运算,…...

react更新页面数据,操作页面,双向数据绑定
// 路由不是组件的直接跳转use client,useEffect,useRouter,需3个结合, use client表示客户端 use client; import { Button,Card, Space,Tag,Table,message,Input } from antd; import { useEffect,useState } from react; impor…...
使用 uv 工具快速部署并管理 vLLM 推理环境
uv:现代 Python 项目管理的高效助手 uv:Rust 驱动的 Python 包管理新时代 在部署大语言模型(LLM)推理服务时,vLLM 是一个备受关注的方案,具备高吞吐、低延迟和对 OpenAI API 的良好兼容性。为了提高部署效…...