Java实现Excel文件读写

一、简介

  HSSFWorkbook XSSFWorkbook SXSSFWorkbook三者的区别

  • HSSFWorkbook:是操作Excel 2003以前(包括2003)的版本,扩展名是.xls,数据限制是65535,超过则内存溢出
  • XSSFWorkbook:是操作Excel 2007后的版本,扩展名是.xlsx,数据限制是1048576行,16384列,超过则内存溢出
  • SXSSFWorkbook:是操作Excel 2007后的版本,扩展名是.xlsx,从POI 3.8版本开始可以持久化到磁盘,减少内存溢出问题

  我们写个工具不仅能直接把简单Java对象数据直接导出到Excel,还能把数据读取成我们需要的简单的Java对象,并且可以根据你的需要选择不同的 Workbook,不用纠结HSSFWorkbook XSSFWorkbook SXSSFWorkbook分别怎么用,本文中 org.apache.poi 使用的版本是 5.0.0

二、maven依赖

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.alian</groupId>
    <artifactId>excel</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>excel</name>
    <description>Java实现Excel文件读写</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi</artifactId>
            <version>5.0.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.poi</groupId>
            <artifactId>poi-ooxml</artifactId>
            <version>5.0.0</version>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.14</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>compile</scope>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

  最重要的依赖就是下面两个

   <dependency>
       <groupId>org.apache.poi</groupId>
       <artifactId>poi</artifactId>
       <version>5.0.0</version>
   </dependency>

   <dependency>
       <groupId>org.apache.poi</groupId>
       <artifactId>poi-ooxml</artifactId>
       <version>5.0.0</version>
   </dependency>

三、工具类ExcelUtil

ExcelUtil.java

package com.alian.excel.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.*;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@Slf4j
public class ExcelUtil {

    /**
     * 从Excel读取数据返回对象集合
     *
     * @param filePath         文件路径
     * @param t                java对象
     * @param filterRowNumbers 过滤行数(比如数据的头部一行就不是数据)
     * @param <T>              泛型
     * @return List<T>
     */
    public static <T> List<T> readDataFromExcel(String filePath, Class<T> t, int filterRowNumbers) {
        FileInputStream fis = null;
        ArrayList<T> list = new ArrayList<>();
        try {
            fis = new FileInputStream(filePath);
            Workbook sheets = WorkbookFactory.create(fis);
            //获取sheet第一页(根据自己需要)
            Sheet sheet = sheets.getSheetAt(0);
            //获取表格的行数
            int totalRowNumber = sheet.getPhysicalNumberOfRows();
            //获取对象的字段列表
            Field[] fields = t.getDeclaredFields();
            for (int i = filterRowNumbers; i < totalRowNumber; i++) {
                //获取一行数据
                Row row = sheet.getRow(i);
                //实例对象放到循环内(这个bug是网友【starwenran】提到的,网址【https://blog.csdn.net/weixin_42345741】)
                T obj = t.newInstance();
//              //变量一行数据的每个单元格,row.getPhysicalNumberOfCells()是单元格的数量
                for (int j = 0, jLen = row.getPhysicalNumberOfCells(); j < jLen; j++) {
                    Cell cell = row.getCell(j);
                    //获取字段
                    Field dataField = fields[j];
                    String startWord = dataField.getName().substring(0, 1);
                    //需要注意的是如果变量是is开头的Boolean类型,它的set方法不能用下面的,因为它的set方法是去掉is的方法
                    //比如isEnable,set方法是setEnable
                    String methodName = "set" + dataField.getName().replaceFirst(startWord, startWord.toUpperCase());
                    //获取字段的set方法,dataField.getType()是参数的类型
                    Method method = t.getMethod(methodName, dataField.getType());
                    //反射调用set方法,getValueFromCell是把表格的值转成对应的类型
                    method.invoke(obj, getValueFromCell(dataField, cell));
                }
                list.add(obj);
            }
            return list;
        } catch (Exception e) {
            log.error("从Excel读取数据异常", e);
            return Collections.emptyList();
        } finally {
            try {
                if (fis != null) {
                    fis.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 把数据导入到Excel(支持HSSFWorkbook、XSSFWorkbook、SXSSFWorkbook)
     *
     * @param workbook    工作薄
     * @param sheetName   sheet名称
     * @param headerArray 头部标签数据
     * @param list        数据list
     * @param filePath    数据导出路径
     * @param <T>         泛型
     */
    public static <T> void exportDataToExcel(Workbook workbook, String sheetName, String[] headerArray, List<T> list, String filePath) {
        //生成一个表格,并命名
        Sheet sheet = workbook.createSheet(sheetName);
        //设置表格默认列宽15个字节
        sheet.setDefaultColumnWidth(15);
        //生成一个头部样式
        CellStyle headerStyle = getCellStyle(workbook, true);
        //生成表格标题
        Row headerRow = sheet.createRow(0);
        headerRow.setHeight((short) 300);

        for (int i = 0, len = headerArray.length; i < len; i++) {
            //创建头部行的一个小单元格
            Cell headerRowCell = headerRow.createCell(i);
            //设置头部单元格的样式
            headerRowCell.setCellStyle(headerStyle);
            //设置头部单元格的值
            headerRowCell.setCellValue(headerArray[i]);
        }
        //获取数据域样式
        CellStyle bodyStyle = getCellStyle(workbook, false);
        FileOutputStream os = null;
        try {
            //将数据放入sheet中
            for (int i = 0, iLen = list.size(); i < iLen; i++) {
                //创建一行,因为头部已经占用一行故需要加1
                Row dataRow = sheet.createRow(i + 1);
                T t = list.get(i);
                //利用反射,根据JavaBean属性的先后顺序,动态调用get方法得到属性的值
                Field[] fields = t.getClass().getDeclaredFields();
                try {
                    for (int j = 0, jLen = fields.length; j < jLen; j++) {
                        //获取单元格第值
                        Cell dataRowCell = dataRow.createCell(j);
                        //获取字段
                        Field dataField = fields[j];
                        String startWord = dataField.getName().substring(0, 1);
                        //需要注意的是如果变量是is开头的Boolean类型,它的get方法不能用下面的,因为它的get方法是去掉is的方法
                        //比如isEnable,get方法是getEnable
                        String methodName = "get" + dataField.getName().replaceFirst(startWord, startWord.toUpperCase());
                        //获取对象的get方法
                        Method getMethod = t.getClass().getMethod(methodName);
                        //反射调用get方法
                        Object value = getMethod.invoke(t);
                        //单元格值为String
                        dataRowCell.setCellValue(null == value ? "" : value.toString());
                        dataRowCell.setCellStyle(bodyStyle);
                    }
                } catch (Exception e) {
                    log.error("第【{}】行数据生成异常(下标0开始)", i, e);
                }
            }
            os = new FileOutputStream(filePath);
            workbook.write(os);
            os.flush();
        } catch (Exception e) {
            log.error("生成数据异常", e);
        } finally {
            try {
                if (os != null) {
                    os.close();
                }
            } catch (IOException e) {
                log.error("关闭文件异常", e);
            }
        }
    }

    /**
     * 获取单元格样式
     *
     * @param workbook 工作薄
     * @param isHeader 是否是头部标签
     * @return CellStyle
     */
    public static CellStyle getCellStyle(Workbook workbook, boolean isHeader) {
        CellStyle style = workbook.createCellStyle();
        //设置边框
        style.setBorderBottom(BorderStyle.THIN);
        style.setBorderTop(BorderStyle.THIN);
        style.setBorderLeft(BorderStyle.THIN);
        style.setBorderRight(BorderStyle.THIN);
        //设置边框颜色
        style.setLeftBorderColor(IndexedColors.BLACK.getIndex());
        style.setRightBorderColor(IndexedColors.BLACK.getIndex());
        style.setTopBorderColor(IndexedColors.BLACK.getIndex());
        style.setBottomBorderColor(IndexedColors.BLACK.getIndex());
        //水平对齐方式
        style.setAlignment(HorizontalAlignment.CENTER);
        //垂直对齐方式
        style.setVerticalAlignment(VerticalAlignment.CENTER);
        //设置字体样式
        Font font = workbook.createFont();
        font.setColor(IndexedColors.BLACK.getIndex());
        font.setFontHeightInPoints((short) 12);
        if (isHeader) {
            //设置背景色
            style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
            style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
            font.setFontHeightInPoints((short) 14);
            font.setBold(true);
        }
        //把字体应用到当前样式
        style.setFont(font);
        return style;
    }

    /**
     * 把数据转成对象字段相应的类型(待完善:其他类型的判断,默认值及为空判断)
     *
     * @param dataField
     * @param cell
     * @return
     */
    public static Object getValueFromCell(Field dataField, Cell cell) {
        String fieldTypeStr = dataField.getType().toString();
        if (fieldTypeStr.contains("String")) {
            return cell.getStringCellValue();
        } else if (fieldTypeStr.contains("Integer") || fieldTypeStr.contains("int")) {
            return Integer.parseInt(cell.getStringCellValue());
        } else if (fieldTypeStr.contains("Boolean") || fieldTypeStr.contains("boolean")) {
            return Boolean.getBoolean(cell.getStringCellValue());
        } else if (fieldTypeStr.contains("Double") || fieldTypeStr.contains("double")) {
            return Double.parseDouble(cell.getStringCellValue());
        } else if (fieldTypeStr.contains("float")) {
            return Float.parseFloat(cell.getStringCellValue());
        } else if (fieldTypeStr.contains("Long") || fieldTypeStr.contains("long")) {
            return Long.parseLong(cell.getStringCellValue());
        } else if (fieldTypeStr.contains("char")) {
            return cell.getStringCellValue().charAt(0);
        } else if (fieldTypeStr.contains("LocalTime")) {
            return LocalTime.parse(cell.getStringCellValue());
        } else if (fieldTypeStr.contains("LocalDate")) {
            return LocalDate.parse(cell.getStringCellValue());
        } else if (fieldTypeStr.contains("LocalDateTime")) {
            return LocalDateTime.parse(cell.getStringCellValue());
        } else if (fieldTypeStr.contains("Date")) {
            try {
                return new SimpleDateFormat("yyyy-MM-dd").parse(cell.getStringCellValue());
            } catch (ParseException e) {
                log.error("时间转化异常", e);
            }
        }
        return null;
    }

}

  工具类中使用到了反射的知识,来完成getset方法的操作。最近太忙了,都没有更新博客了,这个是修订后的版本,多谢网友【starwenran】的留言提醒,因为存数据的时候,随意造了重复数据,没检查读出的结果,造成读出的数据是最后一行数据,【starwenran】的博客地址是:https://blog.csdn.net/weixin_42345741,当然这个工具类还有他的不足,比如对is开头的Boolean对象的处理(本文注释中有说明),以后有时间我再优化下。

四、测试

4.1、EmployeeVo

EmployeeVo.java

package com.alian.excel.vo;

import lombok.Data;

import java.io.Serializable;
import java.time.LocalDate;

@Data
public class EmployeeVo {
    
    /**
     * 员工编号
     */
    private String id;

    /**
     * 员工姓名
     */
    private String name;

    /**
     * 员工年龄
     */
    private int age;

    /**
     * 工资
     */
    private double salary;

    /**
     * 部门
     */
    private String department;

    /**
     * 入职时间
     */
    private LocalDate hireDate;

    /**
     * 无参构造函数不能少
     */
    public EmployeeVo(){

    }

    public EmployeeVo(String id, String name, int age, double salary, String department, LocalDate hireDate) {
        this.id = id;
        this.name = name;
        this.age = age;
        this.salary = salary;
        this.department = department;
        this.hireDate = hireDate;
    }
}

4.2、写入数据到Excel

我们写个测试类

    @Test
    public void write() {
        List<EmployeeVo> dataList = new ArrayList<>();
        //仅仅是模拟数据
        for (int i = 10000; i < 10030; i++) {
            EmployeeVo employeeVo = new EmployeeVo("BAT"+i,"梁南生",18,20000.0,"研发部",LocalDate.of(2020,2,13));
            dataList.add(employeeVo);
        }
        String sheetName = "员工信息";
        String[] headerArray = new String[]{"员工编号", "员工姓名", "员工年龄", "工资", "部门", "入职时间"};
        String filePath = "C:\\myFile\\CSDN\\Excel\\Excel文件写入测试.xls";
        ExcelUtil.exportDataToExcel(new HSSFWorkbook(), sheetName, headerArray, dataList, filePath);
        //ExcelUtil.exportDataToExcel(new XSSFWorkbook(), sheetName, headerArray, dataList, filePath);
        //ExcelUtil.exportDataToExcel(new SXSSFWorkbook(),sheetName, headerArray, dataList, filePath);
    }

运行结果:
在这里插入图片描述

如果你用HSSFWorkbook 写入的数据大于65535则会报错

	17:02:37.541 [main] ERROR com.alian.excel.utils.ExcelUtil - 生成数据异常
	java.lang.IllegalArgumentException: Invalid row number (65536) outside allowable range (0..65535)

4.3、从Excel读取数据

    @Test
    public void read() {
        String filePath = "C:\\myFile\\CSDN\\Excel\\Excel文件写入测试.xls";
        List<EmployeeVo> employeeVos = ExcelUtil.readDataFromExcel(filePath, EmployeeVo.class, 1);
        log.info("读取的数据行数:{}",employeeVos.size());
        for (EmployeeVo employeeVo:employeeVos){
            log.info("{}",employeeVo);
        }
    }

运行结果:

19:54:37.313 [main] INFO com.alian.excel.service.TestExcelService - 读取的数据行数:30
19:54:37.316 [main] INFO com.alian.excel.service.TestExcelService - EmployeeVo(id=BAT10000, name=梁南生, age=18, salary=20000.0, department=研发部, hireDate=2020-02-13)
19:54:37.317 [main] INFO com.alian.excel.service.TestExcelService - EmployeeVo(id=BAT10001, name=梁南生, age=18, salary=20000.0, department=研发部, hireDate=2020-02-13)
19:54:37.317 [main] INFO com.alian.excel.service.TestExcelService - EmployeeVo(id=BAT10002, name=梁南生, age=18, salary=20000.0, department=研发部, hireDate=2020-02-13)
19:54:37.317 [main] INFO com.alian.excel.service.TestExcelService - EmployeeVo(id=BAT10003, name=梁南生, age=18, salary=20000.0, department=研发部, hireDate=2020-02-13)
19:54:37.317 [main] INFO com.alian.excel.service.TestExcelService - EmployeeVo(id=BAT10004, name=梁南生, age=18, salary=20000.0, department=研发部, hireDate=2020-02-13)
19:54:37.317 [main] INFO com.alian.excel.service.TestExcelService - EmployeeVo(id=BAT10005, name=梁南生, age=18, salary=20000.0, department=研发部, hireDate=2020-02-13)
19:54:37.317 [main] INFO com.alian.excel.service.TestExcelService - EmployeeVo(id=BAT10006, name=梁南生, age=18, salary=20000.0, department=研发部, hireDate=2020-02-13)
19:54:37.317 [main] INFO com.alian.excel.service.TestExcelService - EmployeeVo(id=BAT10007, name=梁南生, age=18, salary=20000.0, department=研发部, hireDate=2020-02-13)
19:54:37.317 [main] INFO com.alian.excel.service.TestExcelService - EmployeeVo(id=BAT10008, name=梁南生, age=18, salary=20000.0, department=研发部, hireDate=2020-02-13)
19:54:37.317 [main] INFO com.alian.excel.service.TestExcelService - EmployeeVo(id=BAT10009, name=梁南生, age=18, salary=20000.0, department=研发部, hireDate=2020-02-13)
19:54:37.317 [main] INFO com.alian.excel.service.TestExcelService - EmployeeVo(id=BAT10010, name=梁南生, age=18, salary=20000.0, department=研发部, hireDate=2020-02-13)
19:54:37.317 [main] INFO com.alian.excel.service.TestExcelService - EmployeeVo(id=BAT10011, name=梁南生, age=18, salary=20000.0, department=研发部, hireDate=2020-02-13)
19:54:37.317 [main] INFO com.alian.excel.service.TestExcelService - EmployeeVo(id=BAT10012, name=梁南生, age=18, salary=20000.0, department=研发部, hireDate=2020-02-13)
19:54:37.317 [main] INFO com.alian.excel.service.TestExcelService - EmployeeVo(id=BAT10013, name=梁南生, age=18, salary=20000.0, department=研发部, hireDate=2020-02-13)
19:54:37.317 [main] INFO com.alian.excel.service.TestExcelService - EmployeeVo(id=BAT10014, name=梁南生, age=18, salary=20000.0, department=研发部, hireDate=2020-02-13)
19:54:37.317 [main] INFO com.alian.excel.service.TestExcelService - EmployeeVo(id=BAT10015, name=梁南生, age=18, salary=20000.0, department=研发部, hireDate=2020-02-13)
19:54:37.317 [main] INFO com.alian.excel.service.TestExcelService - EmployeeVo(id=BAT10016, name=梁南生, age=18, salary=20000.0, department=研发部, hireDate=2020-02-13)
19:54:37.317 [main] INFO com.alian.excel.service.TestExcelService - EmployeeVo(id=BAT10017, name=梁南生, age=18, salary=20000.0, department=研发部, hireDate=2020-02-13)
19:54:37.317 [main] INFO com.alian.excel.service.TestExcelService - EmployeeVo(id=BAT10018, name=梁南生, age=18, salary=20000.0, department=研发部, hireDate=2020-02-13)
19:54:37.317 [main] INFO com.alian.excel.service.TestExcelService - EmployeeVo(id=BAT10019, name=梁南生, age=18, salary=20000.0, department=研发部, hireDate=2020-02-13)
19:54:37.317 [main] INFO com.alian.excel.service.TestExcelService - EmployeeVo(id=BAT10020, name=梁南生, age=18, salary=20000.0, department=研发部, hireDate=2020-02-13)
19:54:37.317 [main] INFO com.alian.excel.service.TestExcelService - EmployeeVo(id=BAT10021, name=梁南生, age=18, salary=20000.0, department=研发部, hireDate=2020-02-13)
19:54:37.317 [main] INFO com.alian.excel.service.TestExcelService - EmployeeVo(id=BAT10022, name=梁南生, age=18, salary=20000.0, department=研发部, hireDate=2020-02-13)
19:54:37.317 [main] INFO com.alian.excel.service.TestExcelService - EmployeeVo(id=BAT10023, name=梁南生, age=18, salary=20000.0, department=研发部, hireDate=2020-02-13)
19:54:37.317 [main] INFO com.alian.excel.service.TestExcelService - EmployeeVo(id=BAT10024, name=梁南生, age=18, salary=20000.0, department=研发部, hireDate=2020-02-13)
19:54:37.317 [main] INFO com.alian.excel.service.TestExcelService - EmployeeVo(id=BAT10025, name=梁南生, age=18, salary=20000.0, department=研发部, hireDate=2020-02-13)
19:54:37.317 [main] INFO com.alian.excel.service.TestExcelService - EmployeeVo(id=BAT10026, name=梁南生, age=18, salary=20000.0, department=研发部, hireDate=2020-02-13)
19:54:37.317 [main] INFO com.alian.excel.service.TestExcelService - EmployeeVo(id=BAT10027, name=梁南生, age=18, salary=20000.0, department=研发部, hireDate=2020-02-13)
19:54:37.317 [main] INFO com.alian.excel.service.TestExcelService - EmployeeVo(id=BAT10028, name=梁南生, age=18, salary=20000.0, department=研发部, hireDate=2020-02-13)
19:54:37.317 [main] INFO com.alian.excel.service.TestExcelService - EmployeeVo(id=BAT10029, name=梁南生, age=18, salary=20000.0, department=研发部, hireDate=2020-02-13)

结语

  本工具类是针对简单Java对象实现读写的,尽量不要使用继承之类的,假设你的对象实现了序列化接口,可能就要对字段 serialVersionUID进行排除。还有就是各种类型的转换并没有写全(只包括常见类型),包括数据空值处理等,大家可以根据需要进行调整。

  本文是先写入再读取,因为写入时字段的类型是字符串,所以读取的时候会转为对象相应的类型,Excel里的格式本身就是很复杂的,所以使用本工具类时,最好是使用本工具写入的,或者先把数据的格式先转为文本格式再使用。

  如果你对账文件处理,建议还是使用CSV文件,参考:Java实现CSV文件的读写(包含追加内容)

  • 7
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 6
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值