java中使用sax的方式读取大数据量的excel数据

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 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 {
    /**
     * 开始读取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);
  }

}

  1. 编写实现方法实现事件驱动
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 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 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));
    }

    /**
     * 填充空白单元格,如果前一个单元格大于后一个,不需要填充
* * @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);
    }

    /**
     * 如果字符串是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,并且解压此文件
java中使用sax的方式读取大数据量的excel数据_第1张图片
图片.png
2.查询xl文件打开sheet1,图中的t是作为字符的类型,s代表字符型 ,属性s中的value值,我们去style.xml中找对应的第几个从零开始算。
java中使用sax的方式读取大数据量的excel数据_第2张图片
图片.png
图片.png

当t等于s,我们拿到v中的值去sharedStrings.xml中找第几个从0开始取出string的值


java中使用sax的方式读取大数据量的excel数据_第3张图片
图片.png
小新java讲堂

你可能感兴趣的:(java中使用sax的方式读取大数据量的excel数据)