基于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, int r, int c, Cell cell, Row row, Sheet sheet, Drawing<?> drawing, Workbook workbook, CreationHelper creationHelper, Object value);
}

public interface ValueGetter {

    Object getValue(int s, int r, int c, Cell cell, Row row, Sheet sheet, Drawing<?> drawing, Workbook workbook, CreationHelper creationHelper);
}

ValueSetter的作用是把數據塞到poi里面。
ValueGetter的作用是把數據從poi中讀出來。
可以說是數據和view的一層連接。

  • ValueConverter
public interface ValueConverter extends SupportOrder, SupportCache {

    boolean supportValue(int sheet, int row, int cell, Object value);

    Object convertValue(int sheet, int row, int cell, 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, r, c, o);
                valueSetter.setValue(s, r, c, cell, row, sheet, drawing, workbook, creationHelper, value);
            }
        }
    }
  }

private static Object analyze(Workbook workbook, ReadAdapter readAdapter, List<PoiListener> poiListeners,
                                  List<ValueConverter> valueConverters, 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, r, c, cell, row, sheet, drawing, workbook, creationHelper);
                Object cellValue = convertValue(valueConverters, s, r, c, o);
                readAdapter.readCell(cellValue, s, r, c, sCount, rCount, cCount);
            } 
        }
    }
    return readAdapter.getValue();
}

上面就是寫和讀對應的一個主要邏輯代碼(部分刪減),我就不做過多的解讀了,應該都能看懂。
當然實際上這個庫還有很多的一個適配,上述代碼只是一個主要的邏輯思想。
我的項目現在用的就是我自己封裝的這個庫,主要是有bug容易改,再一個就是我可以說自定義Adapter加上ValueConverter相當于可以適配所有的數據結構和特殊情況了。

用法說明

目前這個庫里我其實擴展了一些adapter可以直接使用,比如支持List<Bean>+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,對于內存占用做了很大的優化。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。