前言
在我們的項目需求中,經常會遇到導出的需求,其中excel的導出最為常見。生成Excel比較有名的框架有Apache poi,jxl等,但他們都存在一個嚴重的問題就是非常的耗內存,如果你的系統(tǒng)并發(fā)量不大的話可能還行,但是一旦并發(fā)上來后一定會OOM或者JVM頻繁的full gc.
一、EasyExcel特點
EasyExcel是阿里巴巴開源的一個excel處理框架,以使用簡單,節(jié)省內存著稱,
64M內存1分鐘內讀取75M(46W行25列)的Excel(當然還有急速模式能更快,但是內存占用會在100M多一點)。
EasyExcel能大大減少占用內存的主要原因是在解析Excel時沒有將文件數(shù)據一次性全部加載到內存中,而是從磁盤上一行行讀取數(shù)據,逐個解析。
不支持的功能
1、單個文件的并發(fā)寫入、
2、讀取讀取圖片
3、宏
4、csv讀?。ㄟ@個后續(xù)可能會考慮)
三、常見問題
1、讀取文件務必使用2.0.5+(現(xiàn)在項目中用的是2.2.10)
2、讀寫反射對象用到了Cglib動態(tài)代理,所以成員變量必須符合駝峰規(guī)范,而且使用@Data不能使用@Accessors(chain = true)。后續(xù)會考慮支持非駝峰。
3、出現(xiàn) NoSuchMethodException, ClassNotFoundException, NoClassDefFoundError。極大概率是jar沖突,建議clean項目,或者統(tǒng)一poi 的版本,理論上來說easyexcel兼容poi的3.17,4.0.1,4.1.0所有較新版本
4、用String去接收數(shù)字,出現(xiàn)小數(shù)點等情況這個是BUG,但是很難修復,后續(xù)版本會修復這個問題。目前請使用@NumberFormat注解,里面的參數(shù)就是調用了java自帶的NumberFormat.format方法,不知道怎么入參的可以自己網上查詢。
easyExcel的官方文檔地址:https://alibaba-easyexcel.github.io/index.html
四、常用注解
4-1、讀
ExcelProperty 指定當前字段對應excel中的那一列??梢愿鶕只蛘逫ndex去匹配。當然也可以不寫,默認第一個字段就是index=0,以此類推。千萬注意,要么全部不寫,要么全部用index,要么全部用名字去匹配。千萬別三個混著用,除非你非常了解源代碼中三個混著用怎么去排序的。
@Getter
@Setter
@EqualsAndHashCode
public class IndexOrNameData{
//強制讀取第三個這里不建議 index 和 name 同時用,要么一個對象只用index,要么一個對象只用name去山配*
@ExcelProperty(index = 2)
private Double doubleData
/*用名字去匹配,這里需要注意,如果名字重復會導致只有一個字殷讀取到數(shù)據@ExcelProperty ("字符串標題”) */
private Stringstring;
@ExcelProperty ("日期標題")
private Date date;
}
ExcelIgnore 默認所有字段都會和excel去匹配,加了這個注解會忽略該字段
//強制讀取第三個這里不建議 index 和 name 同時用,要么一個對象只用index,要么一個對象只用name去山配*
@ExcelIgnore
private Double doubleData
DateTimeFormat 日期轉換,用String去接收excel日期格式的數(shù)據會調用這個注解。里面的value參照java.text.SimpleDateFormat。
@DateTimeFormat("yyyy年MM月dd日HH時mm分ss秒")
private String date;
NumberFormat 數(shù)字轉換,用String去接收excel數(shù)字格式的數(shù)據會調用這個注解。里面的value參照java.text.DecimalFormat。
@NumberFormat("#.##%")
private String doubleData; //接收百比的數(shù)字
4-2、寫
ExcelProperty index 指定寫到第幾列,默認根據成員變量排序。value指定寫入的名稱,默認成員變量的名字,多個value可以參照快速開始中的復雜頭
ExcelIgnore 默認所有字段都會寫入excel,這個注解會忽略這個字段
DateTimeFormat 日期轉換,將Date寫到excel會調用這個注解。里面的value參照java.text.SimpleDateFormat
NumberFormat 數(shù)字轉換,用Number寫excel會調用這個注解。里面的value參照java.text.DecimalFormat
ExcelIgnoreUnannotated 默認不加ExcelProperty 的注解的都會參與讀寫,加了不會參與
五、EasyExcel的使用
1、依賴
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.10</version>
</dependency>
2、讀excel
2.1最簡單的
對象
@Data
public class DemoData {
private String string;
private Date date;
private Double doubleData;
}
controller類
@PostMapping("/outstoragcExce1")
@Apioperation("讀取出庫excel表")
public DeviceResponse storageservice(@RequestBody MultipartFile file) {
try{
storageservice.storageservice(file);
} catch (Exception e){
return new DeviceResponse(Constant.FAIL CODE,"出庫導失敗");
}
return new DeviceResponse(Constant.SUCCESS CODE,"出庫導入成功");
}
service實現(xiàn)類
@Autowired
private StorageService storageServicel
@override
publil void slorageservice(MulliparlFile file) {
Tnnutstream is = null:
try{
is=file.getInputstream();
} catch (IDException e){
e.printstackTrace();
}
//1.進行讀取數(shù)數(shù)據,slorageReLrieval是我的puju類,
//2.new Soragelistenpr(storagpServire)這個是監(jiān)聽器,主要用來i取數(shù)據的,別急后面會講
//3.特別注意的是storageservice這個service,我上面有注入進去 @Autowired,切記不要new會報錯
EasyExcel.read(is,StorageRetrieval.class, new Soragelisterer(storageService))sheet().doRead();
}
SorageListener監(jiān)聽器
@Component
public class SorageListener extends AnalysisEventListener<pojo類> {
private static final Logger LOGGER = LoggerFactory.getLogger(SorageListener.class);
//讀取數(shù)據初始化值
private static final int BATCH_COUNT = 50;
List<pojo類> list = new ArrayList<pojo類>();
private StorageService storageService;
public SorageListener() {
storageService=new StorageServiceImpl();
}
public SorageListener(StorageService storageService) {
this.storageService = storageService;
}
/**
* 這個每一條數(shù)據解析都會來調用,數(shù)據是一條一條進行解析的
*
* @param data one row value. Is is same as {@link AnalysisContext#readRowHolder()}
* @param context
*/
@Override
public void invoke(StorageRetrieval data, AnalysisContext context) {
list.add(data);
// 達到BATCH_COUNT了,需要去存儲一次數(shù)據庫,防止數(shù)據幾萬條數(shù)據在內存,容易OOM
if (list.size() >= BATCH_COUNT) {
saveData();
// 存儲完成清理 list
list.clear();
}
}
/**
* 所有excel表中數(shù)據解析完成了 都會來調用這個
* 解釋為什么要保存數(shù)據?
*初始化讀取數(shù)量為50,表中信息已經加載完畢,,假設excel表中最后只剩下30行遺留數(shù)據,所以為了防止存在遺留數(shù)據 盡量判斷下集合是否為空,不為空在進行存儲(這是我的邏輯需要判斷,如果不需要也可進行不判斷)
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
if(list.size()==0){
return;
}
saveData();
LOGGER.info("所有數(shù)據解析完成!");
}
/**
* 加上存儲數(shù)據庫
*/
public void saveData() {
storageService.save(list); //代碼實現(xiàn)類層保存數(shù)據
LOGGER.info("存儲數(shù)據庫成功!");
}
}
2.2、指定列的下標或者列名
@Data
public class IndexOrNameData {
/**
* 強制讀取第三個 這里不建議 index 和 name 同時用,要么一個對象只用index,要么一個對象只用name去匹配
*/
@ExcelProperty(index = 2)
private Double doubleData;
/**
* 用名字去匹配,這里需要注意,如果名字重復,會導致只有一個字段讀取到數(shù)據
*/
@ExcelProperty("字符串標題")
private String string;
@ExcelProperty("日期標題")
private Date date;
}
/**
* 指定列的下標或者列名
*
* <p>1. 創(chuàng)建excel對應的實體對象,并使用{@link ExcelProperty}注解. 參照{@link IndexOrNameData}
* <p>2. 由于默認一行行的讀取excel,所以需要創(chuàng)建excel一行一行的回調監(jiān)聽器,參照{@link IndexOrNameDataListener}
* <p>3. 直接讀即可
*/
@Test
public void indexOrNameRead() {
String fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
// 這里默認讀取第一個sheet
EasyExcel.read(fileName, IndexOrNameData.class, new IndexOrNameDataListener()).sheet().doRead();
}
3、寫excel
3.1最簡單的
實體對象
@Data
@piModel(value = "年齡統(tǒng)計實體類”
public class FToWAgeStatisticalVo implements Serializable {
private static final long serialVersionUID = -7891558029837989473L;
@ApiModelProperty("區(qū)間")
@ExcelProperty(value = "區(qū)間")
private String ageGap;
@ApiModeLProperty("病例數(shù)”)
@ExcelProperty(value ="病例數(shù))
private Integer casesNumber ;
@ApiModeProperty("密接數(shù)”)
@ExceProperty(value = "密接數(shù)”)
private Integer closeNumber ;
}
service實現(xiàn)
@Override
public void avestatisticalExcel(Httpservlethesponse resonse,FlowReionStatisticalParam flowReionStatisticalParam) throws Exception{
//這里文件名如果涉及中文一定要使用URL編碼,否則會亂碼
String fileName = URLEncoder.encode( s: "floWAgeStatistical.xlsx" StandardCharsets.UTF_8.toString());
List<FloWAgeStatisticalVo> data = ageStatistical(flowRegionStatisticalParam);
response.setContentType("application/force-download");
response.setcharacterEncoding("utf-8");
response.setHeader( s: "Content-Disposition", s1: "attachment;filename=" + fileName);
EasyExcel.write(response.getoutputstream(),FLoWAgeStatisticalVo.class)
.autoclosestream(true)
.exceType(ExcelTypeEnum.XLSX)
.sheet( sheetName: "年齡統(tǒng)計表")
.doWrite(data) ;
}
3.2、列寬、行高
@Data
@ContentRowHeight(10)
@HeadRowHeight(20)
@ColumnWidth(25)
public class WidthAndHeightData {
@ExcelProperty("字符串標題")
private String string;
@ExcelProperty("日期標題")
private Date date;
/**
* 寬度為50
*/
@ColumnWidth(50)
@ExcelProperty("數(shù)字標題")
private Double doubleData;
}
3.3、合并單元格
@Getter
@Setter
@EqualsAndHashCode
// 將第6-7行的2-3列合并成一個單元格
// @OnceAbsoluteMerge(firstRowIndex = 5, lastRowIndex = 6, firstColumnIndex = 1, lastColumnIndex = 2)
public class DemoMergeData {
// 這一列 每隔2行 合并單元格
@ContentLoopMerge(eachRow = 2)
@ExcelProperty("字符串標題")
private String string;
@ExcelProperty("日期標題")
private Date date;
@ExcelProperty("數(shù)字標題")
private Double doubleData;
}
/**
* 合并單元格
* <p>1. 創(chuàng)建excel對應的實體對象 參照{@link DemoData}
* <p>2. 創(chuàng)建一個merge策略 并注冊
* <p>3. 直接寫即可
*/
@Test
public void mergeWrite() {
String fileName = TestFileUtil.getPath() + "mergeWrite" + System.currentTimeMillis() + ".xlsx";
// 每隔2行會合并 把eachColumn 設置成 3 也就是我們數(shù)據的長度,所以就第一列會合并。當然其他合并策略也可以自己寫
LoopMergeStrategy loopMergeStrategy = new LoopMergeStrategy(2, 0);
// 這里 需要指定寫用哪個class去寫,然后寫到第一個sheet,名字為模板 然后文件流會自動關閉
EasyExcel.write(fileName, DemoData.class).registerWriteHandler(loopMergeStrategy).sheet("模板")
.doWrite(data());
}
3.4、復雜頭寫入
@Data
@ApiModel("學校學生缺勤信息")
public class SchoolAnalyseVo {
@ApiModelProperty("學校Id")
@ExcelIgnore()
private Long schoolId;
@ExcelProperty("學校")
private String schoolName;
@ExcelProperty("學校類型")
private String schoolType;
..........
@ExcelProperty({"癥狀", "發(fā)熱"})
private String fever;
@ExcelProperty({"癥狀", "咳嗽"})
private String cough;
@ExcelProperty({"癥狀", "頭痛"})
private String headache;
.........
@ExcelProperty({"疾病","普通感冒", "人數(shù)"})
private String commonColdNumber ;
@ExcelProperty({"疾病","普通感冒", "因病缺勤率"})
private String commonColdRate ;
@ExcelProperty({"疾病","流感", "人數(shù)"})
private String influenzaNumber;
.........
}
3.5、日期、數(shù)字或者自定義格式轉換
@Data
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 = "數(shù)字標題")
private Double doubleData;
}
自定義轉換器
public class CustomStringStringConverter implements Converter<String> {
@Override
public Class supportJavaTypeKey() {
return String.class;
}
@Override
public CellDataTypeEnum supportExcelTypeKey() {
return CellDataTypeEnum.STRING;
}
/**
* 這里讀的時候會調用
*
* @param cellData
* @param contentProperty
* @param globalConfiguration
* @return
*/
@Override
public String convertToJavaData(CellData cellData, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
return "自定義:" + cellData.getStringValue();
}
/**
* 這里是寫的時候會調用 不用管
*
* @param value
* @param contentProperty
* @param globalConfiguration
* @return
*/
@Override
public CellData convertToExcelData(String value, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
return new CellData(value);
}
}
3.6、指定寫入列
@Getter
@Setter
@EqualsAndHashCode
public class IndexData {
@ExcelProperty(value = "字符串標題", index = 0)
private String string;
@ExcelProperty(value = "日期標題", index = 1)
private Date date;
/**
* 這里設置3 會導致第二列空的
*/
@ExcelProperty(value = "數(shù)字標題", index = 3)
private Double doubleData;
}
3.7、其他讀操作
https://www.yuque.com/easyexcel/doc/write
4、填充excel
4.1 最簡單的填充
對象
@Getter
@Setter
@EqualsAndHashCode
public class FillData{
private string name;
private double number;
private Date date;
}
代碼文章來源:http://www.zghlxwxcb.cn/news/detail-618338.html
/*最簡單的填充
* @since 2.1.1
*/
@Test
public void simpleFill() [
// 模板注 用]來表示你要用的變量 如果本來就有””,”]”特殊字符 用””]"代替
String templateFileName =TestFileUtil.getPath() + "demo" + File.separator + "fill" + File.separator + "simple.xlsx";
// 方案1 根據對象填充
String fileName = TestFileUtil,getPath() + "simpleFill" + System,currentTimeMillis() + ".xlsx",
// 這里 會填充到第一個sheet, 然后文件流會自動關閉
FillData fillData = new FillData();
fillData.setName("張一");
fillData.setNumber(5.2);
EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(fillData);
// 方案2 根據Map填充
fileName = TestFileUtil.getPath() + "simpleFill" + System.currentTimeMillis() + ".xlsx"
// 這里 會填充到第一個sheet, 然后文件流會自動關閉
Map<string, Object> map = new HashMap<string, Object>();
map.put("name”,"張二");
map .put("number", 5.2) :
EasyExcel.write(fileName).withTemplate(templateFileName).sheet().doFill(map);
}
4.2、其他填充
填充列表、復雜的填充、數(shù)據量大的復雜填充、橫向的填充、多列表組合填充填充
https://www.yuque.com/easyexcel/doc/fill文章來源地址http://www.zghlxwxcb.cn/news/detail-618338.html
到了這里,關于java實現(xiàn)excel的導出之使用easyExcel的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網!