既然 io 流都学了,那不顺便看看 java 怎么操作excel吗?
不过这些不用担心我们自己来,毕竟我们能想到的,早就有大佬造好轮子。

需要注意的是 excel 分为03版本和07版本。两个版本有着很大的差别。
03版本的 excel 以 xls 后缀结尾,最多只能存放 65536行数据
07版本的 excel 以 xlsx 后缀结尾,理论上不限制行数。

POI方式操作 excel

Apache POI是一个开源的Java读写Excel、WORD等微软OLE2组件文档的项目。

我们可以通过 poi 来达到我们的目的

环境搭建

毫无疑问,在pom 文件中引入 poi 依赖即可。

  1. <dependencies>
  2. <!--xls(03)-->
  3. <dependency>
  4. <groupId>org.apache.poi</groupId>
  5. <artifactId>poi</artifactId>
  6. <version>3.9</version>
  7. </dependency>
  8. <!--xlsx(07)-->
  9. <dependency>
  10. <groupId>org.apache.poi</groupId>
  11. <artifactId>poi-ooxml</artifactId>
  12. <version>3.9</version>
  13. </dependency>
  14. <!--test-->
  15. <dependency>
  16. <groupId>junit</groupId>
  17. <artifactId>junit</artifactId>
  18. <version>4.12</version>
  19. </dependency>
  20. </dependencies>

代码实现前的简单分析

要知道在 java中,一切都为对象。毫无疑问,excel 也可以被看成一个对象。那我们来分析一下这个 excel
excel 里 有 sheet,也就是工作表,
工作表里有一行一行的数据,也就是 row
一行中有很多个列 在 poi 里叫做cell,这个列就是单元格,我们的数据,就是放在这个 cell 中。

简单的写入

其实这两个版本在代码方面,并无太大的差别。因为 POI 提供了我们一个接口 Workbook。
03 版本和 07 版本都实现了这个接口。所以我们只需要改一下实现类即可。
当然,生成的文件后缀名也是要改的。

03版本的写入 HSSFWorkbook

  1. //测试 03 版本的 excel.xls
  2. @Test
  3. public void testXls03() throws IOException {
  4. //1.创建一个工作簿,也就是excel
  5. Workbook workbook = new HSSFWorkbook();
  6. //2.创建一个sheet,也就是工作表
  7. Sheet sheet = workbook.createSheet("今日份的学习");
  8. //3.创建行,下标从0开始,0就是excel 的第一行
  9. Row row1 = sheet.createRow(0);
  10. //4.创建列(单元格)
  11. //现在 这个列 就是 (1,1) 单元格
  12. Cell Cell11 = row1.createCell(0);
  13. //5.填充数据
  14. Cell11.setCellValue("POI的学习进度");
  15. //创建第一行第二列,也就是(1,2) 单元格
  16. Cell Cell12 = row1.createCell(1);
  17. Cell12.setCellValue("刚开始");
  18. //创建第二行
  19. Row row2 = sheet.createRow(1);
  20. Cell cell21 = row2.createCell(0);
  21. Cell cell22 = row2.createCell(1);
  22. cell21.setCellValue("esayExcel的学习进度");
  23. cell22.setCellValue("还没开始");
  24. //就在项目路径下创建一个 xxx.xls
  25. FileOutputStream outputStream = new FileOutputStream(LocalDate.now().toString()+"的03excel.xls");
  26. workbook.write(outputStream);
  27. outputStream.close();
  28. }

07版本的写入 XSSFWorkbook

  1. @Test
  2. public void test07() throws IOException {
  3. //1.创建一个工作簿,也就是excel
  4. Workbook workbook = new XSSFWorkbook();
  5. //2.创建一个sheet,也就是工作表
  6. Sheet sheet = workbook.createSheet("今日份的学习");
  7. //3.创建行,下标从0开始,0就是excel 的第一行
  8. Row row1 = sheet.createRow(0);
  9. //4.创建列(单元格)
  10. //现在 这个列 就是 (1,1) 单元格
  11. Cell Cell11 = row1.createCell(0);
  12. //5.填充数据
  13. Cell11.setCellValue("POI的学习进度");
  14. //创建第一行第二列,也就是(1,2) 单元格
  15. Cell Cell12 = row1.createCell(1);
  16. Cell12.setCellValue("刚开始");
  17. //创建第二行
  18. Row row2 = sheet.createRow(1);
  19. Cell cell21 = row2.createCell(0);
  20. Cell cell22 = row2.createCell(1);
  21. cell21.setCellValue("esayExcel的学习进度");
  22. cell22.setCellValue("还没开始");
  23. //就在项目路径下创建一个 xxx.xlsx
  24. FileOutputStream outputStream = new FileOutputStream(LocalDate.now().toString()+"的07excel.xlsx");
  25. workbook.write(outputStream);
  26. outputStream.close();
  27. }

区别 HSSFWorkbook 和 XSSFWorkbook

HSSF 方式,也就是03版本,只能存放 65536行数据,如果超过这个限制,会抛异常。
但他的优点是速度快,因为它会将数据直接读取到 内存中,然后在最后一次性将所有数据写入硬盘。
不过在操作大数据的时候,可能会导致内存溢出。

而 XSSF 方式,也就是 07版本。写数据的时候非常缓慢,非常耗内存,但是可以写入更多的数据。
如果数据过多,也会导致内存溢出。为了解决这个问题,推荐使用 SXSSFWorkbook

SXSSFWorkbook

SXSSFWorkbook,是 POI 为了解决 XSSF 写入数据过慢而提供的一个Workbook实现类。
它的优点是速度快,也可以写入多量的数据,占用的内存也很少。
SXSSFWorkbook 采用临时文件的方式。
默认会将100条数据存放到内存中,如果超过100条,写入这个临时文件,然后继续读取。
这个临时文件需要我们清理。

但是需要注意的是,如果在 excel 中广泛使用合并单元格等操作的话,同样会占用大量的内存。

大数据的写入

通过上边讲述的HSSFWorkbook,XSSFWorkbook 和 SXSSFWorkbook的区别,我们来进行一下大数据的测试

HSSFWorkbook

因为我的这个代码存放的是 机械硬盘,所以读写速度过慢。
不过也可以看到,HSSF 速度还是很快的只花费了2.229 秒
测试一下 100000 条数据,会发现抛了异常。

  1. //测试一下 03版本的大数据
  2. @Test
  3. public void test03BigData() throws IOException {
  4. long start = System.currentTimeMillis();
  5. //1.创建一个工作簿,也就是excel
  6. Workbook workbook = new HSSFWorkbook();
  7. //2.创建一个sheet,也就是工作表
  8. Sheet sheet = workbook.createSheet("大数据测试");
  9. // 03 版本最多只能有 65536行数据,否则会报异常
  10. for (int i = 0; i < 65536; i++) {
  11. Row row = sheet.createRow(i);
  12. for (int j = 0; j < 10; j++) {
  13. row.createCell(j).setCellValue(i);
  14. }
  15. }
  16. System.out.println("over");
  17. //就在项目路径下创建一个 xxx.xls
  18. FileOutputStream outputStream = new FileOutputStream(LocalDate.now().toString()+"的03bigdata.xls");
  19. workbook.write(outputStream);
  20. outputStream.close();
  21. long end = System.currentTimeMillis();
  22. System.out.println((double) (end - start) / 1000);//2.229
  23. }

XSSFWorkbook

在 XSSF 的方式下,10万条数据成功写入。
但是速度非常的慢,花费了11.206秒。

  1. @Test
  2. public void test07BigData() throws IOException {
  3. long start = System.currentTimeMillis();
  4. Workbook workbook = new XSSFWorkbook();
  5. Sheet sheet = workbook.createSheet("大数据测试");
  6. // 07 版本 理论上是无限大的,可以存取很多行
  7. for (int i = 0; i < 100000; i++) {
  8. Row row = sheet.createRow(i);
  9. for (int j = 0; j < 10; j++) {
  10. row.createCell(j).setCellValue(i);
  11. }
  12. }
  13. System.out.println("over");
  14. //就在项目路径下创建一个 xxx.xls
  15. FileOutputStream outputStream = new FileOutputStream(LocalDate.now().toString()+"的07bigdata.xlsx");
  16. workbook.write(outputStream);
  17. outputStream.close();
  18. long end = System.currentTimeMillis();
  19. System.out.println((double) (end - start) / 1000);//11.206
  20. //可以发现,速度很慢,那有没有什么办法改善一下呢。可以使用 SXSSFWorkbook
  21. }

SXSSFWorkbook

使用 SXSSFWorkBook 来给 10万条数据加速。
会发现,速度提升了很多,只花费了2.806秒

  1. @Test
  2. public void test07SXSSFWorkbookBigData() throws IOException {
  3. long start = System.currentTimeMillis();
  4. Workbook workbook = new SXSSFWorkbook();
  5. Sheet sheet = workbook.createSheet("大数据测试");
  6. // 07 版本 理论上是无限大的,可以存取很多行
  7. for (int i = 0; i < 100000; i++) {
  8. Row row = sheet.createRow(i);
  9. for (int j = 0; j < 10; j++) {
  10. row.createCell(j).setCellValue(i);
  11. }
  12. }
  13. System.out.println("over");
  14. //就在项目路径下创建一个 xxx.xls
  15. FileOutputStream outputStream = new FileOutputStream(LocalDate.now().toString()+"SXSSFWorkbook.xlsx");
  16. workbook.write(outputStream);
  17. outputStream.close();
  18. long end = System.currentTimeMillis();
  19. //清楚临时文件
  20. ((SXSSFWorkbook)workbook).dispose();
  21. //这次就会发现,速度快了很多。
  22. System.out.println((double) (end - start) / 1000);//2.806
  23. }

读取excel

使用 iputStream 来读取刚才我们写入的 数据。
要注意的是读取数据的类型。这也是读取数据时最麻烦的一点。

  1. @Test
  2. public void testRead() throws IOException {
  3. FileInputStream inputStream = new FileInputStream("2020-04-25的07excel.xlsx");
  4. // Workbook workbook = new HSSFWorkbook(inputStream);
  5. Workbook workbook = new XSSFWorkbook(inputStream);
  6. //获取 excel 的 第一张表
  7. Sheet sheet = workbook.getSheetAt(0);
  8. //获取第一行
  9. Row row = sheet.getRow(0);
  10. //获取第一个单元格
  11. Cell cell = row.getCell(0);
  12. //这里知道第一个单元格的数据为 String 类型,所以获取String 类型
  13. System.out.println(cell.getStringCellValue());
  14. inputStream.close();
  15. }

读取任意类型的数据

其实也很简单,因为数据类型,并不受我们控制(我们不知到excel 当前数据到底是 String 还是 int 还是 boolean)。
所以我们也没办法,只能 通过 switch 来判断。
以下以 XSSFWorkbook 示例:
HSSF 同理

  1. @Test
  2. public void testReadByType() throws IOException {
  3. FileInputStream inputStream = new FileInputStream("2020-04-25的07excel.xlsx");
  4. Workbook workbook = new XSSFWorkbook(inputStream);
  5. //获取 excel 的 第一张表
  6. Sheet sheet = workbook.getSheetAt(0);
  7. //获取该 表 有多少行
  8. int rowsCount = sheet.getPhysicalNumberOfRows();
  9. for (int rowNum = 0; rowNum < rowsCount; rowNum++) {
  10. Row row = sheet.getRow(rowNum);
  11. //获取 该行有多少列中有数据
  12. int cellsCount = row.getPhysicalNumberOfCells();
  13. for (int cellNum = 0; cellNum < cellsCount; cellNum++) {
  14. Cell cell = row.getCell(cellNum);
  15. if (cell == null) {
  16. //说明,该单元格为 空单元格,没有数据
  17. cell = row.createCell(cellNum);
  18. }
  19. //判断数据的类型。
  20. switch (cell.getCellType()) {
  21. case Cell.CELL_TYPE_STRING :
  22. //字符串类型
  23. System.out.println(cell.getStringCellValue());
  24. break;
  25. case Cell.CELL_TYPE_BOOLEAN :
  26. //boolean类型
  27. System.out.println(cell.getBooleanCellValue());
  28. break;
  29. case Cell.CELL_TYPE_BLANK :
  30. System.out.println("这是一个空的单元格");
  31. break;
  32. case Cell.CELL_TYPE_NUMERIC :
  33. //当type是numberic的时候,又分为数字格式和日期格式。
  34. boolean isDate = DateUtil.isCellDateFormatted(cell);
  35. if (isDate) {
  36. //时间类型
  37. Date date = cell.getDateCellValue();
  38. LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
  39. System.out.println(localDate);
  40. }else {
  41. //数字类型
  42. System.out.println(cell.getNumericCellValue());
  43. }
  44. break;
  45. }
  46. }
  47. }
  48. inputStream.close();
  49. }

关于 excel 中的公式

有的时候 excel 单元格里存放的是 公式
比如:
image.png
这个时候我们怎么获取并且得到结果呢?
我们在 POI 中也可以获取 计算公式 FormulaEvaluator
然后通过 evaluate方法 获取计算结果

    @Test
    public void testFormula() throws IOException {
        FileInputStream inputStream = new FileInputStream("2020-04-25的07excel.xlsx");

        Workbook workbook = new XSSFWorkbook(inputStream);
        //获取 excel 的 第一张表
        Sheet sheet = workbook.getSheetAt(0);

        Row row = sheet.getRow(4);
        Cell cell = row.getCell(1);

        //拿到计算公式
        FormulaEvaluator formulaEvaluator = new XSSFFormulaEvaluator((XSSFWorkbook) workbook);
        switch (cell.getCellType()) {
            //可以这样写特定的类型XSSFCell
            //case XSSFCell.CELL_TYPE_FORMULA
            //不过最好建议这样写
            case Cell.CELL_TYPE_FORMULA :
                System.out.println("这是一个公式类型");
                //执行公式
                CellValue cellValue = formulaEvaluator.evaluate(cell);
                //输出执行结果
                System.out.println(cellValue.formatAsString());
                break;
        }
        inputStream.close();
    }

easyExcel

easyExcel 是阿里巴巴开发的用来解析 excel的工具,比 POI 更快,更简单,而且一定不发产生你内存溢出的状况
easyExcel github地址
easyExcel 语雀文档
非常建议了解一下。
文档很详细。
建议直接看文档

引入相关依赖

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>easyexcel</artifactId>
            <version>2.2.3</version>
        </dependency>