1.当使用sax事件驱动读取大数据量的excel的使用maven导入我们需要的jar包:
<properties>
<poi.version>3.17</poi.version>
<xerces.version>2.11.0</xerces.version>
</properties>
<!-- POI -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>${poi.version}</version>
</dependency>
<dependency>
<groupId>xerces</groupId>
<artifactId>xercesImpl</artifactId>
<version>${xerces.version}</version>
</dependency>
2.定义行处理器:
public interface RowHandler {
/**
* 处理一行数据
* @param sheetIndex 当前Sheet序号
* @param rowIndex 当前行号
* @param rowList 行数据列表
*/
void handle(int sheetIndex, int rowIndex, List<Object> rowList);
}
3.定于字符串枚举类型
public enum CellDataType {
/** Boolean类型 */
BOOL("b"),
/** 类型错误 */
ERROR("e"),
/** 计算结果类型 */
FORMULA("str"),
/** 富文本类型 */
INLINESTR("inlineStr"),
/** 字符串类型 */
SSTINDEX("s"),
/** 数字类型 */
NUMBER(""),
/** 日期类型 */
DATE("m/d/yy"),
/** 空类型 */
NULL("");
/** 属性值 */
private String name;
/**
* 构造
*
* @param name 类型属性值
*/
private CellDataType(String name) {
this.name = name;
}
/**
* 获取对应类型的属性值
*
* @return 属性值
*/
public String getName() {
return name;
}
/**
* 类型字符串转为枚举
* @param name 类型字符串
* @return 类型枚举
*/
public static CellDataType of(String name) {
if(null == name) {
//默认数字
return NUMBER;
}
if(BOOL.name.equals(name)) {
return BOOL;
}else if(ERROR.name.equals(name)) {
return ERROR;
}else if(INLINESTR.name.equals(name)) {
return INLINESTR;
}else if(SSTINDEX.name.equals(name)) {
return SSTINDEX;
}else if(FORMULA.name.equals(name)) {
return FORMULA;
}else {
return NULL;
}
}
}
4.定义一个抽象接口为实现接口编程,方便扩展03版
import java.io.File;
import java.io.InputStream;
public interface SaxReader<T> {
/**
* 开始读取Excel,读取所有sheet
*
* @param path Excel文件路径
* @return this
*/
T read(String path) ;
/**
* 开始读取Excel,读取所有sheet
*
* @param file Excel文件
* @return this
*/
T read(File file) ;
/**
* 开始读取Excel,读取所有sheet,读取结束后并不关闭流
*
* @param in Excel包流
* @return this
*/
T read(InputStream in) ;
/**
* 开始读取Excel
*
* @param path 文件路径
* @param sheetIndex Excel中的sheet编号,如果为-1处理所有编号的sheet
* @return this
*/
T read(String path, int sheetIndex);
/**
* 开始读取Excel
*
* @param file Excel文件
* @param sheetIndex Excel中的sheet编号,如果为-1处理所有编号的sheet
* @return this
*/
T read(File file, int sheetIndex);
/**
* 开始读取Excel,读取结束后并不关闭流
*
* @param in Excel流
* @param sheetIndex Excel中的sheet编号,如果为-1处理所有编号的sheet
* @return this
*/
T read(InputStream in, int sheetIndex) ;
}
5.定义抽象类实现部分接口方法
import java.io.File;
import java.io.InputStream;
public abstract class AbstractReader<T> implements SaxReader<T> {
@Override
public T read(String path) {
return read(new File(path),-1);
}
@Override
public T read(File file) {
return read(file,-1);
}
@Override
public T read(InputStream in) {
return read(in,-1);
}
@Override
public T read(String path, int sheetIndex) {
return read(new File(path),sheetIndex);
}
}
- 编写实现方法实现事件驱动
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.exceptions.OpenXML4JException;
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.ss.usermodel.BuiltinFormats;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.apache.poi.xssf.model.StylesTable;
import org.apache.poi.xssf.usermodel.XSSFCellStyle;
import org.xml.sax.*;
import org.xml.sax.helpers.XMLReaderFactory;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Excel07Bysax extends AbstractReader<Excel07Bysax> implements ContentHandler {
// saxParser
private static final String CLASS_SAXPARSER = "org.apache.xerces.parsers.SAXParser";
/** Cell单元格元素 */
private static final String C_ELEMENT = "c";
/** 行元素 */
private static final String ROW_ELEMENT = "row";
/** Cell中的行列号 */
private static final String R_ATTR = "r";
/** Cell类型 */
private static final String T_ELEMENT = "t";
/** SST(SharedStringsTable) 的索引 */
private static final String S_ATTR_VALUE = "s";
// 列中属性值
private static final String T_ATTR_VALUE = "t";
// sheet r:Id前缀
private static final String RID_PREFIX = "rId";
// excel 2007 的共享字符串表,对应sharedString.xml
private SharedStringsTable sharedStringsTable;
// 当前行
private int curRow;
// 当前列
private int curCell;
// 上一次的内容
private String lastContent;
// 单元数据类型
private CellDataType cellDataType;
// 当前列坐标, 如A1,B5
private String curCoordinate;
// 前一个列的坐标
private String preCoordinate;
// 行的最大列坐标
private String maxCellCoordinate;
// 单元格的格式表,对应style.xml
private StylesTable stylesTable;
// 单元格存储格式的索引,对应style.xml中的numFmts元素的子元素索引
private int numFmtIndex;
// 单元格存储的格式化字符串,nmtFmt的formateCode属性的值
private String numFmtString;
// sheet的索引
private int sheetIndex;
// 存储每行的列元素
List<Object> rowCellList = new ArrayList<>();
/** 行处理器 */
private RowHandler rowHandler;
/**
* 构造
*
* @param rowHandler 行处理器
*/
public Excel07Bysax(RowHandler rowHandler) {
this.rowHandler = rowHandler;
}
@Override
public Excel07Bysax read(File file, int sheetIndex) {
try {
return read(OPCPackage.open(file),sheetIndex);
} catch (InvalidFormatException e) {
e.printStackTrace();
}
return null;
}
@Override
public Excel07Bysax read(InputStream in, int sheetIndex) {
try {
return read(OPCPackage.open(in),sheetIndex);
} catch (InvalidFormatException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public Excel07Bysax read(OPCPackage opcPackage, int sheetIndex) {
//获得excel包
InputStream sheetInputStream = null;
try{
//xssf读取器
final XSSFReader xssfReader = new XSSFReader(opcPackage);
// 获取共享样式表
//style.xml
stylesTable = xssfReader.getStylesTable();
// 获取共享字符串表
// sharedStrings.xml
this.sharedStringsTable = xssfReader.getSharedStringsTable();
if (sheetIndex > -1) {
this.sheetIndex = sheetIndex;
// 根据 rId# 或 rSheet# 查找sheet
sheetInputStream = xssfReader.getSheet(RID_PREFIX + (sheetIndex + 1));
parse(sheetInputStream);
} else {
this.sheetIndex = -1;
// 遍历所有sheet
final Iterator<InputStream> sheetInputStreams = xssfReader.getSheetsData();
while (sheetInputStreams.hasNext()) {
// 重新读取一个sheet时行归零
curRow = 0;
this.sheetIndex++;
sheetInputStream = sheetInputStreams.next();
parse(sheetInputStream);
}
}
} catch (IOException e) {
e.printStackTrace();
} catch (OpenXML4JException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
}
return this;
}
/**
* 读取第一个xml时标签回调处理方法
* @param uri
* @param localName
* @param qName
* @param attributes
* @throws SAXException
*/
@Override
public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
// 单元格元素
if (C_ELEMENT.equals(qName)) {//c为元素名称
// 获取当前列坐标
String tempCurCoordinate = attributes.getValue(R_ATTR);//获取当前位置
// 前一列为null,则将其设置为"@",A为第一列,ascii码为65,前一列即为@,ascii码64
if (preCoordinate == null) {
preCoordinate = String.valueOf('@');
} else {
// 存在,则前一列要设置为上一列的坐标
preCoordinate = curCoordinate;
}
// 重置当前列
curCoordinate = tempCurCoordinate;
// 设置单元格类型
setCellType(attributes);
}
lastContent = "";
}
/**
* 设置单元格的类型
*
* @param attribute
*/
private void setCellType(Attributes attribute) {
// 重置numFmtIndex,numFmtString的值
numFmtIndex = 0;
numFmtString = "";
this.cellDataType = CellDataType.of(attribute.getValue(T_ATTR_VALUE));
// 获取单元格的xf索引,对应style.xml中cellXfs的子元素xf的第几个
final String xfIndexStr = attribute.getValue(S_ATTR_VALUE);
//判断是否为日期类型
if (xfIndexStr != null) {
int xfIndex = Integer.parseInt(xfIndexStr);
XSSFCellStyle xssfCellStyle = stylesTable.getStyleAt(xfIndex);
numFmtIndex = xssfCellStyle.getDataFormat();
numFmtString = xssfCellStyle.getDataFormatString();
if (numFmtString == null) {
cellDataType = CellDataType.NULL;
numFmtString = BuiltinFormats.getBuiltinFormat(numFmtIndex);
} else if (org.apache.poi.ss.usermodel.DateUtil.isADateFormat(numFmtIndex, numFmtString)) {
cellDataType = CellDataType.DATE;
}
}
}
/**
* 标签结束的回调处理方法
* @param uri
* @param localName
* @param qName
* @throws SAXException
*/
@Override
public void endElement(String uri, String localName, String qName) throws SAXException {
final String contentStr = StrUtil.trim(lastContent);
if (T_ELEMENT.equals(qName)) {
// type标签
rowCellList.add(curCell++, contentStr);
} else if (C_ELEMENT.equals(qName)) {
// cell标签
Object value = ExcelSaxUtil.getDataValue(this.cellDataType, contentStr, this.sharedStringsTable, this.numFmtString);
// 补全单元格之间的空格
fillBlankCell(preCoordinate, curCoordinate, false);
rowCellList.add(curCell++, value);
} else if (ROW_ELEMENT.equals(qName)) {
// 如果是row标签,说明已经到了一行的结尾
// 最大列坐标以第一行的为准
if (curRow == 0) {
maxCellCoordinate = curCoordinate;
}
// 补全一行尾部可能缺失的单元格
if (maxCellCoordinate != null) {
fillBlankCell(curCoordinate, maxCellCoordinate, true);
}
rowHandler.handle(sheetIndex, curRow, rowCellList);
// 一行结束
// 清空rowCellList,
rowCellList.clear();
// 行数增加
curRow++;
// 当前列置0
curCell = 0;
// 置空当前列坐标和前一列坐标
curCoordinate = null;
preCoordinate = null;
}
}
/**
* s标签结束的回调处理方法
* @param ch
* @param start
* @param length
* @throws SAXException
*/
@Override
public void characters(char[] ch, int start, int length) throws SAXException {
// 得到单元格内容的值
lastContent = lastContent.concat(new String(ch, start, length));
}
/**
* 填充空白单元格,如果前一个单元格大于后一个,不需要填充<br>
*
* @param preCoordinate 前一个单元格坐标
* @param curCoordinate 当前单元格坐标
* @param isEnd 是否为最后一个单元格
*/
private void fillBlankCell(String preCoordinate, String curCoordinate, boolean isEnd) {
if (false == curCoordinate.equals(preCoordinate)) {
int len = ExcelSaxUtil.countNullCell(preCoordinate, curCoordinate);
if (isEnd) {
len++;
}
while (len-- > 0) {
rowCellList.add(curCell++, "");
}
}
}
@Override
public void setDocumentLocator(Locator locator) {
}
@Override
public void startDocument() throws SAXException {
}
@Override
public void endDocument() throws SAXException {
}
@Override
public void startPrefixMapping(String prefix, String uri) throws SAXException {
}
@Override
public void endPrefixMapping(String prefix) throws SAXException {
}
@Override
public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
}
@Override
public void processingInstruction(String target, String data) throws SAXException {
}
@Override
public void skippedEntity(String name) throws SAXException {
}
//
/**
* 处理流中的Excel数据
*
* @param sheetInputStream sheet流
* @throws IOException IO异常
* @throws SAXException SAX异常
*/
private void parse(InputStream sheetInputStream) throws IOException, SAXException {
fetchSheetReader().parse(new InputSource(sheetInputStream));
}
/**
* 获取sheet的解析器
*
* @return {@link XMLReader}
* @throws SAXException SAX异常
*/
private XMLReader fetchSheetReader() throws SAXException {
//创建xml读取器
XMLReader xmlReader = null;
try {
xmlReader = XMLReaderFactory.createXMLReader(CLASS_SAXPARSER);
} catch (SAXException e) {
if (e.getMessage().contains("org.apache.xerces.parsers.SAXParser")) {
throw new RuntimeException(e+ "You need to add 'xerces:xercesImpl' to your project and version >= 2.11.0");
} else {
throw e;
}
}
//添加本类进入xml解析器
xmlReader.setContentHandler(this);
return xmlReader;
}
}
7.加入实现类中的一些工具类
package com.xx.websocket.saxexcel;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import java.util.Date;
public class ExcelSaxUtil {
// 填充字符串
public static final char CELL_FILL_CHAR = '@';
// 列的最大位数
public static final int MAX_CELL_BIT = 3;
/**
* 计算两个单元格之间的单元格数目(同一行)
*
* @param preRef 前一个单元格位置,例如A1
* @param ref 当前单元格位置,例如A8
* @return 同一行中两个单元格之间的空单元格数
*/
public static int countNullCell(String preRef, String ref) {
// excel2007最大行数是1048576,最大列数是16384,最后一列列名是XFD
// 数字代表列,去掉列信息
String preXfd = StrUtil.nullToDefault(preRef, "@").replaceAll("\\d+", "");
String xfd = StrUtil.nullToDefault(ref, "@").replaceAll("\\d+", "");
// A表示65,@表示64,如果A算作1,那@代表0
// 填充最大位数3
preXfd = StrUtil.fillBefore(preXfd, CELL_FILL_CHAR, MAX_CELL_BIT);
xfd = StrUtil.fillBefore(xfd, CELL_FILL_CHAR, MAX_CELL_BIT);
char[] preLetter = preXfd.toCharArray();
char[] letter = xfd.toCharArray();
// 用字母表示则最多三位,每26个字母进一位
int res = (letter[0] - preLetter[0]) * 26 * 26 + (letter[1] - preLetter[1]) * 26 + (letter[2] - preLetter[2]);
return res - 1;
}
/**
* 根据数据类型获取数据
*
* @param cellDataType 数据类型枚举
* @param value 数据值
* @param sharedStringsTable {@link SharedStringsTable}
* @param numFmtString 数字格式名
* @return 数据值
*/
public static Object getDataValue(CellDataType cellDataType, String value, SharedStringsTable sharedStringsTable, String numFmtString) {
if (null == value) {
return null;
}
Object result;
switch (cellDataType) {
case BOOL:
result = (value.charAt(0) != '0');
break;
case ERROR:
result="error:"+value.toString();
// result = StrUtil.format("\\\"ERROR: {} ", value);
break;
case FORMULA:
// result = StrUtil.format("\"{}\"", value);
result=value;
break;
case INLINESTR:
result = new XSSFRichTextString(value.toString()).toString();
break;
case SSTINDEX:
try {
final int index = Integer.parseInt(value);
result = new XSSFRichTextString(sharedStringsTable.getEntryAt(index)).getString();
} catch (NumberFormatException e) {
result = value;
}
break;
case NUMBER:
result = getNumberValue(value, numFmtString);
break;
case DATE:
try {
result = getDateValue(value);
} catch (Exception e) {
result = value;
}
break;
default:
result = value;
break;
}
return result;
}
/**
* 获取日期
*
* @param value 单元格值
* @return 日期
* @since 4.1.0
*/
private static Date getDateValue(String value) {
return org.apache.poi.ss.usermodel.DateUtil.getJavaDate(Double.parseDouble(value), false);
}
/**
* 获取数字类型值
*
* @param value 值
* @param numFmtString 格式
* @return 数字,可以是Double、Long
* @since 4.1.0
*/
private static Number getNumberValue(String value, String numFmtString) {
if(StrUtil.isBlank(value)) {
return null;
}
double numValue = Double.parseDouble(value);
// 普通数字
if (null != numFmtString && numFmtString.indexOf('.') < 0) {
final long longPart = (long) numValue;
if (longPart == numValue) {
// 对于无小数部分的数字类型,转为Long
return longPart;
}
}
return numValue;
}
}
此为工具类
package com.xx.websocket.saxexcel;
public class StrUtil {
public static final String EMPTY = "";
/**
* 当给定字符串为null时,转换为Empty
*
* @param str 被转换的字符串
* @return 转换后的字符串
*/
public static String nullToEmpty(CharSequence str) {
return nullToDefault(str, EMPTY);
}
/**
* 如果字符串是<code>null</code>,则返回指定默认字符串,否则返回字符串本身。
*
* <pre>
* nullToDefault(null, "default") = "default"
* nullToDefault("", "default") = ""
* nullToDefault(" ", "default") = " "
* nullToDefault("bat", "default") = "bat"
* </pre>
*
* @param str 要转换的字符串
* @param defaultStr 默认字符串
*
* @return 字符串本身或指定的默认字符串
*/
public static String nullToDefault(CharSequence str, String defaultStr) {
return (str == null) ? defaultStr : str.toString();
}
/**
* 将已有字符串填充为规定长度,如果已有字符串超过这个长度则返回这个字符串<br>
* 字符填充于字符串前
*
* @param str 被填充的字符串
* @param filledChar 填充的字符
* @param len 填充长度
* @return 填充后的字符串
* @since 3.1.2
*/
public static String fillBefore(String str, char filledChar, int len) {
return fill(str, filledChar, len, true);
}
/**
* 将已有字符串填充为规定长度,如果已有字符串超过这个长度则返回这个字符串
*
* @param str 被填充的字符串
* @param filledChar 填充的字符
* @param len 填充长度
* @param isPre 是否填充在前
* @return 填充后的字符串
* @since 3.1.2
*/
public static String fill(String str, char filledChar, int len, boolean isPre) {
final int strLen = str.length();
if (strLen > len) {
return str;
}
String filledStr = StrUtil.repeat(filledChar, len - strLen);
return isPre ? filledStr.concat(str) : str.concat(filledStr);
}
/**
* 重复某个字符
*
* @param c 被重复的字符
* @param count 重复的数目,如果小于等于0则返回""
* @return 重复字符字符串
*/
public static String repeat(char c, int count) {
if (count <= 0) {
return EMPTY;
}
char[] result = new char[count];
for (int i = 0; i < count; i++) {
result[i] = c;
}
return new String(result);
}
// /**
// * 格式化文本, {} 表示占位符<br>
// * 此方法只是简单将占位符 {} 按照顺序替换为参数<br>
// * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可<br>
// * 例:<br>
// * 通常使用:format("this is {} for {}", "a", "b") =》 this is a for b<br>
// * 转义{}: format("this is \\{} for {}", "a", "b") =》 this is \{} for a<br>
// * 转义\: format("this is \\\\{} for {}", "a", "b") =》 this is \a for b<br>
// *
// * @param template 文本模板,被替换的部分用 {} 表示
// * @param params 参数值
// * @return 格式化后的文本
// */
// public static String format(CharSequence template, Object... params) {
// if (null == template) {
// return null;
// }
// if (isEmpty(params) || isBlank(template)) {
// return template.toString();
// }
// return StrFormatter.format(template.toString(), params);
// }
/**
* 数组是否为空
*
* @param <T> 数组元素类型
* @param array 数组
* @return 是否为空
*/
@SuppressWarnings("unchecked")
public static <T> boolean isEmpty(final T... array) {
return array == null || array.length == 0;
}
/**
* 字符串是否为空白 空白的定义如下: <br>
* 1、为null <br>
* 2、为不可见字符(如空格)<br>
* 3、""<br>
*
* @param str 被检测的字符串
* @return 是否为空
*/
public static boolean isBlank(CharSequence str) {
int length;
if ((str == null) || ((length = str.length()) == 0)) {
return true;
}
for (int i = 0; i < length; i++) {
// 只要有一个非空字符即为非空字符串
if (false == isBlankChar(str.charAt(i))) {
return false;
}
}
return true;
}
/**
* 是否空白符<br>
* 空白符包括空格、制表符、全角空格和不间断空格<br>
*
* @see Character#isWhitespace(int)
* @see Character#isSpaceChar(int)
* @param c 字符
* @return 是否空白符
* @since 4.0.10
*/
public static boolean isBlankChar(int c) {
return Character.isWhitespace(c) || Character.isSpaceChar(c) || c == '\ufeff' || c == '\u202a';
}
/**
* 是否为“null”、“undefined”,不做空指针检查
*
* @param str 字符串
* @return 是否为“null”、“undefined”
*/
private static boolean isNullOrUndefinedStr(CharSequence str) {
String strString = str.toString().trim();
return "null".equals(strString) || "undefined".equals(strString);
}
// ------------------------------------------------------------------------ Trim
/**
* 除去字符串头尾部的空白,如果字符串是<code>null</code>,依然返回<code>null</code>。
*
* <p>
* 注意,和<code>String.trim</code>不同,此方法使用<code>NumberUtil.isBlankChar</code> 来判定空白, 因而可以除去英文字符集之外的其它空白,如中文空格。
*
* <pre>
* trim(null) = null
* trim("") = ""
* trim(" ") = ""
* trim("abc") = "abc"
* trim(" abc ") = "abc"
* </pre>
*
* @param str 要处理的字符串
*
* @return 除去头尾空白的字符串,如果原字串为<code>null</code>,则返回<code>null</code>
*/
public static String trim(CharSequence str) {
return (null == str) ? null : trim(str, 0);
}
/**
* 除去字符串头尾部的空白符,如果字符串是<code>null</code>,依然返回<code>null</code>。
*
* @param str 要处理的字符串
* @param mode <code>-1</code>表示trimStart,<code>0</code>表示trim全部, <code>1</code>表示trimEnd
*
* @return 除去指定字符后的的字符串,如果原字串为<code>null</code>,则返回<code>null</code>
*/
public static String trim(CharSequence str, int mode) {
if (str == null) {
return null;
}
int length = str.length();
int start = 0;
int end = length;
// 扫描字符串头部
if (mode <= 0) {
while ((start < end) && (isBlankChar(str.charAt(start)))) {
start++;
}
}
// 扫描字符串尾部
if (mode >= 0) {
while ((start < end) && (isBlankChar(str.charAt(end - 1)))) {
end--;
}
}
if ((start > 0) || (end < length)) {
return str.toString().substring(start, end);
}
return str.toString();
}
}
8.此方法是吧xlsx作为xml进行读取,我们可以修改xlsx文件更好的观察
1.修改xlsx文件的后缀为zip,并且解压此文件
2.查询xl文件打开sheet1,图中的t是作为字符的类型,s代表字符型 ,属性s中的value值,我们去style.xml中找对应的第几个从零开始算。
当t等于s,我们拿到v中的值去sharedStrings.xml中找第几个从0开始取出string的值