1.当使用sax事件驱动读取大数据量的excel的使用maven导入我们需要的jar包:
3.17
2.11.0
org.apache.poi
poi-ooxml
${poi.version}
xerces
xercesImpl
${xerces.version}
2.定义行处理器:
public interface RowHandler {
/**
* 处理一行数据
* @param sheetIndex 当前Sheet序号
* @param rowIndex 当前行号
* @param rowList 行数据列表
*/
void handle(int sheetIndex, int rowIndex, List
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 {
/**
* 开始读取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 implements SaxReader {
@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 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
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);
}
/**
* 如果字符串是null
,则返回指定默认字符串,否则返回字符串本身。
*
*
* nullToDefault(null, "default") = "default"
* nullToDefault("", "default") = ""
* nullToDefault(" ", "default") = " "
* nullToDefault("bat", "default") = "bat"
*
*
* @param str 要转换的字符串
* @param defaultStr 默认字符串
*
* @return 字符串本身或指定的默认字符串
*/
public static String nullToDefault(CharSequence str, String defaultStr) {
return (str == null) ? defaultStr : str.toString();
}
/**
* 将已有字符串填充为规定长度,如果已有字符串超过这个长度则返回这个字符串
* 字符填充于字符串前
*
* @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);
}
// /**
// * 格式化文本, {} 表示占位符
// * 此方法只是简单将占位符 {} 按照顺序替换为参数
// * 如果想输出 {} 使用 \\转义 { 即可,如果想输出 {} 之前的 \ 使用双转义符 \\\\ 即可
// * 例:
// * 通常使用:format("this is {} for {}", "a", "b") =》 this is a for b
// * 转义{}: format("this is \\{} for {}", "a", "b") =》 this is \{} for a
// * 转义\: format("this is \\\\{} for {}", "a", "b") =》 this is \a for b
// *
// * @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 数组元素类型
* @param array 数组
* @return 是否为空
*/
@SuppressWarnings("unchecked")
public static boolean isEmpty(final T... array) {
return array == null || array.length == 0;
}
/**
* 字符串是否为空白 空白的定义如下:
* 1、为null
* 2、为不可见字符(如空格)
* 3、""
*
* @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;
}
/**
* 是否空白符
* 空白符包括空格、制表符、全角空格和不间断空格
*
* @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
/**
* 除去字符串头尾部的空白,如果字符串是null
,依然返回null
。
*
*
* 注意,和String.trim
不同,此方法使用NumberUtil.isBlankChar
来判定空白, 因而可以除去英文字符集之外的其它空白,如中文空格。
*
*
* trim(null) = null
* trim("") = ""
* trim(" ") = ""
* trim("abc") = "abc"
* trim(" abc ") = "abc"
*
*
* @param str 要处理的字符串
*
* @return 除去头尾空白的字符串,如果原字串为null
,则返回null
*/
public static String trim(CharSequence str) {
return (null == str) ? null : trim(str, 0);
}
/**
* 除去字符串头尾部的空白符,如果字符串是null
,依然返回null
。
*
* @param str 要处理的字符串
* @param mode -1
表示trimStart,0
表示trim全部, 1
表示trimEnd
*
* @return 除去指定字符后的的字符串,如果原字串为null
,则返回null
*/
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的值