目录

    • 1、常用信息
    • 2、Apache POI
    • 3、easyExcel
    • 4、xls和xlsx区别
    • 5、POI Excel 写 03和07版本方式
      • 5.1小数据量
      • 5.2大文件写HSSF(03)
      • 5.3大文件写XSSF(07)
      • 5.4大文件写SXSSF
    • 6、POI Excel读 03和07版本方式
      • 6.1 (03版本)
      • 6.2(07版本)
      • 6.3读取不同的数据类型
    • 7 EasyExcel操作
      • 7.1导入依赖
      • 7.2写入测试
      • 7.3读测试

链接: 面试题库

https://www.bloghut.cn/questionBank


1、常用信息

  (1)、将用户信息导出为excel表格(导出数据…)

  (2)、将Excel表中的信息录入到网站数据库(习题上传…)

  (3)开发中经常会设计到excel的处理,如导出Excel,导入Excel到数据库中!操作Excel目前比较流行的就是Apache POI和阿里巴巴的easyExcel !

是什么:组件,工具

  Apache POI 是用Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java程式对Microsoft Office格式档案读和写的功能。


2、Apache POI

官网:

https://poi.apache.org/


3、easyExcel

官网:

https://www.yuque.com/easyexcel/doc/easyexcel

  Java领域解析,生成Excel比较有名的框架有Apache poi,jxl等,但他们都存在一个严重的问题就是非常的耗内存,如果你的系统并发量不大的话可能还行,但是一旦并发上来后一定会OOM或者JVM频繁的full gc.

  EasyExcel是阿里巴巴开源的一个excel处理框架,以使用简单,节省内存著称,EasyExcel能大大减少占用内存的主要原因是在解析Excel时没有将文件数据一次性全部加载到内存中,而是从磁盘上一行行读取数据,逐个解析。

  EasyExcel采用一行一行的解析模式,并将一行的解析结果以观察者的模式通知处理(AnalysisEventListener)。


4、xls和xlsx区别

03和07版本的写,就是对象不同,方法一样

最大行列得数量不同:

  xls最大只有65536行、256列

  xlsx可以有1048576行、16384列

        <!--xls(03)-->        <dependency>            <groupId>org.apache.poi</groupId>            <artifactId>poi</artifactId>            <version>3.9</version>        </dependency>                <!--xlsx(07)-->        <dependency>            <groupId>org.apache.poi</groupId>            <artifactId>poi-ooxml</artifactId>            <version>3.9</version>        </dependency>

poi 操作xls的
poi-ooxml 操作xlsx的

操作的版本不同,使用的工具类也不同


工作簿:
工作表:
行:
列:


5、POI Excel 写 03和07版本方式

5.1小数据量

package cn.bloghut;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.jupiter.api.Test;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.util.Date;/** * @Classname ExcelWrite * @Description TODO * @Date 2022/1/7 12:41 * @Created by 闲言 */public class ExcelWrite {    String PATH = "G:\\狂\\POIStudy\\xy-poi";    /**     * 写工作簿 03版本     */    @Test    public void Write03() throws Exception {        //1.创建一个工作簿        Workbook workbook = new HSSFWorkbook();        //2.创建 一个工作表        Sheet sheet = workbook.createSheet("闲言粉丝统计表");        //3.创建一行        Row row1 = sheet.createRow(0);        //4.创建一个单元格        //(1,1)        Cell cell1 = row1.createCell(0);        cell1.setCellValue("今日新增观众");        //(1,2)        Cell cell2 = row1.createCell(1);        cell2.setCellValue(666);        //创建第二行        Row row2 = sheet.createRow(1);        //(2,1)        Cell cell21 = row2.createCell(0);        cell21.setCellValue("统计时间");        //(2,2)        Cell cell22 = row2.createCell(1);        String datetime = new DateTime().toString("yyyy-MM-dd HH:mm:ss");        cell22.setCellValue(datetime);        //生成一张表(IO流),03版本就是使用xls结尾        FileOutputStream fos = new FileOutputStream(PATH + "闲言观众统计表03.xls");        //输出        workbook.write(fos);        //关闭流        fos.close();        System.out.println("文件生成完毕");    }    /**     * 写工作簿 07版本     */    @Test    public void Write07() throws Exception {        //1.创建一个工作簿        Workbook workbook = new XSSFWorkbook();        //2.创建 一个工作表        Sheet sheet = workbook.createSheet("闲言粉丝统计表");        //3.创建一行        Row row1 = sheet.createRow(0);        //4.创建一个单元格        //(1,1)        Cell cell1 = row1.createCell(0);        cell1.setCellValue("今日新增观众");        //(1,2)        Cell cell2 = row1.createCell(1);        cell2.setCellValue(666);        //创建第二行        Row row2 = sheet.createRow(1);        //(2,1)        Cell cell21 = row2.createCell(0);        cell21.setCellValue("统计时间");        //(2,2)        Cell cell22 = row2.createCell(1);        String datetime = new DateTime().toString("yyyy-MM-dd HH:mm:ss");        cell22.setCellValue(datetime);        //生成一张表(IO流),03版本就是使用xlsx结尾        FileOutputStream fos = new FileOutputStream(PATH + "闲言观众统计表07.xlsx");        //输出        workbook.write(fos);        //关闭流        fos.close();        System.out.println("文件生成完毕");    }}

注意对象一个区别,文件后缀


5.2大文件写HSSF(03)

缺点:最多只能处理65536行,否则会抛异常

java.lang.IllegalArgumentException: Invalid row number (65536) outside allowable range (0..65535)

优点:过程中写入缓存,不操作磁盘,最后一次性吸入磁盘,速度快

@Test    public void Write03BigData() throws Exception{        //时间        long begin = System.currentTimeMillis();        //1.创建一个工作簿       Workbook workbook = new HSSFWorkbook();        //2.创建一个表        Sheet sheet = workbook.createSheet("第一页");        //写入数据        for (int rowNum = 0;rowNum<65536;rowNum++){            //3.创建行            Row row = sheet.createRow(rowNum);           for (int CellNum = 0;CellNum<10;CellNum++){               Cell cell = row.createCell(CellNum);               cell.setCellValue(CellNum);           }        }        System.out.println("over");        //获取io流        FileOutputStream fos = new FileOutputStream(PATH+"Write03BigData.xls");        //生成一张表        workbook.write(fos);        fos.close();        long end = System.currentTimeMillis();        System.out.println("耗时:"+(end-begin));    }

结果:


5.3大文件写XSSF(07)

缺点:写数据时速度非常慢,非常耗内存,也会发生内存溢出,如100万条。
优点:可以写较大数据量,如20万条

    @Test    public void Write07BigData() throws Exception{        //时间        long begin = System.currentTimeMillis();        //1.创建一个工作簿        Workbook workbook = new XSSFWorkbook();        //2.创建一个表        Sheet sheet = workbook.createSheet("第一页");        //写入数据        for (int rowNum = 0;rowNum<65537;rowNum++){            //3.创建行            Row row = sheet.createRow(rowNum);            for (int CellNum = 0;CellNum<10;CellNum++){                Cell cell = row.createCell(CellNum);                cell.setCellValue(CellNum);            }        }        System.out.println("over");        //获取io流        FileOutputStream fos = new FileOutputStream(PATH+"Write03BigData.xlsx");        //生成一张表        workbook.write(fos);        fos.close();        long end = System.currentTimeMillis();        System.out.println("耗时:"+(end-begin));    }

结果:


5.4大文件写SXSSF

优点:可以写非常大的数据量,如100万条甚至更多条写数据速度快,占用更少的内存

注意:

  1. 过程中会产生临时文件,需要清理临时文件
  2. 默认由100条记录被保存在内存中,如果超过这数量,则最前面的数据被写入临时文件
  3. 如果想自定义内存中数据的数量,可以使用new SXSSFWorkbook(数量)
@Test    public void Write07BigDataS() throws Exception{        //时间        long begin = System.currentTimeMillis();        //1.创建一个工作簿        Workbook workbook = new SXSSFWorkbook();        //2.创建一个表        Sheet sheet = workbook.createSheet("第一页");        //写入数据        for (int rowNum = 0;rowNum<100000;rowNum++){            //3.创建行            Row row = sheet.createRow(rowNum);            for (int CellNum = 0;CellNum<10;CellNum++){                Cell cell = row.createCell(CellNum);                cell.setCellValue(CellNum);            }        }        System.out.println("over");        //获取io流        FileOutputStream fos = new FileOutputStream(PATH+"Write03BigDataS.xlsx");        //生成一张表        workbook.write(fos);        fos.close();        //清除临时文件        ((SXSSFWorkbook) workbook).dispose();        long end = System.currentTimeMillis();        System.out.println("耗时:"+(end-begin));    }

  SXSSFWorkbook-来至官方的解释︰实现”BigGridDemo”策略的流式XSSFWorkbook版本。这允许写入非常大的文件而不会耗尽内存,因为任何时候只有可配置的行部分被保存在内存中。

  请注意,仍然可能会消耗大量内存,这些内存基于您正在使用的功能,例如合并区域,注.…….当然只存储在内存中,因此如果广泛使用,可能需要大量内存。
再使用POI的时候!内存问题Jprofile !


6、POI Excel读 03和07版本方式

6.1 (03版本)

 @Test    public  void Read03() throws Exception{        //1.获取文件流        FileInputStream fis = new FileInputStream(PATH+"xy-poi闲言观众统计表03.xls");        //2.创建一个工作簿。使用excel能操作的这边都可以操作!        Workbook workbook = new HSSFWorkbook(fis);        //3.获取表        Sheet sheet = workbook.getSheetAt(0);        //4.获取第一行        Row row1 = sheet.getRow(0);        //5.获取第一列        Cell cell1 = row1.getCell(0);        //6.获取第一行第一列的值        String stringCellValue = cell1.getStringCellValue();        //获取第二列        Cell cell2 = row1.getCell(1);        //获取第一行第二列的值        double numericCellValue = cell2.getNumericCellValue();        System.out.println(stringCellValue+" | "+numericCellValue);        fis.close();    }

  注意获取值的类型即可


6.2(07版本)

@Test    public  void Read07() throws Exception{        //1.获取文件流        FileInputStream fis = new FileInputStream(PATH+"xy-poi闲言观众统计表07.xlsx");        //2.创建一个工作簿。使用excel能操作的这边都可以操作!        Workbook workbook = new XSSFWorkbook(fis);        //3.获取表        Sheet sheet = workbook.getSheetAt(0);        //4.获取第一行        Row row1 = sheet.getRow(0);        //5.获取第一列        Cell cell1 = row1.getCell(0);        //6.获取第一行第一列的值        String stringCellValue = cell1.getStringCellValue();        //获取第二列        Cell cell2 = row1.getCell(1);        //获取第一行第二列的值        double numericCellValue = cell2.getNumericCellValue();        System.out.println(stringCellValue+" | "+numericCellValue);        fis.close();    }

  注意获取值的类型即可


6.3读取不同的数据类型

    @Test    public void CellType03() throws Exception{        //获取文件流        FileInputStream fis = new FileInputStream(PATH+"明显表.xls");        //获取一个工作簿        Workbook workbook = new HSSFWorkbook(fis);        //获取一个工作表        Sheet sheet = workbook.getSheetAt(0);        //获取第一行内容        Row row = sheet.getRow(0);        if (row != null){            //获取所有的列            int Cells = row.getPhysicalNumberOfCells();            for (int col = 0;col < Cells;col++){                //获取当前列                Cell cell = row.getCell(col);                if (cell != null){                    //获取当前行的第 col 列的值                    String cellValue = cell.getStringCellValue();                    System.out.print(cellValue+" | ");                }            }        }        //获取标准的内容        //获取有多少行        int rowCount = sheet.getPhysicalNumberOfRows();        //从1开始,第一行是标题        for (int rowNum = 1;rowNum < rowCount;rowNum++){            Row rowData = sheet.getRow(rowNum);            if (rowData != null){                //获取当前行的列数                int cellCount = rowData.getPhysicalNumberOfCells();                System.out.println();                for (int col = 0;col < cellCount;col++){                    //获取当前列的值                    Cell cellData = rowData.getCell(col);                    //打印当前行当前列的值                    System.out.print("["+(rowNum+1)+"-"+(col+1)+"]");                    //匹配列的类型                    if (cellData != null){                        //获取列的类型                        int cellType = cellData.getCellType();                        String cellValue = "";                        switch (cellType){                            case  Cell.CELL_TYPE_STRING://字符串                                System.out.print("[string]");                                cellValue = cellData.getStringCellValue();                                break;                            case  Cell.CELL_TYPE_BOOLEAN://布尔                                System.out.print("[boolean]");                                cellValue = String.valueOf(cellData.getBooleanCellValue());                                break;                            case  Cell.CELL_TYPE_BLANK://System.out.print("[blank]");                                break;                            case  Cell.CELL_TYPE_NUMERIC://数字(日期、普通数字)                                System.out.print("[numeric]");                                if (HSSFDateUtil.isCellDateFormatted(cellData)){                                    //如果是日期                                    System.out.print("[日期] ");                                    Date date = cellData.getDateCellValue();                                    cellValue = new  DateTime(date).toString("yyyy-MM-dd HH:mm:ss");                                }else {                                    //不是日期格式,防止数字过长                                    System.out.print("[转换字符串输出] ");                                    //转为字符串                                    cellData.setCellType(HSSFCell.CELL_TYPE_STRING);                                    cellValue = cellData.toString();                                }                                break;                            case  Cell.CELL_TYPE_ERROR://错误                                System.out.print("[error]");                                break;                        }                        System.out.print("["+cellValue+"]\n");                    }                }            }        }        System.out.println();        System.out.println("over");        fis.close();    }

  如果是07版本的Excel ,只需要将HSSFWorkbook类修改为XSSFWorkbook类。将xls文件修改为xlsx文件即可


测试:读取以下表格内容


结果:


7 EasyExcel操作

7.1导入依赖

        <!--easyExcel-->        <dependency>            <groupId>com.alibaba</groupId>            <artifactId>easyexcel</artifactId>            <version>2.2.0-beta2</version>        </dependency>

7.2写入测试

1.格式类

@Getter@Setter@EqualsAndHashCodepublic class DemoData {    @ExcelProperty("字符串标题")    private String string;    @ExcelProperty("日期标题")    private Date date;    @ExcelProperty("数字标题")    private Double doubleData;    /**     * 忽略这个字段     */    @ExcelIgnore    private String ignore;}

2.写的方法

@Test    public  void simpleWrite(){        // 写法1        String fileName = PATH+"EasyTest.xlsx";        //write(fileName,格式类)        //sheet(表名)        //doWrite(数据)        EasyExcel.write(fileName,DemoData.class).sheet("模板").doWrite(data());    }

结果


固定套路:
1、写入:固定类格式进行写入
2、读取:根据监听器设置的规则进行读取

7.3读测试

演示读取以下excel表格数据

1.格式类

@Getter@Setter@EqualsAndHashCodepublic class DemoData {    private String string;    private Date date;    private Double doubleData;}

2.监听器

package cn.bloghut.esay;import com.alibaba.excel.context.AnalysisContext;import com.alibaba.excel.event.AnalysisEventListener;import com.alibaba.fastjson.JSON;import java.util.ArrayList;import java.util.List;// 有个很重要的点 DemoDataListener 不能被spring管理,要每次读取excel都要new,然后里面用到spring可以构造方法传进去public class DemoDataListener extends AnalysisEventListener<DemoData> {    private static final int BATCH_COUNT = 100;    private List<DemoData> cachedDataList = new ArrayList<>(BATCH_COUNT);    private DemoDAO demoDAO;    public DemoDataListener() {        // 这里是demo,所以随便new一个。实际使用如果到了spring,请使用下面的有参构造函数        demoDAO = new DemoDAO();    }    public DemoDataListener(DemoDAO demoDAO) {        this.demoDAO = demoDAO;    }    /**     * 读取数据会执行invoke 方法     * DemoData 类型     * AnalysisContext 分析上下文     *     * @param data     * @param context     */    @Override    public void invoke(DemoData data, AnalysisContext context) {        System.out.println(JSON.toJSONString(data));        cachedDataList.add(data);        // 达到BATCH_COUNT了,需要去存储一次数据库,防止数据几万条数据在内存,容易OOM        if (cachedDataList.size() >= BATCH_COUNT) {            saveData();            // 存储完成清理 list            cachedDataList.clear();        }    }    /**     * 所有数据解析完成了 都会来调用     *     * @param context     */    @Override    public void doAfterAllAnalysed(AnalysisContext context) {        // 这里也要保存数据,确保最后遗留的数据也存储到数据库        saveData();        System.out.println("所有数据解析完成");    }    /**     * 加上存储数据库     */    private void saveData() {        System.out.println("{}条数据,开始存储数据库!"+cachedDataList.size());        demoDAO.save(cachedDataList);        System.out.println("存储数据库成功");    }}

DAO类(不操作数据库,用不到)

public class DemoDAO {    public void save(List<DemoData> list) {        // 如果是mybatis,尽量别直接调用多次insert,自己写一个mapper里面新增一个方法batchInsert,所有数据一次性插入    }}

测试

@Test    public void simpleRead() throws Exception{        String fileName = PATH+"EasyTest.xlsx";        EasyExcel.read(fileName,DemoData.class,new DemoDataListener()).sheet().doRead();    }

结果