Excel - POI與EasyExcel操作Excel表

POI

Apache POI是Apache軟件基金會的開放源碼函式庫,POI提供API給Java程序對Microsoft Office格式檔案讀和寫的功能。
結構:

  1. HSSF - 提供讀寫Microsoft Excel格式檔案的功能。
  2. XSSF - 提供讀寫Microsoft Excel OOXML格式檔案的功能。
  3. HWPF - 提供讀寫Microsoft Word格式檔案的功能。
  4. HSLF - 提供讀寫Microsoft PowerPoint格式檔案的功能。
  5. HDGF - 提供讀寫Microsoft Visio格式檔案的功能。

Excel 03版本和07版本

03版本的Excel,即後綴名爲.xls的。07版本的Excel,即後綴名爲.xlsx的。
Excel03:只支持xls類型的文檔,最多65536行、256列。
Excel07:除了支來持xls類型文檔,還支持xlsx類型的文檔,最多1048576行、6384列。

Excel對象

工作簿:一個Excel文件
工作表:一個Excel文件中的表
行、列:每一行、每一列
在這裏插入圖片描述

POI操作Excel

引入相關依賴
<!-- xls(Excel 03版本) -->
<dependency>
	<groupId>org.apache.poi</groupId>
	<artifactId>poi</artifactId>
	<version>3.9</version>
</dependency>
<!-- xlsx(Excel 07版本) -->
<dependency>
	<groupId>org.apache.poi</groupId>
	<artifactId>poi-ooxml</artifactId>
	<version>3.9</version>
</dependency>
<!-- 日期格式化工具 -->
<dependency>
	<groupId>joda-time</groupId>
	<artifactId>joda-time</artifactId>
	<version>2.10.1</version>
</dependency>
<!-- 單元測試 -->
<dependency>
	<groupId>junit</groupId>
	<artifactId>junit</artifactId>
	<version>4.12</version>
</dependency>
基本寫操作

1、03版本

public class ExcelWriteTest {
    // 文件輸入位置
    public static String Path = "D:\\";

    @Test
    public void testWrite03() throws IOException {
        // 創建一個工作簿
        Workbook workbook = new HSSFWorkbook();
        // 根據工作簿創建一個工作表
        Sheet sheet = workbook.createSheet("柳成蔭");
        // 根據工作表創建一行
        Row row0 = sheet.createRow(0);
        // 根據行創建一個單元格
        Cell cell00 = row0.createCell(0);
        cell00.setCellValue("今日聽歌數");   // 即Excel裏的座標(0,0),第一行第一列的格子

        Row row1 = sheet.createRow(1);
        Cell cell10 = row1.createCell(0);
        cell10.setCellValue(10);   // 即Excel裏的座標(1,0),第二行第一列的格子

        Cell cell01 = row0.createCell(1);
        cell01.setCellValue("統計時間");   // (0,1)

        Cell cell11 = row1.createCell(1);
        cell11.setCellValue(new DateTime().toString("yyyy-MM-dd HH:mm:ss"));  // (1,1)

        // 生成一張表
        OutputStream outputStream = new FileOutputStream(Path + "03版本測試.xls");
        workbook.write(outputStream);
        // 關閉流
        outputStream.close();
        System.out.println("03版本Excel生成完畢!");
    }
}

在這裏插入圖片描述
2、07版本
相對上面03版本,代碼沒有什麼變化,就是創建工作簿的對象(XSSFWorkbook)變了和文件後綴(.xlsx)變了

@Test
public void testWrite07() throws IOException {
    // 創建一個工作簿 - 07
    Workbook workbook = new XSSFWorkbook();
    // 根據工作簿創建一個工作表
    Sheet sheet = workbook.createSheet("柳成蔭");
    // 根據工作表創建一行
    Row row0 = sheet.createRow(0);
    // 根據行創建一個單元格
    Cell cell00 = row0.createCell(0);
    cell00.setCellValue("今日聽歌數");   // 即Excel裏的座標(0,0),第一行第一列的格子

    Row row1 = sheet.createRow(1);
    Cell cell10 = row1.createCell(0);
    cell10.setCellValue(10);   // 即Excel裏的座標(1,0),第二行第一列的格子

    Cell cell01 = row0.createCell(1);
    cell01.setCellValue("統計時間");   // (0,1)

    Cell cell11 = row1.createCell(1);
    cell11.setCellValue(new DateTime().toString("yyyy-MM-dd HH:mm:ss"));  // (1,1)

    // 生成一張表
    OutputStream outputStream = new FileOutputStream(Path + "07版本測試.xlsx");
    workbook.write(outputStream);
    // 關閉流
    outputStream.close();
    System.out.println("07版本Excel生成完畢!");
}
大數據量的寫入

1、03版本

@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 < 256; cellNum++){
            Cell cell = row.createCell(cellNum);
            cell.setCellValue(cellNum);
        }
    }
    OutputStream outputStream = new FileOutputStream(Path + "03bigData.xls");
    workbook.write(outputStream);
    outputStream.close();
    // 結束寫入時間
    long end = System.currentTimeMillis();
    System.out.println("寫入完畢,花費時間:");
    System.out.println((double) (end - begin)/1000);
}

在這裏插入圖片描述
2、07版本
在03版本基礎上,只需修改對象(XSSFWorkbook)和文件後綴名(.xlsx)即可。
相對03版本,寫入相同的數據花費時間更長,但是07能寫入的數據更多。
3、07版本升級版 - SXSSFWorkbook
它可以寫入非常大的數據量,如100萬條甚至更多,寫數據速度極快,佔用內存更少。
但是它會在寫入過程中產生臨時文件,默認是100條數據被保存在內存中,如果超過這個數量,則最前面的數據被寫入臨時文件,如果想要自定義內存中數據的數量,可以使用SXSSFWorkbook(數量)

@Test
public void testWrite07BigDataPlus() throws IOException{
    // 開始寫入時間
    long begin = System.currentTimeMillis();
    // 創建工作簿
    Workbook workbook = new SXSSFWorkbook();
    // 創建表
    Sheet sheet = workbook.createSheet("九月清晨");
    for (int rowNum = 0; rowNum < 65536; rowNum++){
        Row row = sheet.createRow(rowNum);
        for (int cellNum = 0; cellNum < 256; cellNum++){
            Cell cell = row.createCell(cellNum);
            cell.setCellValue(cellNum);
        }
    }
    OutputStream outputStream = new FileOutputStream(Path + "07bigDataPlus.xlsx");
    workbook.write(outputStream);
    outputStream.close();
    ((SXSSFWorkbook) workbook).dispose();   // 刪除生成的臨時文件
    // 結束寫入時間
    long end = System.currentTimeMillis();
    System.out.println("寫入完畢,花費時間:");
    System.out.println((double) (end - begin)/1000);
}

我這個電腦有點撈,花費時間比較長,上面那個低配版本寫了很久都沒寫完所以纔沒截圖的。
在這裏插入圖片描述

基本讀操作

讀操作,需要根據類型來讀,也就是需要判斷類型。這裏就以07版爲例:
在這裏插入圖片描述
下面代碼可以單獨封裝出來,根據類型判斷。Excel表裏的每一行內容實際也就是一個對象,也可以讀出來之後封裝到對象裏。

@Test
public void testCellType() throws Exception{
    // 獲取文件流
    InputStream inputStream = new FileInputStream("D:\\test.xlsx");
    // 創建工作簿
    Workbook workbook = new XSSFWorkbook(inputStream);
    // 創建工作表 - 下標爲0
    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){
                String cellValue = cell.getStringCellValue();
                System.out.print(cellValue + "|");
            }
        }
        System.out.println();
    }
    // 獲取行數
    int rowCount = sheet.getPhysicalNumberOfRows();
    for (int rowNum = 1; rowNum < rowCount; rowNum++){
        // 獲取行
        Row row = sheet.getRow(rowNum);
        if (row != null){
            // 獲取列數
            int cellCount = row.getPhysicalNumberOfCells();
            for (int cellNum = 0; cellNum < cellCount; cellNum++){ // 遍歷列
                Cell cell = row.getCell(cellNum);
                String value = "";
                if (cell != null){
                    int cellType = cell.getCellType(); // 獲取單元格數據類型
                    switch (cellType){
                        case HSSFCell.CELL_TYPE_STRING:   // 字符串類型
                            System.out.print("【String類型】");
                            value = cell.getStringCellValue();
                            break;
                        case HSSFCell.CELL_TYPE_BOOLEAN:  // 布爾類型
                            System.out.println("【boolean類型】");
                            value = String.valueOf(cell.getBooleanCellValue());
                            break;
                        case HSSFCell.CELL_TYPE_BLANK:  // 爲空
                            break;
                        case HSSFCell.CELL_TYPE_NUMERIC:  // 數字(日期、普通數字)
                            if (HSSFDateUtil.isCellDateFormatted(cell)){  // 日期
                                System.out.println("【Date日期】");
                                value = new DateTime(cell.getDateCellValue()).toString("yyyy-MM-dd");
                            }else{      // 數字
                                System.out.println("【純數字】");
                                cell.setCellType(HSSFCell.CELL_TYPE_STRING);
                                value = String.valueOf(cell.toString());
                            }
                            break;
                        case HSSFCell.CELL_TYPE_ERROR:
                            System.out.println("【數據類型錯誤】");
                            break;
                    }
                    System.out.println(value);
                }
            }
        }
    }
}

結果:
在這裏插入圖片描述

EsayExcel - 推薦使用

Java解析、生成Excel比較有名的框架有Apache poi、jxl。但他們都存在一個嚴重的問題就是非常的耗內存,poi有一套SAX模式的API可以一定程度的解決一些內存溢出的問題,但POI還是有一些缺陷,比如07版Excel解壓縮以及解壓後存儲都是在內存中完成的,內存消耗依然很大。easyexcel重寫了poi對07版Excel的解析,能夠原本一個3M的excel用POI sax依然需要100M左右內存降低到幾M,並且再大的excel不會出現內存溢出,03版依賴POI的sax模式。在上層做了模型轉換的封裝,讓使用者更加簡單方便。

EasyExcel是阿里巴巴開源的一個excel處理框架,以使用簡單、節省內存著稱。EasyExcel能大大減少佔用內存的主要原因是在解析Excel時沒有將文件數據一次性全部加載到內存中,而是從磁盤上一行行讀取數據,逐個解析。

EasyExcel採用一行一行的解析模式,並將一行的解析結果以觀察者的模式通知處理(AnalysisEventListener)。

寫操作

先創建一個實體類

@Data
public class User {
    @ExcelProperty("學生編號")
    private Long id;
    @ExcelProperty("學生姓名")
    private String name;
    @ExcelProperty("入學時間")
    private Date date;
    @ExcelProperty("入學成績")
    private Double grade;
    @ExcelProperty("是否入學")
    private Boolean isOk;
    @ExcelIgnore  // 忽略
    private String sex;
}

創建寫的測試類

@Test
public void testWrite(){
    String fileName = "D:\\write.xlsx";
    EasyExcel.write(fileName, User.class).sheet("學生列表").doWrite(getDate());
}

/**
 * 獲取數據
 */
public static List<User> getDate(){
    List<User> list = new ArrayList<User>();
    User user;
    for (int i = 1; i <= 10; i++){
        user = new User();
        user.setId((long)i);
        user.setName("學生" + i + "號");
        user.setDate(new Date());
        user.setGrade(99.99);
        user.setIsOk(i%2==0);
        user.setSex((i%2==0)?"男":"女");
        list.add(user);
        System.out.println(user);
    }
    return list;
}

結果:
在這裏插入圖片描述

讀操作

先創建一個監聽器

public class ExcelListener extends AnalysisEventListener<User> {

    private List<User> users = new ArrayList<User>();
    // 讓外界可以調用
    public List<User> getUser() {
        return users;
    }

    // 一行一行讀取Excel內容
    @Override
    public void invoke(User user, AnalysisContext analysisContext) {
        System.out.println("讀取中...");
        users.add(user);  // 加入到集合
    }

    // 讀取表頭內容
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        System.out.println("表頭:" + headMap);
    }

    // 讀取完成之後做什麼事
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        System.out.println("讀完了");
    }
}

測試代碼

@Test
public  void testRead() {
    // 實現Excel讀操作
    String fileName = "D:\\write.xlsx";
    ExcelListener excelListener = new ExcelListener();
    EasyExcel.read(fileName,User.class,excelListener).sheet().doRead();
    List<User> users = excelListener.getUser();
    for (User user : users) {
        System.out.println(user);
    }
}

結果(sex已經被忽略,所以爲null):
在這裏插入圖片描述

實際開發中Excel的的讀取

因爲在實際開發中,我們通常要調用某個服務模塊,把從Excel裏讀取的數據存入到數據庫。因此,修改監聽器代碼如下:

public class ExcelListener extends AnalysisEventListener<User> {

    // 用於控制存儲數據庫
    private static int BATCH_COUNT = 100;
    // 用於存儲數據
    private List<User> users = new ArrayList<User>();

    // 在這個監聽器類沒有辦法通過註解的方式注入Service,只能通過參數傳遞
    private UserService userService;
    
    public ExcelListener(){}
    
    public ExcelListener(UserService userService){
        this.userService = userService;
    }
    
    // 一行一行讀取Excel內容
    @Override
    public void invoke(User user, AnalysisContext analysisContext) {
        users.add(user);
        // 達到BATCH_COUNT了,需要存入一次數據,防止數據太多在內存,導致OOM
        if(users.size() >= BATCH_COUNT){
            userService.saveUsers(users);  // 保存一次
            users.clear();   // 清空List集合
        }
    }

    // 讀取表頭內容
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        System.out.println("表頭:" + headMap);
    }

    // 讀取完成之後做什麼事
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        System.out.println("讀完了");
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章