jjzjj

【狂神说Java】POI技术详解

闲言博客 2023-06-30 原文

目录

链接: 面试题库

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
@EqualsAndHashCode
public 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
@EqualsAndHashCode
public 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();

    }

结果


有关【狂神说Java】POI技术详解的更多相关文章

  1. java - 等价于 Java 中的 Ruby Hash - 2

    我真的很习惯使用Ruby编写以下代码:my_hash={}my_hash['test']=1Java中对应的数据结构是什么? 最佳答案 HashMapmap=newHashMap();map.put("test",1);我假设? 关于java-等价于Java中的RubyHash,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/22737685/

  2. java - 从 JRuby 调用 Java 类的问题 - 2

    我正在尝试使用boilerpipe来自JRuby。我看过guide从JRuby调用Java,并成功地将它与另一个Java包一起使用,但无法弄清楚为什么同样的东西不能用于boilerpipe。我正在尝试基本上从JRuby中执行与此Java等效的操作:URLurl=newURL("http://www.example.com/some-location/index.html");Stringtext=ArticleExtractor.INSTANCE.getText(url);在JRuby中试过这个:require'java'url=java.net.URL.new("http://www

  3. java - 我的模型类或其他类中应该有逻辑吗 - 2

    我只想对我一直在思考的这个问题有其他意见,例如我有classuser_controller和classuserclassUserattr_accessor:name,:usernameendclassUserController//dosomethingaboutanythingaboutusersend问题是我的User类中是否应该有逻辑user=User.newuser.do_something(user1)oritshouldbeuser_controller=UserController.newuser_controller.do_something(user1,user2)我

  4. java - 什么相当于 ruby​​ 的 rack 或 python 的 Java wsgi? - 2

    什么是ruby​​的rack或python的Java的wsgi?还有一个路由库。 最佳答案 来自Python标准PEP333:Bycontrast,althoughJavahasjustasmanywebapplicationframeworksavailable,Java's"servlet"APImakesitpossibleforapplicationswrittenwithanyJavawebapplicationframeworktoruninanywebserverthatsupportstheservletAPI.ht

  5. Unity 热更新技术 | (三) Lua语言基本介绍及下载安装 - 2

    ?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------

  6. Observability:从零开始创建 Java 微服务并监控它 (二) - 2

    这篇文章是继上一篇文章“Observability:从零开始创建Java微服务并监控它(一)”的续篇。在上一篇文章中,我们讲述了如何创建一个Javaweb应用,并使用Filebeat来收集应用所生成的日志。在今天的文章中,我来详述如何收集应用的指标,使用APM来监控应用并监督web服务的在线情况。源码可以在地址 https://github.com/liu-xiao-guo/java_observability 进行下载。摄入指标指标被视为可以随时更改的时间点值。当前请求的数量可以改变任何毫秒。你可能有1000个请求的峰值,然后一切都回到一个请求。这也意味着这些指标可能不准确,你还想提取最小/

  7. 【Java 面试合集】HashMap中为什么引入红黑树,而不是AVL树呢 - 2

    HashMap中为什么引入红黑树,而不是AVL树呢1.概述开始学习这个知识点之前我们需要知道,在JDK1.8以及之前,针对HashMap有什么不同。JDK1.7的时候,HashMap的底层实现是数组+链表JDK1.8的时候,HashMap的底层实现是数组+链表+红黑树我们要思考一个问题,为什么要从链表转为红黑树呢。首先先让我们了解下链表有什么不好???2.链表上述的截图其实就是链表的结构,我们来看下链表的增删改查的时间复杂度增:因为链表不是线性结构,所以每次添加的时候,只需要移动一个节点,所以可以理解为复杂度是N(1)删:算法时间复杂度跟增保持一致查:既然是非线性结构,所以查询某一个节点的时候

  8. MIMO-OFDM无线通信技术及MATLAB实现(1)无线信道:传播和衰落 - 2

     MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO

  9. 【Java入门】使用Java实现文件夹的遍历 - 2

    遍历文件夹我们通常是使用递归进行操作,这种方式比较简单,也比较容易理解。本文为大家介绍另一种不使用递归的方式,由于没有使用递归,只用到了循环和集合,所以效率更高一些!一、使用递归遍历文件夹整体思路1、使用File封装初始目录,2、打印这个目录3、获取这个目录下所有的子文件和子目录的数组。4、遍历这个数组,取出每个File对象4-1、如果File是否是一个文件,打印4-2、否则就是一个目录,递归调用代码实现publicclassSearchFile{publicstaticvoidmain(String[]args){//初始目录Filedir=newFile("d:/Dev");Datebeg

  10. java - 为什么 ruby​​ modulo 与 java/other lang 不同? - 2

    我基本上来自Java背景并且努力理解Ruby中的模运算。(5%3)(-5%3)(5%-3)(-5%-3)Java中的上述操作产生,2个-22个-2但在Ruby中,相同的表达式会产生21个-1-2.Ruby在逻辑上有多擅长这个?模块操作在Ruby中是如何实现的?如果将同一个操作定义为一个web服务,两个服务如何匹配逻辑。 最佳答案 在Java中,模运算的结果与被除数的符号相同。在Ruby中,它与除数的符号相同。remainder()在Ruby中与被除数的符号相同。您可能还想引用modulooperation.

随机推荐