天天看點

POI & easyExcel快速使用

常用資訊
  1. 将使用者資訊導出為Excel表格(導出資料)
  2. 将Excel表中的資訊錄入到網站資料庫(習題上傳…),可以大大減輕網站錄入量

開發中經常涉及到Excel的處理,如導出Excel,導入資料到Excel中

操作Excel目前比較流行的就是 Apache POI 和 阿裡巴巴的 easyExcel

Apache POI

Apache POI 官網 : https://poi.apache.org/

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-eGWtRorg-1622371513711)(https://img2020.cnblogs.com/blog/2233039/202105/2233039-20210530182529061-1519557148.png)]

POI(Poor Obfuscation Implementation),直譯為“可憐的模糊實作”,利用POI接口可以通過

Java

操作 Microsoft office 套件工具的讀寫功能。POI支援office的所有版本。

基本功能

結構:

HSSF - 提供讀寫Microsoft Excel格式檔案的功能。(03版本,行數最多支援65536行)

XSSF - 提供讀寫Microsoft Excel OOXML格式檔案的功能。(07版本,行數無限制)

HWPF - 提供讀寫Microsoft Word格式檔案的功能。

HSLF - 提供讀寫Microsoft PowerPoint格式檔案的功能。

HDGF - 提供讀寫Microsoft Visio格式檔案的功能。

POI 較為原生和複雜,操作起來比較麻煩,POI 會将資料先加載到記憶體中,然後再寫入到檔案中,處理大量資料時可能出現OOM(Out Of Memory,記憶體溢出)問題。

easyExcel

easyExcel 官網: https://github.com/alibaba/easyexcel

官方文檔:https://www.yuque.com/easyexcel/doc/easyexcel

介紹

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-QzD18Zjx-1622371513716)(https://img2020.cnblogs.com/blog/2233039/202105/2233039-20210530183743793-2035327558.png)]

easyExcel 是阿裡巴巴開源的一個Excel處理架構,以使用簡單,節省記憶體著稱。

easyExcel能大大減少占用記憶體的主要原因是在解析Excel時沒有将檔案資料一次性全部加載到記憶體中,而是從磁盤上一行行讀取資料,進行逐個解析。

easyExcel 和 POI 在解析Excel時的對比圖

POI & easyExcel快速使用

POI - Excel 寫

建立一個項目,建立普通Maven的Module

導入依賴

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.shiguang</groupId>
    <artifactId>poi-easyExcel</artifactId>
    <version>1.0-SNAPSHOT</version>

    <!--導入依賴-->
    <dependencies>
        <!-- xls (03) -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>4.1.2</version>
        </dependency>
        <!-- xlsx (07) -->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>4.1.2</version>
        </dependency>
        <!-- 日期格式化工具 -->
        <dependency>
            <groupId>joda-time</groupId>
            <artifactId>joda-time</artifactId>
            <version>2.10.8</version>
        </dependency>
        <!-- 單元測試 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>
           

注意:2003和2007版本存在相容性的問題!03版本最多隻有65535行!

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-B589QY0P-1622371513724)(https://img2020.cnblogs.com/blog/2233039/202105/2233039-20210530183024518-1536589830.png)]

可以将Excle 抽象出工作簿,工作表,行,列 這幾個對象

Workbook接口下的實作類

POI &amp; easyExcel快速使用

示例代碼

package com.shiguang;


import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.joda.time.DateTime;
import org.junit.Test;


import java.io.FileOutputStream;
import java.io.IOException;

public class ExcelWriteTest {
    String PATH = "D:\\Desktop\\"; //表輸出路徑
    @Test
    public void testWrite03() throws IOException {
        //1.建立一個工作簿
        Workbook workbook = new HSSFWorkbook();
        //2.建立一個工作表
        Sheet sheet = workbook.createSheet("時光收支統計表");
        //3.建立一個行
        Row row1 = sheet.createRow(0);
        //4.建立一個單元格

        // 第一行一列資料(1,1)
        Cell cell11 = row1.createCell(0);
        cell11.setCellValue("今日新增收入");
        //第一行二列資料(1,2)
        Cell cell12 = row1.createCell(1);
        cell12.setCellValue("統計時間");

        //第二行
        Row row2 = sheet.createRow(1);
        //第二行第一列(2,1)
        Cell cell21 = row2.createCell(0);
        cell21.setCellValue("888");
        //第二行第二列(2,2)
        Cell cell22 = row2.createCell(1);
        String time = new DateTime().toString("yyyy-MM-dd HH:mm:ss");
        cell22.setCellValue(time);

        //生成一張表(IO操作,需要使用流) 03版本使用xls結尾,
        FileOutputStream fileOutputStream = new FileOutputStream(PATH + "時光收支情況統計表03.xls");
        //輸出
        workbook.write(fileOutputStream);

        //關閉流
        fileOutputStream.close();
        System.out.println("Excle檔案生成完畢ヾ(•ω•`)o");

    }



}

           

效果

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-11dLLlxm-1622371513730)(https://img2020.cnblogs.com/blog/2233039/202105/2233039-20210530183214360-1707090783.png)]

07版本隻需要将對象該為

XSSFWorkbook()

,并将表名稱字尾改為

xlsx

即可

資料批量導入

大檔案寫HSSF

示例代碼

@Test
    public void testWrite03BigData() throws IOException {
        long begin = System.currentTimeMillis();
        //建立一個工作簿
        Workbook workbook = new HSSFWorkbook();
        //建立表
        Sheet sheet = workbook.createSheet();
        //寫入資料
        for(int rowNum = 0; rowNum<65536;rowNum++){
            Row row = sheet.createRow(rowNum);
            for (int cellNum = 0; cellNum < 10; cellNum++) {
                Cell cell = row.createCell(cellNum);
                cell.setCellValue(cellNum);
            }
        }
        System.out.println("工作表建立成功ヾ(^▽^*)))");
        FileOutputStream OutputStream = new FileOutputStream(PATH + "大資料測試03.xls");
        workbook.write(OutputStream);
        OutputStream.close();
        long end = System.currentTimeMillis();
        System.out.println((double) (end-begin)/1000);
    }
           

缺點:最多隻能處理

65536

行資料,否則會抛出異常

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-BBH0Drei-1622371513733)(https://img2020.cnblogs.com/blog/2233039/202105/2233039-20210530183255295-181106482.png)]

優點: 先寫入緩存,最後一次性寫入磁盤,速度快

大檔案寫XSSF

示例代碼

@Test
    public void testWrite07BigData() throws IOException {
        long begin = System.currentTimeMillis();
        //建立一個工作簿
        Workbook workbook = new XSSFWorkbook();
        //建立表
        Sheet sheet = workbook.createSheet();
        //寫入資料
        for(int rowNum = 0; rowNum<100000;rowNum++){
            Row row = sheet.createRow(rowNum);
            for (int cellNum = 0; cellNum < 10; cellNum++) {
                Cell cell = row.createCell(cellNum);
                cell.setCellValue(cellNum);
            }
        }
        System.out.println("工作表建立成功ヾ(^▽^*)))");
        FileOutputStream OutputStream = new FileOutputStream(PATH + "大資料測試07.xlsx");
        workbook.write(OutputStream);
        OutputStream.close();
        long end = System.currentTimeMillis();
        System.out.println((double) (end-begin)/1000);
    }
           

缺點:寫資料時速度非常慢,非常小号記憶體,并且會發生記憶體溢出,如處理100萬條資料時

優點:可以寫較大的資料量,如20萬條

大檔案寫SXSSF

示例代碼

@Test
    public void testWrite07BigDataS() throws IOException {
        long begin = System.currentTimeMillis();
        //建立一個工作簿
        Workbook workbook = new SXSSFWorkbook();
        //建立表
        Sheet sheet = workbook.createSheet();
        //寫入資料
        for(int rowNum = 0; rowNum<100000;rowNum++){
            Row row = sheet.createRow(rowNum);
            for (int cellNum = 0; cellNum < 10; cellNum++) {
                Cell cell = row.createCell(cellNum);
                cell.setCellValue(cellNum);
            }
        }
        System.out.println("工作表建立成功ヾ(^▽^*)))");
        FileOutputStream OutputStream = new FileOutputStream(PATH + "大資料測試07S.xlsx");
        workbook.write(OutputStream);
        OutputStream.close();
        //清除臨時檔案
        ((SXSSFWorkbook) workbook).dispose();
        long end = System.currentTimeMillis();
        System.out.println((double) (end-begin)/1000);
    }
           

優點:可以寫非常大的資料量,如100萬條甚至更多條,寫資料速度快,占用更少的記憶體

注意:

過程中會産生臨時檔案,需要清理臨時檔案

預設有100條記錄被儲存在記憶體中,如果超出這個數量,則最前面的資料會被寫入臨時檔案中。

如果想自定義記憶體中資料的數量,可以使用

new SXSSFWorkbook(數量)

SXSSFWorkbook官方解釋:“BigGridDemo”政策的流式XSSFWorkbook版本,這允許寫入非常大的檔案而不會耗盡記憶體,因為任何時候隻有可配置的行部分被儲存在記憶體中。

請注意,仍可能會消耗大量記憶體,這些記憶體基于您正在使用的功能,如合并區域,注釋…這些功能仍然隻存儲在記憶體中,是以如果廣泛使用,可能需要大量記憶體。

POI-Excle 讀

03版本

表資料

POI &amp; easyExcel快速使用

示例代碼

package com.shiguang;

import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.joda.time.DateTime;
import org.junit.Test;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

public class ExcelReadTest {
    String PATH = "D:\\Desktop\\"; //Excel表所在路徑
    @Test
    public void testRead03() throws IOException {
        //1.建立一個工作簿 03

        // 擷取檔案流
        FileInputStream inputStream = new FileInputStream(PATH + "時光收支情況統計表03.xls");
        Workbook workbook = new HSSFWorkbook(inputStream);
        //2.擷取一個工作表
        Sheet sheet = workbook.getSheetAt(0);
        //擷取第一行
        Row row = sheet.getRow(1);
        //擷取第一行第一列
        Cell cell = row.getCell(0);

        //讀取值時一定要注意類型
        //getStringCellValue() 字元串類型
        //System.out.println(cell.getStringCellValue());
        //getNumericCellValue() 數值類型
        System.out.println(cell.getNumericCellValue());
        //關閉流
        inputStream.close();
    }

}

           

07 版本

示例代碼

@Test
    public void testRead07() throws IOException {
        //1.建立一個工作簿 07

        // 擷取檔案流
        FileInputStream inputStream = new FileInputStream(PATH + "時光收支情況統計表07.xlsx");
        Workbook workbook = new XSSFWorkbook(inputStream);
        //2.擷取一個工作表
        Sheet sheet = workbook.getSheetAt(0);
        //擷取第一行
        Row row = sheet.getRow(1);
        //擷取第一行第一列
        Cell cell = row.getCell(0);

        //讀取值時一定要注意類型
        //getStringCellValue() 字元串類型
        //System.out.println(cell.getStringCellValue());
        //getNumericCellValue() 數值類型
        System.out.println(cell.getNumericCellValue());
        //關閉流
        inputStream.close();
    }
           

注意:讀取值時一定要注意類型

讀取不同資料類型

示例代碼

@Test
    public void testRead07demo() throws IOException {
        //擷取檔案流
        FileInputStream fileInputStream = new FileInputStream(PATH + "銷售資訊統計表.xls");
        //建立一個工作簿
        Workbook workbook = new HSSFWorkbook(fileInputStream);
        Sheet sheet = workbook.getSheetAt(0);
        //擷取表頭資訊
        Row rowTitle = sheet.getRow(0);
        if(rowTitle != null){
            //擷取總列數
            int cellCount = rowTitle.getPhysicalNumberOfCells();
            //周遊所有列資訊
            for (int cellNum = 0; cellNum < cellCount ; cellNum++) {
                Cell cell = rowTitle.getCell(cellNum);
                if(cell != null){
                    //擷取目前列資訊資料類型
                    CellType cellType = cell.getCellType();
                    String stringCellValue = cell.getStringCellValue();
                    System.out.print(stringCellValue + "|");
                }
            }
            System.out.println();

        }

        //擷取表中的内容

        //擷取所有行數
        int rowCount = sheet.getPhysicalNumberOfRows();
        //由于第0行是表頭資訊,是以從第一行開始周遊
        for (int rowNum = 1; rowNum < rowCount ; rowNum++) {
            Row rowData = sheet.getRow(rowNum);
            if(rowData != null){
                //讀取行中的列資訊

                //擷取總列數
                //int cellCount = rowTitle.getPhysicalNumberOfCells();
                int cellCount = rowData.getPhysicalNumberOfCells();
                for (int celNum = 0; celNum <cellCount ; celNum++) {
                    System.out.print("[" + (rowNum+1) + "-" + (celNum+1) + "]");
                    Cell cell = rowData.getCell(celNum);
                    //比對列的資料類型
                    if(cell != null){
                        //擷取目前列的資料類型
                        CellType cellType = cell.getCellType();
                        String cellValue = "";
                        switch (cellType){
                            case BLANK: // 空
                                System.out.println("[BLANK]");
                                break;
                            case STRING: //字元串
                                System.out.println("[STRING]");
                                cellValue = cell.getStringCellValue();
                                break;
                            case BOOLEAN: //布爾類型
                                System.out.println("[BOOLEAN]");
                                //強制轉換為字元串類型
                                cellValue= String.valueOf(cell.getBooleanCellValue());
                                break;
                            case NUMERIC: //數字(日期,普通數字)
                                System.out.print("[Numeric]");
                                //新版本HSSFDateUtil方法過時,使用DataUtil
                                if (DateUtil.isCellDateFormatted(cell)){
                                    // 日期
                                    System.out.print("[日期]");
                                    Date dateCellValue = cell.getDateCellValue();
                                    cellValue = new DateTime(dateCellValue).toString("yyyy-MM-dd");
                                }else {
                                    System.out.print("[轉換為字元串輸出]");
                                    // 不是日期格式,防止數字過長,轉換為字元串類型
//                                    cell.setCellType(CellType.STRING); 5.0 版本後過時,使用如下方法
                                    HSSFDataFormatter hssfDataFormat = new HSSFDataFormatter();
                                    cellValue = hssfDataFormat.formatCellValue(cell).toString();
                                }
                                break;
                            case ERROR: //錯誤
                                System.out.println("[資料類型錯誤!!]");
                                break;

                        }
                        System.out.println(cellValue);

                    }
                }

            }
        }
        //關閉流
        fileInputStream.close();
    }
           
公式計算

表資料

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-uLLK0BwG-1622371513739)(https://img2020.cnblogs.com/blog/2233039/202105/2233039-20210530183401806-1233248115.png)]

示例代碼

@Test
    public void testFormula() throws IOException {
        FileInputStream fileInputStream = new FileInputStream(PATH + "公式.xls");
        Workbook workbook = new HSSFWorkbook(fileInputStream);
        Sheet sheet = workbook.getSheetAt(0);
        Row row = sheet.getRow(5);
        Cell cell = row.getCell(0);
        //拿到計算公式
        FormulaEvaluator FormulaEvaluator = new HSSFFormulaEvaluator((HSSFWorkbook) workbook);
        //輸出單元格的内容
        CellType cellType = cell.getCellType();
        switch (cellType){
            case FORMULA: //公式
                //擷取目前單元格的計算公式
                String cellFormula = cell.getCellFormula();
                System.out.println("使用公式:\t" + cellFormula);
                //計算得到計算結果
                CellValue evaluate = FormulaEvaluator.evaluate(cell);
                //将計算結果轉換為字元串類型
                String cellValue = evaluate.formatAsString();
                System.out.println("計算結果為:\t" + cellValue);
                break;
        }
    }
           

EasyExcel基本使用

easyExcel 官網: https://github.com/alibaba/easyexcel

官方文檔:https://www.yuque.com/easyexcel/doc/easyexcel

導入依賴
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.10</version>
<!-- lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.20</version>
</dependency>
           
寫入測試

實體類

@Data
public class DemoData {
    @ExcelProperty("字元串标題")
    private String string;
    @ExcelProperty("日期标題")
    private Date date;
    @ExcelProperty("數字标題")
    private Double doubleData;
    /**
     * 忽略這個字段
     */
    @ExcelIgnore
    private String ignore;
}
           

測試類

package com.shiguang.easy;

import com.alibaba.excel.EasyExcel;
import org.junit.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.Date;

public class easyTest {
    String PATH = "D:\\Desktop\\";
    private List<DemoData> data() {
        List<DemoData> list = new ArrayList<DemoData>();
        for (int i = 0; i < 10; i++) {
            DemoData data = new DemoData();
            data.setString("字元串" + i);
            data.setDate(new Date());
            data.setDoubleData(0.56);
            list.add(data);
        }
        return list;
    }

    /**
     * 最簡單的寫
     * <p>1. 建立excel對應的實體對象 參照{@link DemoData}
     * <p>2. 直接寫即可
     */
    @Test
    public void simpleWrite() {
        // 寫法1
        String fileName = PATH + "EasyTest.xlsx";
        // 這裡 需要指定寫用哪個class去寫,然後寫到第一個sheet,名字為模闆 然後檔案流會自動關閉
        // 如果這裡想使用03 則 傳入excelType參數即可
        EasyExcel.write(fileName, DemoData.class).sheet("模闆").doWrite(data());

    }
}
           

此處運作時遇到了一個錯誤

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-xNKRVu5v-1622371513744)(https://img2020.cnblogs.com/blog/2233039/202105/2233039-20210530183428098-1872114401.png)]

官方解釋:

此錯誤在組織slf4j.inf.strestcoperbinder類無法裝入記憶體時報告。當在類路徑上找不到合适的slf4j綁定時,就會發生這種情況。slf4j-nop.jar放置一個(且隻有一個), slf4j-simple.jar, slf4j-log4j12.jar, slf4j-jdk14.jar 或 logback-classic.jar 的類路徑應該解決這個問題。

解決方法:

在Maven工程的pom檔案中新增如下依賴

<dependency>  
  <groupId>org.slf4j</groupId> 
  <artifactId>slf4j-nop</artifactId> 
  <version>1.7.2</version> 
</dependency>
           

重新重新整理依賴就可以了

運作

simpleWrite()

方法後生成的Excel表如下所示

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-bw8Se6ov-1622371513746)(https://img2020.cnblogs.com/blog/2233039/202105/2233039-20210530183449011-1983856433.png)]

讀測試

實體類

參考上文

監聽器

// 有個很重要的點 DemoDataListener 不能被spring管理,要每次讀取excel都要new,然後裡面用到spring可以構造方法傳進去
public class DemoDataListener extends AnalysisEventListener<DemoData> {
    private static final Logger LOGGER = LoggerFactory.getLogger(DemoDataListener.class);
    /**
     * 每隔5條存儲資料庫,實際使用中可以3000條,然後清理list ,友善記憶體回收
     */
    private static final int BATCH_COUNT = 5;
    List<DemoData> list = new ArrayList<DemoData>();
    /**
     * 假設這個是一個DAO,當然有業務邏輯這個也可以是一個service。當然如果不用存儲這個對象沒用。
     */
    private DemoDAO demoDAO;
    public DemoDataListener() {
        // 這裡是demo,是以随便new一個。實際使用如果到了spring,請使用下面的有參構造函數
        demoDAO = new DemoDAO();
    }
    /**
     * 如果使用了spring,請使用這個構造方法。每次建立Listener的時候需要把spring管理的類傳進來
     *
     * @param demoDAO
     */
    public DemoDataListener(DemoDAO demoDAO) {
        this.demoDAO = demoDAO;
    }
    /**
     * 這個每一條資料解析都會來調用
     *
     * @param data
     *            one row value. Is is same as {@link AnalysisContext#readRowHolder()}
     * @param context
     */
    @Override
    public void invoke(DemoData data, AnalysisContext context) {
        LOGGER.info("解析到一條資料:{}", JSON.toJSONString(data));
        list.add(data);
        // 達到BATCH_COUNT了,需要去存儲一次資料庫,防止資料幾萬條資料在記憶體,容易OOM
        if (list.size() >= BATCH_COUNT) {
            saveData();
            // 存儲完成清理 list
            list.clear();
        }
    }
    /**
     * 所有資料解析完成了 都會來調用
     *
     * @param context
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 這裡也要儲存資料,確定最後遺留的資料也存儲到資料庫
        saveData();
        LOGGER.info("所有資料解析完成!");
    }
    /**
     * 加上存儲資料庫
     */
    private void saveData() {
        LOGGER.info("{}條資料,開始存儲資料庫!", list.size());
        demoDAO.save(list);
        LOGGER.info("存儲資料庫成功!");
    }
}
           

持久層

/**
 * 假設這個是你的DAO存儲。當然還要這個類讓spring管理,當然你不用需要存儲,也不需要這個類。
 **/
public class DemoDAO {
    public void save(List<DemoData> list) {
        // 如果是mybatis,盡量别直接調用多次insert,自己寫一個mapper裡面新增一個方法batchInsert,所有資料一次性插入
    }
}
           

注意:

如果

DemoDataListener

@Override

錯誤,将Modules該為8版本即可

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-H1ERgUH7-1622371513750)(https://img2020.cnblogs.com/blog/2233039/202105/2233039-20210530183512223-2088337746.png)]

另外,還需要導入

fastjson

依賴

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.75</version>
</dependency>
           

測試代碼

@Test
    public void simpleRead() {
        // 有個很重要的點 DemoDataListener 不能被spring管理,要每次讀取excel都要new,然後裡面用到spring可以構造方法傳進去
        // 寫法1:
        String fileName = PATH + "EasyTest.xlsx";
        // 這裡 需要指定讀用哪個class去讀,然後讀取第一個sheet 檔案流會自動關閉
        EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
    }
           

執行效果如下:

POI &amp; easyExcel快速使用

總結

寫入:根據固定類格式寫入

讀取:根據監聽器設定的規則進行讀取