基於Adapter的Excel庫
前言
Excel的功能應該說很常見了,也有很多有名的庫,比如說阿里的EasyExcel等。
之前一段時間在專案上也遇到了excel的相關功能,再早些時候其實也接觸過poi這塊,只不過基本上都是複製貼上改一改能用就行,那時應該還沒有一些比較好的庫。然後這次遇上的時候我也上github上找了找有沒有什麼成熟的庫(話說poi難道還不成熟???)主要還是阿里的star比較多。
之後我就看了看EasyExcel的用法,說實話,我感覺高階用法的檔案有點缺,可能是我沒找到或者檔案還沒有完善,至少我當時想的,如果我有一些特殊的資料需要自定義一些單元格的資料該怎麼辦。畢竟會有這種情況,但是我大致看了也沒發現有對應的檔案所以就沒有用他們的庫。
再加上當時我正好有一個思路,就打算自己封裝一個看看,於是就有了
大家可以想象一下excel的檔案,是不是就是有很多單元格然後每個單元格填充了一些資料。因為我之前是做Android的,所以我突然發現這和Android裡面的GridView特別的像,也就是網格佈局。其實熟悉Android的人都知道,Android裡面類似這種list或grid的資料填充都是使用adapter的模式,而excel其實就相當於給每一個單元格(View)進行資料填充。
思路分析
-
Adapter為核心
public interface WriteAdapter {
Object getData (int sheet,int row,int cell);//對應單元格的資料
String getSheetName(int sheet);//對應sheet的名稱
int getSheetCount();//有多少sheet
int getRowCount(int sheet);//對應的sheet有多少行
int getCellCount(int sheet,int row);//對應sheet和對應行有多少列
}
public interface ReadAdapter {
void readCell(Object value,int s,int r,int c,int sCount,int rCount,int cCount);//對應位置的資料
Object getValue();//讀取的資料
}
複製程式碼
首先是來說寫excel,我只需要你提供一些基本的幾行幾列,然後把每個單元格的資料返回給我就行,至於填充資料就不是adapter該做的事了。 然後是讀excel,我會把每個單元格的資料都讀出來給你,你要怎麼組裝完全可以自定義。 可以看到adpater不涉及任何poi的類。
-
ValueSetter和ValueGetter
public interface ValueSetter {
void setValue(int s,Cell cell,Row row,Sheet sheet,Drawing<?> drawing,Workbook workbook,CreationHelper creationHelper,Object value);
}
public interface ValueGetter {
Object getValue(int s,CreationHelper creationHelper);
}
複製程式碼
ValueSetter的作用是把資料塞到poi裡面。
ValueGetter的作用是把資料從poi中讀出來。
可以說是資料和view的一層連線。
-
ValueConverter
public interface ValueConverter extends SupportOrder,SupportCache {
boolean supportValue(int sheet,int cell,Object value);
Object convertValue(int sheet,Object value);
}
複製程式碼
poi只支援一些有限的資料型別,所以我們還是需要一些轉換器來將我們複雜的資料型別轉換成poi可以設定讀取的型別,甚至包括一個單元格內同時有填充的資料,圖片,註釋等肯定需要一些自定義處理。
-
業務流程方法
private static void transfer(Workbook workbook,WriteAdapter writeAdapter,List<PoiListener> poiListeners,List<ValueConverter> valueConverters,ValueSetter valueSetter) {
CreationHelper creationHelper = workbook.getCreationHelper();
int sheetCount = writeAdapter.getSheetCount();
for (int s = 0; s < sheetCount; s++) {
String sheetName = writeAdapter.getSheetName(s);
Sheet sheet;
if (sheetName == null) {
sheet = workbook.createSheet();
} else {
sheet = workbook.createSheet(sheetName);
}
Drawing<?> drawing = sheet.createDrawingPatriarch();
int rowCount = writeAdapter.getRowCount(s);
for (int r = 0; r < rowCount; r++) {
Row row = sheet.createRow(r);
int cellCount = writeAdapter.getCellCount(s,r);
for (int c = 0; c < cellCount; c++) {
Cell cell = row.createCell(c);
Object o = writeAdapter.getData(s,r,c);
Object value = convertValue(valueConverters,s,c,o);
valueSetter.setValue(s,cell,row,sheet,drawing,workbook,creationHelper,value);
}
}
}
}
private static Object analyze(Workbook workbook,ReadAdapter readAdapter,ValueGetter valueGetter,boolean close) throws IOException {
CreationHelper creationHelper = workbook.getCreationHelper();
int sCount = workbook.getNumberOfSheets();
for (int s = 0; s < sCount; s++) {
Sheet sheet = workbook.getSheetAt(s);
Drawing<?> drawing = sheet.getDrawingPatriarch();
int rCount = sheet.getLastRowNum() + 1;
for (int r = 0; r < rCount; r++) {
Row row = sheet.getRow(r);
int cCount = row.getLastCellNum();
for (int c = 0; c < cCount; c++) {
Cell cell = row.getCell(c);
Object o = valueGetter.getValue(s,creationHelper);
Object cellValue = convertValue(valueConverters,o);
readAdapter.readCell(cellValue,sCount,rCount,cCount);
}
}
}
return readAdapter.getValue();
}
複製程式碼
上面就是寫和讀對應的一個主要邏輯程式碼(部分刪減),我就不做過多的解讀了,應該都能看懂。
當然實際上這個庫還有很多的一個適配,上述程式碼只是一個主要的邏輯思想。
我的專案現在用的就是我自己封裝的這個庫,主要是有bug容易改,再一個就是我可以說自定義Adapter加上ValueConverter相當於可以適配所有的資料結構和特殊情況了。
用法說明
目前這個庫裡我其實擴充套件了一些adapter可以直接使用,比如支援List+Bean上註解的通用方式實現匯出,或者是一個單元格內同時寫入或讀取資料,圖片,註釋的支援,或是自適應列寬等,下面羅列了一些具體的基本用法。
依賴
implementation 'com.github.linyuzai:jpoi:0.1.0'
複製程式碼
基本用法
JExcel.xlsx().setWriteAdapter(WriteAdapter).write().to(File/OutputStream);//寫
Object v = JExcel.xlsx(InputStream).setReadAdapter(ReadAdapter).read().getValue();//讀
複製程式碼
支援用法
JExcel.xlsx().data(List...).write().to(File/OutputStream);//寫類
List<List<Class>> v = JExcel.xlsx(InputStream).target(Class).read().getValue();//讀類
List<List<Map<String,Object>>> v = JExcel.xlsx(InputStream).toMap().read().getValue();//讀map
List<List<List<Object>>> v = JExcel.xlsx(InputStream).direct().read().getValue();//讀list
複製程式碼
全部用法
JExcel
.xls()//HSSFWorkbook
.xlsx()//XSSFWorkbook
.sxlsx()//SXSSFWorkbook
.data()//list bean
.setWriteAdapter()//自定義WriteAdapter
.addPoiListener()//poi監聽器
.addValueConverter()//value轉換器
.setValueSetter()//value寫入poi的支援類
.write()//執行寫入
.to();//輸出
JExcel
.xls(InputStream)//HSSFWorkbook
.xlsx(InputStream)//XSSFWorkbook
.sxlsx(InputStream)//自定義Sax寫法,暫不完善,無法使用
.target()//轉成bean
.toMap()//轉成map
.direct()//轉成list
.setReadAdapter()//自定義ReadAdapter
.addPoiListener()//poi監聽器
.addValueConverter()//value轉換器
.setValueGetter()//poi讀取value的支援類
.read()//執行讀取
.getValue();//獲得值
複製程式碼
註解加類寫
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface JExcelSheetWriter {
/**
* @return sheet名稱
*/
String name() default "";
/**
* @return 是否只處理添加了註解的欄位
*/
@Deprecated
boolean annotationOnly() default true;
/**
* @return 樣式
*/
JExcelRowStyle style() default @JExcelRowStyle;
}
複製程式碼
@Target({ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface JExcelCellWriter {
/**
* @return 每一列的標題
*/
String title() default "";
/**
* @return value轉換器
*/
Class<? extends ValueConverter> valueConverter() default ValueConverter.class;
/**
* @return 是否自適應列寬
*/
boolean autoSize() default true;
/**
* @return 指定寬度
*/
int width() default 0;
/**
* @return 排序
*/
int order() default Integer.MAX_VALUE;
/**
* @return 作為對應欄位的註釋
*/
String commentOfField() default "";
/**
* @return 作為對應index列的註釋
*/
int commentOfIndex() default -1;
/**
* @return 作為對應欄位的圖片
*/
String pictureOfFiled() default "";
/**
* @return 作為對應index的圖片
*/
int pictureOfIndex() default -1;
@Deprecated
String standbyFor() default "";
/**
* @return 樣式
*/
JExcelCellStyle style() default @JExcelCellStyle;
}
複製程式碼
JExcel.xlsx().data(List...).write().to(File/OutputStream);//寫類
複製程式碼
註解加類讀
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface JExcelSheetReader {
@Deprecated
String name() default "";
/**
* @return 是否只處理添加了註解的欄位
*/
@Deprecated
boolean annotationOnly() default true;
/**
* @return 是否轉成map
*/
boolean toMap() default false;
}
複製程式碼
@Target({ElementType.METHOD,ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface JExcelCellReader {
/**
* @return 每一列的標題
*/
String title() default "";
/**
* @return value轉換器
*/
Class<? extends ValueConverter> valueConverter() default ValueConverter.class;
@Deprecated
int index() default -1;
/**
* @return 作為對應欄位的註釋
*/
String commentOfField() default "";
/**
* @return 作為對應index列的註釋
*/
int commentOfIndex() default -1;
/**
* @return 作為對應欄位的圖片
*/
String pictureOfFiled() default "";
/**
* @return 作為對應index的圖片
*/
int pictureOfIndex() default -1;
}
複製程式碼
List<List<Class>> v = JExcel.xlsx(InputStream).target(Class).read().getValue();//讀類
複製程式碼
注意事項
-
commentOfField
commentOfIndex
pictureOfFiled
pictureOfIndex
只用於支援一個單元格內的多個資料(內容,註釋,圖片)對應bean的多個欄位,如果只想寫入一個值或讀取一個值,請直接設定ValueConverter
結束
最後感謝您的閱讀,如果有什麼更好的建議或是使用中有任何問題可以直接提issue,並且我也會持續維護這個專案。另外提一點,如果資料量真的很大的情況下,本人還是建議使用阿里的EasyExcel,對於記憶體佔用做了很大的優化。