场景

使用POI官网上的事件驱动模式的示例方法,读取单sheet单次创建的Excel表格文件(.xlsx),Microsoft Excel和WPS Excel创建的表格文件可以正常读取数据,但是java代码创建的表格文件(不使用软件打开并保存)却读取不到数据。(原因是rId获取的不对、没有读取t标签)

环境

java 1.8、poi-ooxml 4.0.1 、maven工程

解决

1、动态获取rId(兼容软件创建的表格和代码创建的表格(普通方式创建))
2、增加读取t标签内容(兼容通过限制对滑动窗口内的行的访问来降低内存占用的方式创建的表格)

Main.java

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.Map;import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;public class Main {public static void main(String[] args) throws Exception {System.out.println("------读取使用Microsoft Excel创建的表格------");String MSExcelPath = "E:\\tmp\\test\\excelTest\\test-MSExcel.xlsx";Map<Integer, Map<Integer, String>> msExcelData = new ExcelEventUserModel().processOneSheet(MSExcelPath);printExcelData(msExcelData);System.out.println("------读取使用代码创建的表格------");String javaExcelPath = "E:\\tmp\\test\\excelTest\\test-JavaExcel.xlsx";createExcel(javaExcelPath);Map<Integer, Map<Integer, String>> javaExcelData = new ExcelEventUserModel().processOneSheet(javaExcelPath);printExcelData(javaExcelData);}private static void printExcelData(Map<Integer, Map<Integer, String>> map) {for (Map.Entry<Integer, Map<Integer, String>> entry : map.entrySet()) {StringBuilder rowData = new StringBuilder();for (Map.Entry<Integer, String> cell : entry.getValue().entrySet()) {rowData.append(cell.getKey()).append(": ").append(cell.getValue()).append(" ");}int row = entry.getKey();System.out.println(row + "-->" + rowData.toString());}}private static void createExcel(String filePath) throws Exception {createDirectory(filePath);OutputStream fileOut = null;XSSFWorkbook wb = null;try {fileOut = new FileOutputStream(filePath);wb = new XSSFWorkbook();XSSFSheet sheet = wb.createSheet("Sheet1");XSSFRow row_0 = sheet.createRow(0);row_0.createCell(0).setCellValue("姓名");row_0.createCell(1).setCellValue("职位");XSSFRow row_1 = sheet.createRow(1);row_1.createCell(0).setCellValue("千手纲手");row_1.createCell(1).setCellValue("五代火影");XSSFRow row_2 = sheet.createRow(2);row_2.createCell(0).setCellValue("旗木卡卡西");row_2.createCell(1).setCellValue("六代火影");wb.write(fileOut);} catch (Exception e) {e.printStackTrace();} finally {try {if (wb != null) wb.close();} catch (Exception ex) {ex.printStackTrace();}try {if (fileOut != null) fileOut.close();} catch (Exception ex) {ex.printStackTrace();}}}private static void createDirectory(String filePath) throws Exception {if (filePath == null || "".equals(filePath.trim())) {throw new Exception("filePath is empty");}File file;if (filePath.contains(".")) {file = new File(filePath.substring(0, filePath.lastIndexOf(File.separator)));} else {file = new File(filePath);}if (!file.exists()) file.mkdirs();}}

ExcelEventUserModel.java

import java.io.*;
import java.util.HashMap;
import java.util.Map;import javax.xml.parsers.ParserConfigurationException;import org.apache.poi.ooxml.util.SAXHelper;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;/*** POI事件驱动模式示例** http://poi.apache.org/components/spreadsheet/how-to.html#xssf_sax_api*/
public class ExcelEventUserModel {public Map<Integer, Map<Integer, String>> data;private String rId;public ExcelEventUserModel() {this.data = new HashMap<>();this.rId = "rId1";}/*** 读取第一个sheet的数据** @param filePath 文件路径* @return 表格数据*/public Map<Integer, Map<Integer, String>> processOneSheet(String filePath) {this.data = new HashMap<>();InputStream in = null;OPCPackage pkg = null;SharedStringsTable sst = null;InputStream sheet = null;try {in = new BufferedInputStream(new FileInputStream(new File(filePath)));pkg = OPCPackage.open(in);XSSFReader r = new XSSFReader(pkg);// 动态获取rId:通过读取workbook设置ridsetRelationshipId(r);// 这里输出一下rIdSystem.out.println("rId = " + rId);sst = r.getSharedStringsTable();XMLReader parser = fetchSheetParser(sst);sheet = r.getSheet(rId);InputSource sheetSource = new InputSource(sheet);parser.parse(sheetSource);} catch (Exception e) {e.printStackTrace();} finally {try {if (sheet != null) sheet.close();} catch (Exception closeException) {closeException.printStackTrace();}try {if (sst != null) sst.close();} catch (Exception closeException) {closeException.printStackTrace();}try {if (pkg != null) pkg.close();} catch (Exception closeException) {closeException.printStackTrace();}try {if (in != null) in.close();} catch (Exception closeException) {closeException.printStackTrace();}}return this.data;}private void setRelationshipId(XSSFReader r)throws IOException, InvalidFormatException, SAXException, ParserConfigurationException {InputStream workbookData = r.getWorkbookData();InputSource sheetSource = new InputSource(workbookData);XMLReader parser = WorkbookParser();parser.parse(sheetSource);}private XMLReader WorkbookParser() throws SAXException, ParserConfigurationException {XMLReader parser = SAXHelper.newXMLReader();ContentHandler handler = new WorkbookHandler();parser.setContentHandler(handler);return parser;}private class WorkbookHandler extends DefaultHandler {private WorkbookHandler() {}public void startElement(String uri, String localName, String name, Attributes attributes) {if ("sheet".equals(name)) {rId = attributes.getValue("r:id");}}public void endElement(String uri, String localName, String name) {}}private XMLReader fetchSheetParser(SharedStringsTable sst)throws SAXException, ParserConfigurationException {XMLReader parser = SAXHelper.newXMLReader();ContentHandler handler = new SheetHandler(sst);parser.setContentHandler(handler);return parser;}/*** See org.xml.sax.helpers.DefaultHandler javadocs*/private class SheetHandler extends DefaultHandler {private SharedStringsTable sst;private String lastContents;private boolean nextIsString;private int rowIndex;private int colIndex;private Map<Integer, String> rowData;private SheetHandler(SharedStringsTable sst) {this.sst = sst;this.rowData = new HashMap<>();// 默认设置为第0行this.rowIndex = 0;// 默认设置为第0列this.colIndex = 0;}public void startElement(String uri, String localName, String name, Attributes attributes) {// 一行开始if ("row".equals(name)) {this.rowIndex = Integer.parseInt(attributes.getValue("r")) - 1;}// 单元格if ("c".equals(name)) {String cellType = attributes.getValue("t");this.nextIsString = "s".equals(cellType);this.colIndex = getColIndex(attributes.getValue("r"));}// Clear contents cachelastContents = "";}public void endElement(String uri, String localName, String name) {// Process the last contents as required.// Do now, as characters() may be called more than onceif (nextIsString) {int idx = Integer.parseInt(lastContents);lastContents = sst.getItemAt(idx).getString();nextIsString = false;}// v => contents of a cellif ("v".equals(name) || "t".equals(name)) {// 放入行数据中,key=列数,value=单元格的值rowData.put(colIndex, lastContents);}// 一行的结束if ("row".equals(name)) {// 新的一行,存储上一行的数据data.put(rowIndex, rowData);this.rowData = new HashMap<>();}}public void characters(char[] ch, int start, int length) {lastContents += new String(ch, start, length);}/*** 转换表格引用为列编号,A-0,B-1** @param cellReference 列引用,例:A1* @return 表格列位置,从0开始算*/private int getColIndex(String cellReference) {String ref = cellReference.replaceAll("\\d+", "");int num;int result = 0;int length = ref.length();for (int i = 0; i < length; i++) {char ch = cellReference.charAt(length - i - 1);num = ch - 'A' + 1;num *= Math.pow(26, i);result += num;}return result - 1;}/*** 转换表格引用为行号* * @param cellReference 列引用,例:A1* @return 行号,从0开始*/private int getRowIndex(String cellReference) {String rowIndexStr = cellReference.replaceAll("[a-zA-Z]+", "");return Integer.parseInt(rowIndexStr) - 1;}}}

maven dependency

    <dependencies><!-- https://mvnrepository.com/artifact/org.apache.poi/poi-ooxml --><dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>4.0.1</version></dependency></dependencies>

结果

test-MSExcel.xlsx

test-JavaExcel.xlsx

------读取使用Microsoft Excel创建的表格------
rId = rId1
0-->0: 姓名 1: 职位
1-->0: 千手柱间 1: 初代火影
2-->0: 千手扉间 1: 二代火影
3-->0: 猿飞日斩 1: 三代火影
4-->0: 波风水门 1: 四代火影
------读取使用代码创建的表格------
rId = rId3
0-->0: 姓名 1: 职位
1-->0: 千手纲手 1: 五代火影
2-->0: 旗木卡卡西 1: 六代火影 

解析

将.xlsx表格文件后缀名改为.zip并解压之后,可以看到很多xml文件。

1、MS Excel创建的.xlsx表格文件,其workbook.xml中的sheet参数为

<sheets><sheet name="Sheet1" sheetId="1" r:id="rId1"/></sheets>

2、java代码创建的.xlsx表格文件,其workbook.xml中的sheet参数为

<sheets><sheet name="Sheet1" r:id="rId3" sheetId="1"/></sheets>

注:如果你使用软件打开java代码创建的.xlsx表格文件,并保存一下,你会发现rId3会变成rId1。

3、MS Excel创建的.xlsx表格文件,其sheet1.xml中的sheetData参数为

<sheetData><row r="1" spans="1:2" x14ac:dyDescent="0.2"><c r="A1" s="1" t="s"><v>0</v></c><c r="B1" s="1" t="s"><v>1</v></c></row><row r="2" spans="1:2" x14ac:dyDescent="0.2"><c r="A2" t="s"><v>2</v></c><c r="B2" t="s"><v>3</v></c></row><row r="3" spans="1:2" x14ac:dyDescent="0.2"><c r="A3" t="s"><v>4</v></c><c r="B3" t="s"><v>5</v></c></row><row r="4" spans="1:2" x14ac:dyDescent="0.2"><c r="A4" t="s"><v>6</v></c><c r="B4" t="s"><v>7</v></c></row><row r="5" spans="1:2" x14ac:dyDescent="0.2"><c r="A5" t="s"><v>8</v></c><c r="B5" t="s"><v>9</v></c></row>
</sheetData>

上面存储值的为v标签

其实,这里存的是共享字符串表(代码中的sst)中的引用,其对应的文件为sharedStrings.xml,内容如下

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<sst xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" count="10" uniqueCount="10"><si><t>姓名</t><phoneticPr fontId="1" type="noConversion"/></si><si><t>职位</t><phoneticPr fontId="1" type="noConversion"/></si><si><t>千手柱间</t><phoneticPr fontId="1" type="noConversion"/></si><si><t>初代火影</t><phoneticPr fontId="1" type="noConversion"/></si><si><t>千手扉间</t><phoneticPr fontId="1" type="noConversion"/></si><si><t>二代火影</t><phoneticPr fontId="1" type="noConversion"/></si><si><t>猿飞日斩</t><phoneticPr fontId="1" type="noConversion"/></si><si><t>三代火影</t><phoneticPr fontId="1" type="noConversion"/></si><si><t>波风水门</t><phoneticPr fontId="1" type="noConversion"/></si><si><t>四代火影</t><phoneticPr fontId="1" type="noConversion"/></si>
</sst>

4、java代码创建的.xlsx表格文件,其sheet1.xml中的sheetData参数为

<sheetData><row r="1"><c r="A1" t="s"><v>0</v></c><c r="B1" t="s"><v>1</v></c></row><row r="2"><c r="A2" t="s"><v>2</v></c><c r="B2" t="s"><v>3</v></c></row><row r="3"><c r="A3" t="s"><v>4</v></c><c r="B3" t="s"><v>5</v></c></row>
</sheetData>

上面存储值的也是v标签

另:使用通过限制对滑动窗口内的行的访问来降低内存占用的方式创建的表格,

其sheet1.xml中的sheetData参数为

<row r="1"><c r="A1" t="inlineStr"><is><t>A1</t></is></c><c r="B1" t="inlineStr"><is><t>B1</t></is></c><c r="C1" t="inlineStr"><is><t>C1</t></is></c><c r="D1" t="inlineStr"><is><t>D1</t></is></c><c r="E1" t="inlineStr"><is><t>E1</t></is></c><c r="F1" t="inlineStr"><is><t>F1</t></is></c><c r="G1" t="inlineStr"><is><t>G1</t></is></c><c r="H1" t="inlineStr"><is><t>H1</t></is></c><c r="I1" t="inlineStr"><is><t>I1</t></is></c><c r="J1" t="inlineStr"><is><t>J1</t></is></c>
</row>

上面存储值的为t标签

【截止到这里】分析了rId值不一样(rId1、rId3),和存储单元格的值的标签页不一样(v、t标签)。(上面的图片和内容有点多,小结一下)

5、POI事件驱动模式读取表格,是通过rId的值获取对应的.xml文件,然后读取对应的.xml中的每个标签,例如sheet1.xml中的读取为:worksheet-->dimension-->sheetViews-->sheetView-->sheetFormatPr-->sheetData-->row-->c-->v-->......不同方式创建的表格文件,标签会有些不同,但表格数据部分sheetData、row、c、v或sheetData、row、c、is、t,表示行和表示单元格的标签都是一样的:row和c。(通过断点调试就很清楚了,断点行:if ("row".equals(name)) {,name即为标签)

6、所以,通过动态获取rId的值和增加对t标签的兼容,就能解决读取不到数据的问题了(虽然场景使用范围不大,仅支持一个sheet的Excel表格文件)。动态获取rId,我是参照读取sheet1.xml的整套方法(fetchSheetParser、SheetHandler)以及对标签的读取流程,自己写了个读取流程,获取workbook.xml中sheet标签中的r:id的值。(好像也不难)

参考链接

https://poi.apache.org/components/spreadsheet/how-to.html#sxssf

说明

1、之前一直被这个问题困扰:为什么POI自己导出的表格,POI自己会读取不了?一直以为这是个大bug,不过,后来折腾了很久,发现了rId和v、t标签的问题。另外,补充个东西:通过限制对滑动窗口内的行的访问来降低内存占用的方式创建Excel,它支持大量数据写,并且不会耗很大的内存,比传统方式创建Excel更优(参考链接中有相关方法,SXSSF (Streaming Usermodel API)部分)。

2、POI事件驱动模式官方示例在参考链接中的XSSF and SAX (Event API)部分。

3、此解决方案仅适用于单sheet单次创建的.xlsx表格,暂不支持多sheet表格读取,支持java代码POI普通方式创建表格(Main.java中的createExcel方法)和通过限制对滑动窗口内的行的访问来降低内存占用的方式创建表格(详见参考链接)两种表格创建方式。

(PS:关于志村团藏这个火影就……此例中就不考虑了,哈哈哈(测试数据内容部分))

【改进】

https://blog.csdn.net/qq_36533690/article/details/106173449

【GitHub】

https://github.com/cxzgwing/poi

解决POI事件驱动模式读取不到Java代码创建的Excel表格数据问题相关推荐

  1. java代码实现导出Excel表格、工具ssm框架、maven、idea

    第一步.导入依赖 <!--生成excel文件--><dependency><groupId>org.apache.poi</groupId><ar ...

  2. java性别分类汇总,excel表格数据男女分类汇总-在Excel中,对数据清单进行“按性别分类汇总出男女......

    在Excel中,对数据清单进行"按性别分类汇总出男女... 在Excel中,对数据清单进行性别分类汇总出男女生的英语平均分"可使用AVERAGEIF函数实现. 操骤如下: 1.打开 ...

  3. java操作mysql导表_Java实现批量导入excel表格数据到数据库中的方法

    本文实例讲述了Java实现批量导入excel表格数据到数据库中的方法.分享给大家供大家参考,具体如下: 1.创建导入抽象类 package com.gcloud.common.excel; impor ...

  4. java 操作 word 表格和样式,java读取word表格中的表格 java如何读取word中的excel表格数据...

    Java 利用poi 可以直接读取word中的表格保持样式生1.读取word 2003及word 2007需要的jar包 读取 2003 版本(.doc)的word文件相对来说比较简单,只需要 poi ...

  5. java中KMP模式_朴素模式匹配算法、kmp模式匹配算法、kmp模式匹配算法改进。java代码...

    ** 朴素模式匹配算法.kmp模式匹配算法.kmp模式匹配算法改进.java代码** 思路过段时间整理~ 可以先看看阮一峰的这篇博客,字符串匹配的KMP算法 package edu.hubu.base ...

  6. Java实现Excel表格数据的导入(兼容xls与xlsx)

    Java实现Excel表格数据的导入(兼容xls与xlsx) 目录 依赖 代码 注意点 目录 依赖 <!-- 添加POI的依赖用于Excel的操作 --><dependency> ...

  7. linux qt写入excel文件内容,Qt 读取Excel表格数据 生成Excel表格并写入数据

    Qt 读取Excel表格数据 生成Excel表格并写入数据 Qt 读取Excel表格数据 生成Excel表格并写入数据 修改.pro文件,增加 axcontainer QT += axcontaine ...

  8. 使用最新的poi-4.1.0.jar导入导出Excel表格——读取Excel表格数据用法

    使用最新的poi-4.1.0.jar导入导出Excel表格--读取Excel表格数据用法 其中主要的一点心得就是在switch语句哪里进行读取数据转换时,我看到网上的一些用法都是使用 HSSFCell ...

  9. Java中Excel表格数据的导入和导出步骤和方法

    Java Excel API既可以从本地文件系统的一个文件(.xls),也可以从输入流中读取Excel数据表.读取Excel数据表的第一步是创建Workbook(术 语:工作薄),下面的代码片段举例说 ...

最新文章

  1. [译] React 路由和 React 组件的爱恨情仇
  2. oracle 整个表空间迁移,ORACLE表批量迁移表空间
  3. Webstorm中提示Cannot find module 'webpack.dev.conf.js'
  4. kvm cobbler无人值守批量安装操作系统
  5. HDMI高清光端机产品介绍
  6. axios vue 回调函数_VUE使用axios调用后台API接口的方法
  7. python 怎么判断文件存在哪里_Python判断文件和文件夹是否存在的方法
  8. 大学计算机知识考试题,大学计算机基础重点知识考试试题
  9. Visual Studio 常用宏
  10. Atitit 读取音频音乐文件的bpm 目录 1.1. Librosa是一个用于音频、音乐分析、处理的python工具包, 1 1.2. \bpm.py 1 1.3. Echo 2 1.4. Cod
  11. 通信技术专业技术人员考试 动力与环境_2020年中级通信工程师动力与环境考试大纲...
  12. 手机APP逆向工具介绍
  13. 金蝶EAS,序时簿界面数据背景色,根据枚举值设置背景色
  14. html设置图片为部分背景颜色,设置HTML的一个部分作为一个不同的背景颜色
  15. 图像压缩-《Learned Image Compression with Discretized Gaussian Mixture Likelihoods and Attention Modules》
  16. 微信挪车功能成功上线,祝贺一下自己
  17. 免费不限速不限存储的网盘推荐
  18. Hi3519V101移植SDL+FreeType+SDL_ttf
  19. HDOJ题目分类大全
  20. Linux内核中的IPSEC实现(3) ---转载

热门文章

  1. 【阅读论文】第八章--多图像的质量增强--博-自动化眼底图像分析技术可筛查糖尿病患者的视网膜疾病
  2. msp430发送pwm信号_msp430TAx PWM输出详解
  3. Elasticsearch5.0 安装 以及 问题集锦
  4. 使用sunshine+moonlight 实现电脑串流到电视(Android 设备)低延迟投屏
  5. vscode 关闭 编辑框右侧的 预览框
  6. 推荐21款最佳 HTML5 网页游戏
  7. typora上传图片出现Can‘t find smms config错误
  8. 10.cocos2d坐标系
  9. NLP会议介绍 2019
  10. 云班课计算机基础知识答案,云班课上的作业