1. 程式人生 > 程式設計 >基於Adapter的Excel庫

基於Adapter的Excel庫

前言

       Excel的功能應該說很常見了,也有很多有名的庫,比如說阿里的EasyExcel等。
       之前一段時間在專案上也遇到了excel的相關功能,再早些時候其實也接觸過poi這塊,只不過基本上都是複製貼上改一改能用就行,那時應該還沒有一些比較好的庫。然後這次遇上的時候我也上github上找了找有沒有什麼成熟的庫(話說poi難道還不成熟???)主要還是阿里的star比較多。
       之後我就看了看EasyExcel的用法,說實話,我感覺高階用法的檔案有點缺,可能是我沒找到或者檔案還沒有完善,至少我當時想的,如果我有一些特殊的資料需要自定義一些單元格的資料該怎麼辦。畢竟會有這種情況,但是我大致看了也沒發現有對應的檔案所以就沒有用他們的庫。
       再加上當時我正好有一個思路,就打算自己封裝一個看看,於是就有了

jpoi這個庫,嚴格來說還不能說庫,只能說一個待完善的工具。所以這篇文章我就主要講述一下封裝的思路。
       大家可以想象一下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,對於記憶體佔用做了很大的優化。