zoukankan      html  css  js  c++  java
  • Java使用POI读取和写入Excel指南

    Java使用POI读取和写入Excel指南

    做项目时经常有通过程序读取Excel数据,或是创建新的Excel并写入数据的需求;

    网上很多经验教程里使用的POI版本都比较老了,一些API在新版里已经废弃,这里基于最新的Apache POI 4.0.1版本来总结一下整个读取和写入Excel的过程,希望能帮助到需要的人 _

    1. 准备工作

    1.1 在项目中引入Apache POI相关类库

    引入 Apache POIApache POI-OOXML 这两个类库,Maven坐标如下:

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

    2. 读取或写入Excel数据

    2.1 示例程序结构说明

    简单说明一下示例程序的整体结构:

    • ExcelReader.java是实现读取Excel数据功能的类;
    • ExcelWriter.java是创建新的Excel并向其中写入数据的类;
    • ExcelDataVO.java封装了读取或写入时每一“行”的数据;
    • MainTest.java是示例程序的入口类,其中演示了读取和写入Excel数据的整个过程;

    2.2 读取数据

    示例程序需要从桌面读取 readExample.xlsx 内的数据,readExample.xlsx 的内容如下:
    读取示例图

    读取Excel时主要调用ExcelReader.java类来读取和解析Excel的具体内容,这里以读取系统文件的形式演示读取过程:(兼容 xls 和 xlsx)

    2.2.1 主程序入口类代码:

    /**
     * Author: Dreamer-1
     * Date: 2019-03-01
     * Time: 10:13
     * Description: 示例程序入口类
     */
    public class MainTest {
    
        public static void main(String[] args) {
            // 设定Excel文件所在路径
            String excelFileName = "/Users/Dreamer-1/Desktop/myBlog/java解析Excel/readExample.xlsx";
            // 读取Excel文件内容
            List<ExcelDataVO> readResult = ExcelReader.readExcel(excelFileName);
            
            // todo 进行业务操作
        }
    
    }
    

    读取和写入时封装每一“行”数据的ExcelDataVO.java代码如下:

    /**
     * Author: Dreamer-1
     * Date: 2019-03-01
     * Time: 11:33
     * Description: 读取Excel时,封装读取的每一行的数据
     */
    public class ExcelDataVO {
    
        /**
         * 姓名
         */
        private String name;
    
        /**
         * 年龄
         */
        private Integer age;
    
        /**
         * 居住地
         */
        private String location;
    
        /**
         * 职业
         */
        private String job;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Integer getAge() {
            return age;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        public String getLocation() {
            return location;
        }
    
        public void setLocation(String location) {
            this.location = location;
        }
    
        public String getJob() {
            return job;
        }
    
        public void setJob(String job) {
            this.job = job;
        }
    }
    
    

    2.2.2 Excel解析类的代码:

    /**
     * Author: Dreamer-1
     * Date: 2019-03-01
     * Time: 10:21
     * Description: 读取Excel内容
     */
    public class ExcelReader {
    
        private static Logger logger = Logger.getLogger(ExcelReader.class.getName()); // 日志打印类
    
        private static final String XLS = "xls";
        private static final String XLSX = "xlsx";
    
        /**
         * 根据文件后缀名类型获取对应的工作簿对象
         * @param inputStream 读取文件的输入流
         * @param fileType 文件后缀名类型(xls或xlsx)
         * @return 包含文件数据的工作簿对象
         * @throws IOException
         */
        public static Workbook getWorkbook(InputStream inputStream, String fileType) throws IOException {
            Workbook workbook = null;
            if (fileType.equalsIgnoreCase(XLS)) {
                workbook = new HSSFWorkbook(inputStream);
            } else if (fileType.equalsIgnoreCase(XLSX)) {
                workbook = new XSSFWorkbook(inputStream);
            }
            return workbook;
        }
    
        /**
         * 读取Excel文件内容
         * @param fileName 要读取的Excel文件所在路径
         * @return 读取结果列表,读取失败时返回null
         */
        public static List<ExcelDataVO> readExcel(String fileName) {
    
            Workbook workbook = null;
            FileInputStream inputStream = null;
    
            try {
                // 获取Excel后缀名
                String fileType = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length());
                // 获取Excel文件
                File excelFile = new File(fileName);
                if (!excelFile.exists()) {
                    logger.warning("指定的Excel文件不存在!");
                    return null;
                }
    
                // 获取Excel工作簿
                inputStream = new FileInputStream(excelFile);
                workbook = getWorkbook(inputStream, fileType);
    
                // 读取excel中的数据
                List<ExcelDataVO> resultDataList = parseExcel(workbook);
    
                return resultDataList;
            } catch (Exception e) {
                logger.warning("解析Excel失败,文件名:" + fileName + " 错误信息:" + e.getMessage());
                return null;
            } finally {
                try {
                    if (null != workbook) {
                        workbook.close();
                    }
                    if (null != inputStream) {
                        inputStream.close();
                    }
                } catch (Exception e) {
                    logger.warning("关闭数据流出错!错误信息:" + e.getMessage());
                    return null;
                }
            }
        }
    
        /**
         * 解析Excel数据
         * @param workbook Excel工作簿对象
         * @return 解析结果
         */
        private static List<ExcelDataVO> parseExcel(Workbook workbook) {
           List<ExcelDataVO> resultDataList = new ArrayList<>();
            // 解析sheet
            for (int sheetNum = 0; sheetNum < workbook.getNumberOfSheets(); sheetNum++) {
                Sheet sheet = workbook.getSheetAt(sheetNum);
    
                // 校验sheet是否合法
                if (sheet == null) {
                    continue;
                }
    
                // 获取第一行数据
                int firstRowNum = sheet.getFirstRowNum();
                Row firstRow = sheet.getRow(firstRowNum);
                if (null == firstRow) {
                    logger.warning("解析Excel失败,在第一行没有读取到任何数据!");
                }
    
                // 解析每一行的数据,构造数据对象
                int rowStart = firstRowNum + 1;
                int rowEnd = sheet.getPhysicalNumberOfRows();
                for (int rowNum = rowStart; rowNum < rowEnd; rowNum++) {
                    Row row = sheet.getRow(rowNum);
    
                    if (null == row) {
                        continue;
                    }
    
                    ExcelDataVO resultData = convertRowToData(row);
                    if (null == resultData) {
                        logger.warning("第 " + row.getRowNum() + "行数据不合法,已忽略!");
                        continue;
                    }
                    resultDataList.add(resultData);
                }
            }
    
            return resultDataList;
        }
    
        /**
         * 将单元格内容转换为字符串
         * @param cell
         * @return
         */
        private static String convertCellValueToString(Cell cell) {
            if(cell==null){
                return null;
            }
            String returnValue = null;
            switch (cell.getCellType()) {
                case NUMERIC:   //数字
                    Double doubleValue = cell.getNumericCellValue();
    
                    // 格式化科学计数法,取一位整数
                    DecimalFormat df = new DecimalFormat("0");
                    returnValue = df.format(doubleValue);
                    break;
                case STRING:    //字符串
                    returnValue = cell.getStringCellValue();
                    break;
                case BOOLEAN:   //布尔
                    Boolean booleanValue = cell.getBooleanCellValue();
                    returnValue = booleanValue.toString();
                    break;
                case BLANK:     // 空值
                    break;
                case FORMULA:   // 公式
                    returnValue = cell.getCellFormula();
                    break;
                case ERROR:     // 故障
                    break;
                default:
                    break;
            }
            return returnValue;
        }
    
        /**
         * 提取每一行中需要的数据,构造成为一个结果数据对象
         *
         * 当该行中有单元格的数据为空或不合法时,忽略该行的数据
         *
         * @param row 行数据
         * @return 解析后的行数据对象,行数据错误时返回null
         */
        private static ExcelDataVO convertRowToData(Row row) {
            ExcelDataVO resultData = new ExcelDataVO();
    
            Cell cell;
            int cellNum = 0;
            // 获取姓名
            cell = row.getCell(cellNum++);
            String name = convertCellValueToString(cell);
            resultData.setName(name);
            // 获取年龄
            cell = row.getCell(cellNum++);
            String ageStr = convertCellValueToString(cell);
            if (null == ageStr || "".equals(ageStr)) {
                // 年龄为空
                resultData.setAge(null);
            } else {
                resultData.setAge(Integer.parseInt(ageStr));
            }
            // 获取居住地
            cell = row.getCell(cellNum++);
            String location = convertCellValueToString(cell);
            resultData.setLocation(location);
            // 获取职业
            cell = row.getCell(cellNum++);
            String job = convertCellValueToString(cell);
            resultData.setJob(job);
    
            return resultData;
        }
    }
    

    2.2.3 应用场景补充

    一般我们会有这样的应用场景,即:在前台页面的文件上传入口上传本地的Excel文件到后台,后台收到Excel文件后进行解析并做对应的业务操作;

    这里假设前台已经有了上传文件的入口,再简单贴一下后台的解析代码;

    后台接收前台数据的Controller层代码示例:

        @PostMapping("/uploadExcel")
        public ResponseEntity<?> uploadImage(MultipartFile file) {
    
            // 检查前台数据合法性
            if (null == file || file.isEmpty()) {
                logger.warning("上传的Excel商品数据文件为空!上传时间:" + new Date());
                return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
            }
    
            try {
                // 解析Excel
                List<ExcelDataVO> parsedResult = ExcelReader.readExcel(file);
                // todo 进行业务操作
    
                return new ResponseEntity<>(HttpStatus.OK);
            } catch (Exception e) {
                logger.warning("上传的Excel商品数据文件为空!上传时间:" + new Date());
                return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
            }
    
        }
    

    ExcelReader.java中的 readExcel() 方法需要做一定的修改,代码如下:

        /**
         * 读取Excel文件内容
         * @param file 上传的Excel文件
         * @return 读取结果列表,读取失败时返回null
         */
        public static List<ExcelDataVO> readExcel(MultipartFile file) {
    
            Workbook workbook = null;
    
            try {
                // 获取Excel后缀名
                String fileName = file.getOriginalFilename();
                if (fileName == null || fileName.isEmpty() || fileName.lastIndexOf(".") < 0) {
                    logger.warning("解析Excel失败,因为获取到的Excel文件名非法!");
                    return null;
                }
                String fileType = fileName.substring(fileName.lastIndexOf(".") + 1, fileName.length());
    
                // 获取Excel工作簿
                workbook = getWorkbook(file.getInputStream(), fileType);
    
                // 读取excel中的数据
                List<ExcelDataVO> resultDataList = parseExcel(workbook);
    
                return resultDataList;
            } catch (Exception e) {
                logger.warning("解析Excel失败,文件名:" + file.getOriginalFilename() + " 错误信息:" + e.getMessage());
                return null;
            } finally {
                try {
                    if (null != workbook) {
                        workbook.close();
                    }
                } catch (Exception e) {
                    logger.warning("关闭数据流出错!错误信息:" + e.getMessage());
                    return null;
                }
            }
        }
    

    2.3 写入数据

    写入数据时主要调用ExcelWriter.java来创建Excel工作簿对象并写入数据,这里以写入系统文件数据为例演示写入的过程:

    2.3.1 主程序入口类代码

    /**
     * Author: Dreamer-1
     * Date: 2019-03-01
     * Time: 10:13
     * Description: 示例程序入口类
     */
    public class MainTest {
    
        private static Logger logger = Logger.getLogger(MainTest.class.getName());
    
        public static void main(String[] args) {
            // 创建需要写入的数据列表
            List<ExcelDataVO> dataVOList = new ArrayList<>(2);
            ExcelDataVO dataVO = new ExcelDataVO();
            dataVO.setName("小明");
            dataVO.setAge(18);
            dataVO.setLocation("广州");
            dataVO.setJob("大学生");
            ExcelDataVO dataVO2 = new ExcelDataVO();
            dataVO2.setName("小花");
            dataVO2.setAge(19);
            dataVO2.setLocation("深圳");
            dataVO2.setJob("大学生");
            dataVOList.add(dataVO);
            dataVOList.add(dataVO2);
    
            // 写入数据到工作簿对象内
            Workbook workbook = ExcelWriter.exportData(dataVOList);
    
            // 以文件的形式输出工作簿对象
            FileOutputStream fileOut = null;
            try {
                String exportFilePath = "/Users/Dreamer-1/Desktop/myBlog/java解析Excel/writeExample.xlsx";
                File exportFile = new File(exportFilePath);
                if (!exportFile.exists()) {
                    exportFile.createNewFile();
                }
    
                fileOut = new FileOutputStream(exportFilePath);
                workbook.write(fileOut);
                fileOut.flush();
            } catch (Exception e) {
                logger.warning("输出Excel时发生错误,错误原因:" + e.getMessage());
            } finally {
                try {
                    if (null != fileOut) {
                        fileOut.close();
                    }
                    if (null != workbook) {
                        workbook.close();
                    }
                } catch (IOException e) {
                    logger.warning("关闭输出流时发生错误,错误原因:" + e.getMessage());
                }
            }
    
        }
    
    }
    
    

    2.3.2 写入Excel类的代码

    ExcelWriter.java类中,你可以根据实际需要替换 CELL_HEADS 列头的信息,然后重写 convertDataToRow 方法,转换你自己的行数据;

    /**
     * Author: Dreamer-1
     * Date: 2019-03-01
     * Time: 11:09
     * Description: 生成Excel并写入数据
     */
    public class ExcelWriter {
    
        private static List<String> CELL_HEADS; //列头
    
        static{
            // 类装载时就载入指定好的列头信息,如有需要,可以考虑做成动态生成的列头
            CELL_HEADS = new ArrayList<>();
            CELL_HEADS.add("姓名");
            CELL_HEADS.add("年龄");
            CELL_HEADS.add("居住城市");
            CELL_HEADS.add("职业");
        }
    
        /**
         * 生成Excel并写入数据信息
         * @param dataList 数据列表
         * @return 写入数据后的工作簿对象
         */
        public static Workbook exportData(List<ExcelDataVO> dataList){
            // 生成xlsx的Excel
            Workbook workbook = new SXSSFWorkbook();
    
            // 如需生成xls的Excel,请使用下面的工作簿对象,注意后续输出时文件后缀名也需更改为xls
            //Workbook workbook = new HSSFWorkbook();
    
            // 生成Sheet表,写入第一行的列头
            Sheet sheet = buildDataSheet(workbook);
            //构建每行的数据内容
            int rowNum = 1;
            for (Iterator<ExcelDataVO> it = dataList.iterator(); it.hasNext(); ) {
                ExcelDataVO data = it.next();
                if (data == null) {
                    continue;
                }
                //输出行数据
                Row row = sheet.createRow(rowNum++);
                convertDataToRow(data, row);
            }
            return workbook;
        }
    
        /**
         * 生成sheet表,并写入第一行数据(列头)
         * @param workbook 工作簿对象
         * @return 已经写入列头的Sheet
         */
        private static Sheet buildDataSheet(Workbook workbook) {
            Sheet sheet = workbook.createSheet();
            // 设置列头宽度
            for (int i=0; i<CELL_HEADS.size(); i++) {
                sheet.setColumnWidth(i, 4000);
            }
            // 设置默认行高
            sheet.setDefaultRowHeight((short) 400);
            // 构建头单元格样式
            CellStyle cellStyle = buildHeadCellStyle(sheet.getWorkbook());
            // 写入第一行各列的数据
            Row head = sheet.createRow(0);
            for (int i = 0; i < CELL_HEADS.size(); i++) {
                Cell cell = head.createCell(i);
                cell.setCellValue(CELL_HEADS.get(i));
                cell.setCellStyle(cellStyle);
            }
            // 单独设置 第1 列为文本格式
            CellStyle textCellStyle= workbook.createCellStyle();
            textCellStyle.setDataFormat(HSSFDataFormat.getBuiltinFormat("@"));
            sheet.setDefaultColumnStyle(0, textCellStyle);
            // 单独设置 第2 列为整数格式
            CellStyle intNumCellStyle = workbook.createCellStyle();
            intNumCellStyle.setDataFormat(HSSFDataFormat.getBuiltinFormat("0"));
            sheet.setDefaultColumnStyle(1, intNumCellStyle);
            // 单独设置 第3 列为两位小数格式
            CellStyle floatNumCellStyle = workbook.createCellStyle();
            floatNumCellStyle.setDataFormat(HSSFDataFormat.getBuiltinFormat("0.00"));
            sheet.setDefaultColumnStyle(2, floatNumCellStyle);
            return sheet;
        }
    
        /**
         * 设置第一行列头的样式
         * @param workbook 工作簿对象
         * @return 单元格样式对象
         */
        private static CellStyle buildHeadCellStyle(Workbook workbook) {
            CellStyle style = workbook.createCellStyle();
            // 水平居中
            style.setAlignment(HorizontalAlignment.CENTER);
            // 垂直居中
            style.setVerticalAlignment(VerticalAlignment.CENTER);
            // 边框颜色和宽度设置
            style.setBorderBottom(BorderStyle.THIN);
            style.setBottomBorderColor(IndexedColors.BLACK.getIndex()); // 下边框
            style.setBorderLeft(BorderStyle.THIN);
            style.setLeftBorderColor(IndexedColors.BLACK.getIndex()); // 左边框
            style.setBorderRight(BorderStyle.THIN);
            style.setRightBorderColor(IndexedColors.BLACK.getIndex()); // 右边框
            style.setBorderTop(BorderStyle.THIN);
            style.setTopBorderColor(IndexedColors.BLACK.getIndex()); // 上边框
            // 设置背景颜色
            style.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex());
            style.setFillPattern(FillPatternType.SOLID_FOREGROUND);
            // 字体设置
            Font font = workbook.createFont();
            font.setBold(true);
            font.setFontName("宋体");
            font.setFontHeightInPoints((short) 16);
            style.setFont(font);
            return style;
        }
    
        /**
         * 将数据转换成行
         * @param data 源数据
         * @param row 行对象
         * @return
         */
        private static void convertDataToRow(ExcelDataVO data, Row row){
            int cellNum = 0;
            Cell cell;
            // 姓名
            cell = row.createCell(cellNum++);
            cell.setCellValue(null == data.getName() ? "" : data.getName());
            // 年龄
            cell = row.createCell(cellNum++);
            if (null != data.getAge()) {
                cell.setCellValue(data.getAge());
            } else {
                cell.setCellValue("");
            }
            // 所在城市
            cell = row.createCell(cellNum++);
            cell.setCellValue(null == data.getLocation() ? "" : data.getLocation());
            // 职业
            cell = row.createCell(cellNum++);
            cell.setCellValue(null == data.getJob() ? "" : data.getJob());
        }
    }
    
    

    示例程序运行后将会在指定的系统路径下生成 writeExample.xlsx文件,其内容如下:
    写入示例图

    2.3.3 应用场景补充

    一般写入Excel时会有这样的场景:前台页面上有一个导出按钮,点击后将后台某张表里的数据以Excel的形式导出,导出的Excel文件通过浏览器下载到用户系统中;

    这里默认前台页面已经有相应的按钮功能,给出对应的Controller层代码供参考:

        @GetMapping("/exportExcel")
        public void exportExcel(HttpServletRequest request, HttpServletResponse response) {
            Workbook workbook = null;
            OutputStream out = null;
            try {
                // todo 根据业务需求获取需要写入Excel的数据列表 dataList
    
                // 生成Excel工作簿对象并写入数据
                workbook = ExcelWriter.exportData(dataList);
    
                // 写入Excel文件到前端
                if(null != workbook){
                    String excelName = "示例Excel导出";
                    String fileName = excelName + DateUtil.format(new Date(), DateUtil.SHORT_DATE) + ".xlsx";
                    fileName = new String(fileName.getBytes("UTF-8"),"iso8859-1");
                    response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
                    response.setContentType("application/x-download");
                    response.setCharacterEncoding("UTF-8");
                    response.addHeader("Pargam", "no-cache");
                    response.addHeader("Cache-Control", "no-cache");
                    response.flushBuffer();
                    out = response.getOutputStream();
                    workbook.write(out);
                    out.flush();
                }
            } catch (Exception e) {
                logger.WARNING("写入Excel过程出错!错误原因:" + e.getMessage());
            } finally {
                try {
                    if (null != workbook) {
                        workbook.close();
                    }
                    if (null != out) {
                        out.close();
                    }
                } catch (IOException e) {
                    logger.WARNING("关闭workbook或outputStream出错!");
                }
            }
        }
    
    // 前台页面发送请求到后台Controller时的JS代码可参考:
    // 注意前台用ajax请求时,可能无法正常导出数据
    
    var url = "/exportExcel";
    window.location=url;
    

    3. 源码下载

    点我哦

  • 相关阅读:
    rest framework 认证 权限 频率
    rest framework 视图,路由
    rest framework 序列化
    10.3 Vue 路由系统
    10.4 Vue 父子传值
    10.2 Vue 环境安装
    10.1 ES6 的新增特性以及简单语法
    Django 跨域请求处理
    20190827 On Java8 第十四章 流式编程
    20190825 On Java8 第十三章 函数式编程
  • 原文地址:https://www.cnblogs.com/Dreamer-1/p/10469430.html
Copyright © 2011-2022 走看看