秒懂POI解析excel,SAXParser解析大xlsx,XSSFReader处理包括被忽略的空单元格处理

poi常用解析excel文件
excel分97-03格式xls和07格式xlsx,官网对于这两种的说明POI-HSSF and POI-XSSF
最新的是SXSSF (是对XSSF的改进,适合生成超大excel文件,有效降低堆内存,防止崩溃)

解析简单excel文件可以尝试这个
超简单读取Excel使用Apache POI java api

【原理】
poi先将excel转为xml,而后是使用SAXParser解析器,解析xml文件得到excel的数据

xml解析一般是先转dom树,然后操作,【方便随意遍历】,但是这需要将全部xml加载处理,适合小的xml,或者配置类xml

xml文件到数百M或上G的量,全部加载效率低,无法生成完整dom树操作,所以SAX解析器是循环读取一定长度处理,读到一个标签就会回调一个用户方法处理,这样减小内存。【适合大量数据导入,不能回头遍历以前的xml,需要自己实现处理xml内读取的数据关系】

excel转换后的完整xml例子,test.xml


    
    
        
            
        
    
    
    
        
    
    
        
            
                1
            
            
                2
            
            
                4
            
            
                7
            
            
                0
            
            
                4
            
            
                32423
            
            
                78979
            
            
                3
            
        
        
            
                22
            
            
                33
            
            
                55
            
            
                1
            
            
                2
            
        
        
            
                201287
            
        
        
            
                123131
            
        
    
    
    
    

【XSSFReader空单元格,空行问题,从上面的xml可以看出】
poi转换excel为xml会忽略空单元格(不是单元格内容为空格,是单元格没有内容,可能是excel格式压缩存储原因)和空行,导致转换后的【数据错位问题】,需要自己实现判断空单元格和空行处理 (根据excel的行列号,比如B1, D1则表明C1是空单元格,行row的行列号由L3(L是列号,3是行号),到AB7,表明4,5,6是空行)
【注意】有的单元格只修改单元格格式,而没有内容,会出现只有c标签,后面没有v标签

【SAXParser解析器DefaultHandler】
从上面的介绍可以大致了解poi处理excel的过程,我们要做的就是覆盖实现解析的方法,来达到自己的需求
自己的Handler继承DefaultHandler,覆盖一些方法
xml标签是成对出现的,有开始,有结束
startDocument是?xml标签的回调处理方法
startElement方法是读到一个xml开始标签时的回调处理方法,比如上面的 endElement是标签结束的回调处理方法,比如 characters方法是处理xml中的v标签中间的内容的回调处理方法

【注意xml中的c与v标签】
c就是cell单元格,c的属性r是行列号,t是类型,当t是s,表示是SST(SharedStringsTable) 的索引,其他类型很多,不一一列举,打开调试看看完整xml内容,注意在自己的Handler中处理,比如单元格是日期格式等等
v是单元内容【或SST索引】,注意SST索引的取值方式

【SharedStringsTable–SST索引】
excel中多个单元格共享一个相同字符串,压缩存储, 打开sst.writeTo(System.out)注释,看看生成的xml,v表示的sst索引就是si节点的序号

以下是一个基本的处理类,可以很好理解poi解析excel,可以根据需要完善一下,【包含空单元格处理,空行处理】

//单文件示例代码,转换结果在List> container

import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.util.IOUtils;
import org.apache.poi.xssf.eventusermodel.XSSFReader;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

public class Excel {

    public static void main(String[] args) throws Exception {
        File test = new File(".");
        String file = test.getAbsolutePath() + "/src/main/resources/test.xlsx";

        OPCPackage pkg = OPCPackage.open(file);
        XSSFReader r = new XSSFReader(pkg);
        InputStream is = r.getSheet("rId1");
        //debug 查看转换的xml原始文件,方便理解后面解析时的处理,
        byte[] isBytes = IOUtils.toByteArray(is);
        Excel.streamOut(new ByteArrayInputStream(isBytes));

        //下面是SST 的索引会用到的
        SharedStringsTable sst = r.getSharedStringsTable();
        System.out.println("excel的共享字符表sst------------------start");
        sst.writeTo(System.out);
        System.out.println();
        System.out.println("excel的共享字符表sst------------------end");

        XMLReader parser = XMLReaderFactory.createXMLReader("org.apache.xerces.parsers.SAXParser");
        List> container = new ArrayList<>();
        parser.setContentHandler(new Myhandler(sst, container));

        InputSource inputSource = new InputSource(new ByteArrayInputStream(isBytes));
        parser.parse(inputSource);

        is.close();

        Excel.printContainer(container);
    }

    public static void printContainer(List> container) {
        System.out.println("excel内容------------- -start");
        for (List stringList : container) {
            for (String str : stringList) {
                System.out.printf("%20s", str + " | ");
            }
            System.out.println();
        }
        System.out.println("excel内容---------------end");
    }

    //读取流,查看文件内容
    public static void streamOut(InputStream in) throws Exception {
        System.out.println("excel转为xml------------start");
        byte[] buf = new byte[1024];
        int len;
        while ((len = in.read(buf)) != -1) {
            System.out.write(buf, 0, len);
        }
        System.out.println();
        System.out.println("excel转为xml------------end");
    }


}

class Myhandler extends DefaultHandler {


    //取SST 的索引对应的值
    private SharedStringsTable sst;

    public void setSst(SharedStringsTable sst) {
        this.sst = sst;
    }

    //解析结果保存
    private List> container;

    public Myhandler(SharedStringsTable sst, List> container) {
        this.sst = sst;
        this.container = container;
    }

    /**
     * 存储cell标签下v标签包裹的字符文本内容
     * 在v标签开始后,解析器自动调用characters()保存到 lastContents
     * 【但】当cell标签的属性 s是 t时, 表示取到的lastContents是 SharedStringsTable 的index值
     * 需要在v标签结束时根据 index(lastContents)获取一次真正的值
     */
    private String lastContents;

    //有效数据矩形区域,A1:Y2
    private String dimension;

    //根据dimension得出每行的数据长度
    private int longest;

    //上个有内容的单元格id,判断空单元格
    private String lastCellid;

    //上一行id, 判断空行
    private String lastRowid;

    // 判断单元格cell的c标签下是否有v,否则可能数据错位
    private boolean hasV = false;


    //行数据保存
    private List currentRow;

    //单元格内容是SST 的索引
    private boolean isSSTIndex = false;

    @Override
    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
//        System.out.println("startElement:"+qName);

        lastContents = "";
        if (qName.equals("dimension")) {
            dimension = attributes.getValue("ref");
            longest = covertRowIdtoInt(dimension.substring(dimension.indexOf(":") + 1));
        }
        //行开始
        if (qName.equals("row")) {
            String rowNum = attributes.getValue("r");
            //判断空行
            if (lastRowid != null) {
                //与上一行相差2, 说明中间有空行
                int gap = Integer.parseInt(rowNum) - Integer.parseInt(lastRowid);
                if (gap > 1) {
                    gap -= 1;
                    while (gap > 0) {
                        container.add(new ArrayList<>());
                        gap--;
                    }
                }
            }

            lastRowid = attributes.getValue("r");
            currentRow = new ArrayList<>();
        }
        if (qName.equals("c")) {
            String rowId = attributes.getValue("r");

            //空单元判断,添加空字符到list
            if (lastCellid != null) {
                int gap = covertRowIdtoInt(rowId) - covertRowIdtoInt(lastCellid);
                for (int i = 0; i < gap - 1; i++) {
                    currentRow.add("");
                }
            } else {
                //第一个单元格可能不是在第一列
                if (!"A1".equals(rowId)) {
                    for (int i = 0; i < covertRowIdtoInt(rowId) - 1; i++) {
                        currentRow.add("");
                    }
                }
            }
            lastCellid = rowId;


            //判断单元格的值是SST 的索引,不能直接characters方法取值
            if (attributes.getValue("t") != null && attributes.getValue("t").equals("s")) {
                isSSTIndex = true;
            } else {
                isSSTIndex = false;
            }
        }
    }

    @Override
    public void endElement(String uri, String localName, String qName) throws SAXException {
//        System.out.println("endElement:"+qName);

        //行结束,存储一行数据
        if (qName.equals("row")) {

            //判断最后一个单元格是否在最后,补齐列数
            //【注意】有的单元格只修改单元格格式,而没有内容,会出现c标签下没有v标签,导致currentRow少
            if (covertRowIdtoInt(lastCellid) < longest) {
                int min = Math.min(currentRow.size(), covertRowIdtoInt(lastCellid));
                for (int i = 0; i < longest - min; i++) {
                    currentRow.add("");
                }
            }

            container.add(currentRow);
            lastCellid = null;
        }

        //单元格结束,没有v时需要补位
        if (qName.equals("c")){
            if (!hasV) currentRow.add("");
            hasV = false;
        }

        //单元格内容标签结束,characters方法会被调用处理内容
        if (qName.equals("v")) {
            hasV = true;
            //单元格的值是SST 的索引
            if (isSSTIndex) {
                String sstIndex = lastContents.toString();
                try {
                    int idx = Integer.parseInt(sstIndex);
                    XSSFRichTextString rtss = new XSSFRichTextString(
                            sst.getEntryAt(idx));
                    lastContents = rtss.toString();
                    currentRow.add(lastContents);
                } catch (NumberFormatException ex) {
                    System.out.println(lastContents);
                }
            } else {
                currentRow.add(lastContents);
            }

        }

    }


    /**
     * 获取element的文本数据
     *
     * @see org.xml.sax.ContentHandler#characters
     */
    public void characters(char[] ch, int start, int length)
            throws SAXException {
        lastContents += new String(ch, start, length);
    }

    /**
     * 列号转数字   AB7-->28 第28列
     *
     * @param cellId 单元格定位id,行列号,AB7
     * @return
     */
    public static int covertRowIdtoInt(String cellId) {
        StringBuilder sb = new StringBuilder();
        String column = "";
        //从cellId中提取列号
        for(char c:cellId.toCharArray()){
            if (Character.isAlphabetic(c)){
                sb.append(c);
            }else{
                column = sb.toString();
            }
        }
        //列号字符转数字
        int result = 0;
        for (char c : column.toCharArray()) {
            result = result * 26 + (c - 'A') + 1;
        }
        return result;
    }

    public static void main(String[] args) {
        System.out.println(Myhandler.covertRowIdtoInt("AB7"));

    }
}

需要用到的类,pom.xml



    4.0.0

    com.ddddd
    poi
    1.0-SNAPSHOT

    
        UTF-8
        1.8
        1.8
    

    
        
            maven-ali
            http://maven.aliyun.com/nexus/content/groups/public//
            
                true
            
            
                true
                always
                fail
            
        
    

    
        
            org.apache.poi
            poi-ooxml
            3.17
        
        
            xerces
            xercesImpl
            2.11.0
        
    


你可能感兴趣的:(点点滴滴,poi,xml,javaxls,poixlsx)