Java操作Excel的相关技术

Java操作Excel的相关技术

1 POI报表概述

1.1 背景说明

java代码操作excel有两种api类库

  • jxl (只能操作旧版本的excel)
  • poi(新的和旧的excel版本都能操作)

常见的应用场景:

  • 为了方便操作,基于Excel的报表批量上传数据
  • 通过java代码生成Excel报表导出

1.2 Excel的两种形式

目前世面上的Excel分为两个大的版本Excel2003和Excel2007及以上两个版本.

image-20210218203118608

  • Excel2003是一个特有的二进制格式,其核心结构是复合文档类型的结构,存储数据量较小
  • Excel2007 的核心结构是 XML 类型的结构,采用的是基于 XML 的压缩方式,使其占用的空间更小,操作效率更高

1.3 常见的Excel操作工具

Java中常见的用来操作Excel的方式一般有2种:JXL和POI

  • JXL只能对Excel进行操作,属于比较老的框架,它只支持到Excel 95-2000的版本。现在已经停止更新和维护
  • POI是apache的项目,可对微软的Word,Excel,Ppt 进行操作,包括office2003和2007,Excl2003和2007。poi现在一直有更新。所以现在主流使用POI

1.4 POI是?

Apache POI是Apache软件基金会的开源项目,由Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java语言操作Microsoft Office的功能.

Apache POI是目前最流行的操作Microsoft Office的API组件,借助POI可以方便的完成诸如:数据报表生成,数据批量上传,数据备份等工作.

1.5 API对象说明

工作簿 一个excel文件 WorkBook HSSfWordBook : 2003版本 XSSfWorkBook : 2007级以上
工作表一个一个的sheet Sheet HssfSheet : 2003版本 XssfSheet : 2007级以上
Row HssfRow : 2003版本 XssfRow : 2007级以上
单元格 Cell HssfCell : 2003版本 XssfCell : 2007级以上

2 POI入门案列

2.1 环境搭建

pom.xml中导入坐标


    <dependencies>

<!--        操作低版本的excel-->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>4.0.1</version>
        </dependency>

<!--        操作高版本的excel-->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>4.0.1</version>
        </dependency>

<!--        操作百万级数据-->
        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml-schemas</artifactId>
            <version>4.0.1</version>
        </dependency>
    </dependencies>

2.2 创建Excel

package com.cf;

import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.io.FileOutputStream;

//创建一个Excel并且向里面随便写入一句话
public class POIDemo  {
    
    
    public static void main(String[] args) throws Exception{
    
    
 	 /* XSSFWorkbook  工作薄    一个Excel文件
        XSSFSheet     工作表    Excel中的工作表
        XSSFRow       行        每个工作表的行
        XSSFCell      单元格    每个行中的单元格
        XSSFCellStyle 单元格样式   */
		//1、创建一个全新的工作薄 里面什么都没有
        XSSFWorkbook workbook = new XSSFWorkbook();
		//2、创建一个工作表
        XSSFSheet sheet = workbook.createSheet("POI报表测试");
		//设置列宽
        sheet.setColumnWidth(0,17*256); // 单位 1代表一个字母的256分之一
		//3、创建第一行行
        XSSFRow row = sheet.createRow(0);
		//4、创建第一个单元格
        XSSFCell cell = row.createCell(0);
		//5、向单元格中放一句话
        cell.setCellValue("这是第一次用POI写数据");
		//6、把Excel输出到磁盘上
        workbook.write(new FileOutputStream("d://demo.xlsx"));

        workbook.close();
    }
}

2.3 读取Excel

package com.cf;

import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

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

//读取一个Excel中的内容
public class POITest {
    
    
    public static void main(String[] args) throws Exception{
    
    
		//1、创建一个的工作薄
        XSSFWorkbook workbook = new XSSFWorkbook(new FileInputStream("d://demo.xlsx"));
		//2、获取一个工作表
        XSSFSheet sheet = workbook.getSheetAt(0);
		//3、获取第一行行
        XSSFRow row = sheet.getRow(0);
		//4、获取第一个单元格
        XSSFCell cell = row.getCell(0);
		//5、获取单元格中的内容  字符串类型
        //需要注意不同的类型,需要不同的方法去读取
        // 字符,布尔,数字(日期,普通数字),公式,空
        String value = cell.getStringCellValue();
        System.out.println(value);
        workbook.close();
        
   /* 引入1: 根据不同的类型获取单元格值
        
    Cell cell = rowData.getCell(cellNum);
		if(cell !=null)
    {
        int cellType = cell.getCellType();

        //判断单元格数据类型
        String cellValue = "";
        switch (cellType) {
            case HSSFCell.CELL_TYPE_STRING://字符串
                System.out.print("【STRING】");
                cellValue = cell.getStringCellValue();
                break;

            case HSSFCell.CELL_TYPE_BOOLEAN://布尔
                System.out.print("【BOOLEAN】");
                cellValue = String.valueOf(cell.getBooleanCellValue());
                break;

            case HSSFCell.CELL_TYPE_BLANK://空
                System.out.print("【BLANK】");
                break;

            case HSSFCell.CELL_TYPE_NUMERIC:
                System.out.print("【NUMERIC】");
                //cellValue = String.valueOf(cell.getNumericCellValue());

                if (HSSFDateUtil.isCellDateFormatted(cell)) {//日期
                    System.out.print("【日期】");
                    Date date = cell.getDateCellValue();
                    cellValue = new DateTime(date).toString("yyyy-MM-dd");
                } else {
                    // 不是日期格式,则防止当数字过长时以科学计数法显示
                    System.out.print("【转换成字符串】");
                    cell.setCellType(HSSFCell.CELL_TYPE_STRING);
                    cellValue = cell.toString();
                }
                break;

            case Cell.CELL_TYPE_ERROR:
                System.out.print("【数据类型错误】");
                break;
        }
	*/
        
     /*  引入2:读取公式 并计算结果
             // 读取第五行第一列
        Row row = sheet.getRow(4);
        Cell cell = row.getCell(0);

        //公式计算器
        FormulaEvaluator formulaEvaluator = new HSSFFormulaEvaluator((HSSFWorkbook) workbook);

        // 输出单元内容
        int cellType = cell.getCellType();
        switch (cellType) {
            case Cell.CELL_TYPE_FORMULA://2 公式

                //得到公式
                String formula = cell.getCellFormula();
                System.out.println(formula);

                CellValue evaluate = formulaEvaluator.evaluate(cell);
                //String cellValue = String.valueOf(evaluate.getNumberValue());
                String cellValue = evaluate.formatAsString();
                System.out.println(cellValue);

                break;
        }
     */
    }
}

2.4 创建有样式的Excel

package com.cf;

import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;

import java.io.FileOutputStream;

//使用java代码创建一个新版本全新的excel,里面带样式
public class POIDemo1 {
    
    
    public static void main(String[] args) throws Exception {
    
    
		//创建了一个里面什么都没有的工作薄
        Workbook workbook = new XSSFWorkbook();

		//创建样式、
        CellStyle cellStyle = workbook.createCellStyle();
		//居中对齐
        cellStyle.setAlignment(HorizontalAlignment.CENTER);  //水平对齐方式
        cellStyle.setVerticalAlignment(VerticalAlignment.CENTER);  //垂直对齐方式
		//字体  宋体 16号 加粗
        Font font = workbook.createFont();
        font.setFontName("宋体");
        font.setFontHeightInPoints((short) 16);
        font.setBold(true);
        cellStyle.setFont(font);


		//创建样式、
        CellStyle cellStyle1 = workbook.createCellStyle();
		//居中对齐
        cellStyle1.setAlignment(HorizontalAlignment.CENTER);  //水平对齐方式
        cellStyle1.setVerticalAlignment(VerticalAlignment.CENTER);  //垂直对齐方式

        cellStyle1.setBorderBottom(BorderStyle.THIN); //下边框 细线
        cellStyle1.setBorderLeft(BorderStyle.THIN);
        cellStyle1.setBorderRight(BorderStyle.THIN);
        cellStyle1.setBorderTop(BorderStyle.THIN);

		//字体  黑体 12号
        Font font1 = workbook.createFont();
        font1.setFontName("黑体");
        font1.setFontHeightInPoints((short) 12);
        font1.setBold(false);
        cellStyle1.setFont(font1);

		//创建新的工作表sheet
        Sheet sheet = workbook.createSheet("POI报表测试");
		//设置sheet的列宽
        sheet.setColumnWidth(0,4200); //1代表一个字母宽度的256分之一
        sheet.setColumnWidth(1,26*256); //1代表一个字母宽度的256分之一
        sheet.setColumnWidth(2,16*256); //1代表一个字母宽度的256分之一
        sheet.setColumnWidth(3,26*256); //1代表一个字母宽度的256分之一
        sheet.setColumnWidth(4,16*256); //1代表一个字母宽度的256分之一
        sheet.setColumnWidth(5,16*256); //1代表一个字母宽度的256分之一
        sheet.setColumnWidth(6,16*256); //1代表一个字母宽度的256分之一
        sheet.setColumnWidth(7,16*256); //1代表一个字母宽度的256分之一
        sheet.setColumnWidth(8,16*256); //1代表一个字母宽度的256分之一
        Row bigTitleRow = sheet.createRow(0);
		//创建单元格
        for (int i = 1; i <= 8; i++) {
    
    
            bigTitleRow.createCell(i);
        }
		//设置行高
        bigTitleRow.setHeightInPoints(36);
		//合并单元格
        sheet.addMergedRegion(new CellRangeAddress(0,0,1,8));
		//向合并后的单元格中写入一句话:2021年1月份出货表
        Cell cell = bigTitleRow.getCell(1);
        cell.setCellValue("2021年1月份出货表");
        cell.setCellStyle(cellStyle);
		//创建小标题行
		//客户
        Row titleRow = sheet.createRow(1);
        Cell cell1 = titleRow.createCell(1);
        cell1.setCellValue("客户");
        cell1.setCellStyle(cellStyle1);

		//把excel输出到我的磁盘上
        workbook.write(new FileOutputStream("d://test.xlsx"));
    }
}

3 EasyExcel

3.1 概述

EasyExcel是一个基于Java的简单、省内存的读写Excel的开源项目。在尽可能节约内存的情况下支持读写百M的Excel.

EasyExcel官网

快速简单,避免OOM的Excel工具.
image-20210218215653026

3.2 写Excel

@Data
public class DemoData {
    
    

    @ExcelProperty("字符串标题")
    private String string;
    @ExcelProperty("日期标题")
    private Date date;
    @ExcelProperty("数字标题")
    private Double doubleData;
    /**
     * 忽略这个字段
     */
    @ExcelIgnore
    private String ignore;
}
 //伪数据, 真实场景为业务数据
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;
    }

    // 最简单的写
    @Test
    public void simpleWrite() {
    
    
        // 写法1
        String fileName = path+"EasyExcel.xlsx";
        // 这里 需要指定写用哪个class去写,然后写到第一个sheet,名字为模板 然后文件流会自动关闭
        // 如果这里想使用03 则 传入excelType参数即可
        EasyExcel.write(fileName, DemoData.class).sheet("模板").doWrite(data());
        
        
         // 写法2
        fileName = TestFileUtil.getPath() + "simpleWrite" + System.currentTimeMillis() + ".xlsx";
        // 这里 需要指定写用哪个class去写
        ExcelWriter excelWriter = null;
        try {
    
    
            excelWriter = EasyExcel.write(fileName, DemoData.class).build();
            WriteSheet writeSheet = EasyExcel.writerSheet("模板").build();
            excelWriter.write(data(), writeSheet);
        } finally {
    
    
            // 千万别忘记finish 会帮忙关闭流
            if (excelWriter != null) {
    
    
                excelWriter.finish();
            }
        }
    }

3.3 读Excel

    // 最简单的读
    @Test
    public void simpleRead() {
    
    
        //写法一
        String fileName = path+"EasyExcel.xlsx";
        // 这里 需要指定读用哪个class去读,然后读取第一个sheet 文件流会自动关闭
        EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).sheet().doRead();
        
                // 写法2:
        fileName = TestFileUtil.getPath() + "demo" + File.separator + "demo.xlsx";
        ExcelReader excelReader = null;
        try {
    
    
            excelReader = EasyExcel.read(fileName, DemoData.class, new DemoDataListener()).build();
            ReadSheet readSheet = EasyExcel.readSheet(0).build();
            excelReader.read(readSheet);
        } finally {
    
    
            if (excelReader != null) {
    
    
                // 这里千万别忘记关闭,读的时候会创建临时文件,到时磁盘会崩的
                excelReader.finish();
            }
        }
    }

写一个监听类, 读取时,一行一行的读取数据,批量保存.


// 有个很重要的点 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) {
    
    
        System.out.println(JSON.toJSONString(data));
        list.add(data);
        System.out.println(list);
        // 达到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() {
    
    
        demoDAO.save(list);
    }
}

猜你喜欢

转载自blog.csdn.net/ABestRookie/article/details/113854933