EasyExcel对Excel文件的解析过程

POI与easyExcel的区别:

POI是通过WorkBook来解析表格文件的,虽然内部有使用到Sax模式,能后大大的提高效率,但是要解析大文件(10w,100w行)的话很容易出现OOM(内存溢出)。
相比之下,
1、easyExcel解析实在磁盘上进行的,几乎可以将几mb级别的内存压缩到kb级别,几乎不用担心OOM;
2、用Java模型进行关系映射,项目中最常用的就Java模型映射,通过 @ExcelProperty注解就可以完成行与列的映射;
3、easyExcel中有一个类AnalysisEventListener,里面有一个方法invoke实现了一行一行返回,另外还可以重写该类的doAfterAllAnalysed方法,用来做事后处理之类的操作,相当的灵活。

准备阶段

第一步:引入easyExcel依赖

   <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>easyexcel</artifactId>
      <version>1.1.2-beta5</version>
    </dependency>

 第二步:自定义监听器ExcelListener继承于AnalysisEventListener,重写invoke()方法(可以读取到excel每一行的数据)和doAfterAllAnalysed()方法(用于后置处理),其中datas用于存取读取到的数据,importHeads为导入表头,modelHeads

package com.cloud.data.utils;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.metadata.ExcelHeadProperty;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * EasyExcell导入监听类
 */
public class ExcelListener extends AnalysisEventListener {
    // 自定义用于暂时存储数据
    private List<Object> datas = new ArrayList<>();
    // 导入表头
    private String importHeads = "";
    // 模版表头
    private String modelHeads = "";

    /**
     * 通过 AnalysisContext对象获取当前sheet,当前行等数据
     */
    @Override
    public void invoke(Object o, AnalysisContext analysisContext) {
        Integer currentRowNum = analysisContext.getCurrentRowNum();
        // 获取导入表头,默认第一行为表头
        if(currentRowNum == 0){
            try {
                Map<String,Object> m = objToMap(o);
                for (Object v : m.values()) {
                    if(!StringUtils.isEmpty(v)){
                        importHeads += String.valueOf(v).trim() + ",";
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }else{
            datas.add(o);
        }
    }

    /**
     * 监听器获取模板表头
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext analysisContext) {
        // 获取模版表头
        ExcelHeadProperty ehp = analysisContext.getExcelHeadProperty();
        for(List<String> s : ehp.getHead()){
            modelHeads += s.get(0) + ",";
        }
    }

    // Object转换为Map
    private Map<String,Object> objToMap(Object obj) throws Exception{
        Map<String,Object> map = new LinkedHashMap<String, Object>();
        Field[] fields = obj.getClass().getDeclaredFields();
        for(Field field : fields){
            field.setAccessible(true);
            map.put(field.getName(), field.get(obj));
        }
        return map;
    }

    public List<Object> getDatas() {
        return datas;
    }

    public void setDatas(List<Object> datas) {
        this.datas = datas;
    }

    public String getImportHeads() {
        return importHeads;
    }

    public void setImportHeads(String importHeads) {
        this.importHeads = importHeads;
    }

    public String getModelHeads() {
        return modelHeads;
    }

    public void setModelHeads(String modelHeads) {
        this.modelHeads = modelHeads;
    }
}

第三步:添加接收excel导入的实体类OutDbillDto,继承BaseRowModel,其中@ExcelProperty(value = "机组调度名称(必填)", index = 0)对应excel表头的(value为表头名称,index为索引位置)

package com.cloud.data.entity.dto;

import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.metadata.BaseRowModel;
import lombok.Data;

import java.io.Serializable;

/**
 * 日结算账单导入类
 */
@Data
public class OutDbillDto extends BaseRowModel implements Serializable {
    @ExcelProperty(value = "机组调度名称(必填)", index = 0)
    private String unitDispatchName;
    @ExcelProperty(value = "日期(必填)", index = 1)
    private String billTime;
    @ExcelProperty(value = "收费项目", index = 2)
    private String project;
    @ExcelProperty(value = "本期电量(万kWh)", index = 3)
    private String quan;
    @ExcelProperty(value = "单价(厘/kWh)", index = 4)
    private String avgPrice;
    @ExcelProperty(value = "本期电费(元)",index = 5)
    private String price;
    /**标记*/
    private String mark;
    /**错误信息*/
    private String errMsg;
}

 第四步,定义EasyExcelUtil工具类,用于读取excel

package com.cloud.data.utils;

import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelReader;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.metadata.BaseRowModel;
import com.alibaba.excel.metadata.Sheet;
import com.alibaba.excel.support.ExcelTypeEnum;
import com.cloud.frame.util.DateUtil;
import com.google.common.collect.Lists;
import org.apache.commons.beanutils.BeanUtils;

import org.apache.poi.ss.formula.functions.T;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;

public class EasyExcelUtil {
    /**
     * 读取某个 sheet 的 Excel
     *
     * @param excel    文件
     * @param rowModel 实体类映射,继承 BaseRowModel 类
     * @return Excel 数据 list
     */
    public static List<Object> readExcel(MultipartFile excel, BaseRowModel rowModel) throws IOException {
        return readExcel(excel, rowModel, 1, 1);
    }

    /**
     * 读取某个 sheet 的 Excel
     * @param excel       文件
     * @param rowModel    实体类映射,继承 BaseRowModel 类
     * @param sheetNo     sheet 的序号 从1开始
     * @return Excel 数据 list
     */
    public static Map<String,Object> readExcel(MultipartFile excel, BaseRowModel rowModel, int sheetNo) throws IOException {
        Map<String,Object> result = new HashMap<>();
        ExcelListener excelListener = new ExcelListener();
        ExcelReader reader = getReader(excel, excelListener);
        if (Objects.isNull(reader)) {
            return null;
        }
        reader.read(new Sheet(sheetNo, 0, rowModel.getClass()));
        //校验表头
        Boolean flag = false;
        if(excelListener.getImportHeads().equals(excelListener.getModelHeads())){
            flag = true;
        }
        result.put("flag", flag);
        result.put("datas", excelListener.getDatas());
        return result;
    }

    /**
     * 读取某个 sheet 的 Excel
     * @param excel       文件
     * @param rowModel    实体类映射,继承 BaseRowModel 类
     * @param sheetNo     sheet 的序号 从1开始
     * @param headLineNum 表头行数,默认为1
     * @return Excel 数据 list
     */
    public static List<Object> readExcel(MultipartFile excel, BaseRowModel rowModel, int sheetNo, int headLineNum) throws IOException {
        ExcelListener excelListener = new ExcelListener();
        ExcelReader reader = getReader(excel, excelListener);
        if (reader == null) {
            return null;
        }
        reader.read(new Sheet(sheetNo, headLineNum, rowModel.getClass()));
        return excelListener.getDatas();
    }

    /**
     * 读取指定sheetName的Excel(多个 sheet)
     * @param excel    文件
     * @param rowModel 实体类映射,继承 BaseRowModel 类
     * @return Excel 数据 list
     * @throws IOException
     */
    public static List<Object> readExcel(MultipartFile excel, BaseRowModel rowModel,String sheetName) throws IOException {
        ExcelListener excelListener = new ExcelListener();
        ExcelReader reader = getReader(excel, excelListener);
        if (reader == null) {
            return null;
        }
        for (Sheet sheet : reader.getSheets()) {
            if (rowModel != null) {
                sheet.setClazz(rowModel.getClass());
            }
            //读取指定名称的sheet
            if(sheet.getSheetName().contains(sheetName)){
                reader.read(sheet);
                break;
            }
        }
        return excelListener.getDatas();
    }

    /**
     * 返回 ExcelReader
     * @param excel 需要解析的 Excel 文件
     * @param excelListener new ExcelListener()
     * @throws IOException
     */
    private static ExcelReader getReader(MultipartFile excel,ExcelListener excelListener) throws IOException {
        String filename = excel.getOriginalFilename();
        if(Objects.nonNull(filename) && (filename.toLowerCase().endsWith(".xls") || filename.toLowerCase().endsWith(".xlsx"))){
            InputStream is = new BufferedInputStream(excel.getInputStream());
            return new ExcelReader(is, null,null, excelListener, false);
        }else{
            return null;
        }
    }

    /**
     * 将导入失败的数据写到系统指定路径
     * @param failDatas 数据
     * @param name 文件名
     * @param dir 写入的路径
     * @param header Excel表头
     * @param columns 需要写入Excel的对象属性集合
     * @param index 合并单元格索引
     * @return
     * @throws Exception
     */
    public static String saveExcel2Loacl(List<?> failDatas, String name, String dir, String[] header,String[] columns,Integer index) throws Exception {
        // 1.唯一文件名
        String fileName = name + "_" + DateUtil.getNowStr(DateUtil.TIME_FORMAT)+".xls";
        Path path= Paths.get(ExcelUtil.FILE_UPLOAD_ROOT, dir).toAbsolutePath();
        File file = new File(path.toString());
        if (!file.exists()){
            file.mkdirs();
        }
        // 2.添加Sheet名
        Sheet sheet = new Sheet(1,0);
        sheet.setSheetName(name);
        // 3.动态添加Excel表头
        List<List<String>> head = new ArrayList<>();
        for (String h : header) {
            head.add(Lists.newArrayList(h));
        }
        sheet.setHead(head);
        // 4.写入数据
        List<T> datas = new ArrayList(failDatas);
        List<List<Object>> data = new ArrayList<>();
        for (Object var : datas) {
            List<Object> objects = Lists.newArrayList();
            Arrays.stream(columns).forEach(e ->{
                try {
                    String property = BeanUtils.getProperty(var, e);
                    objects.add(property);
                } catch (Exception err) {
                    err.printStackTrace();
                }
            });
            data.add(objects);
        }

        FileOutputStream fileOutputStream = new FileOutputStream(Paths.get(ExcelUtil.FILE_UPLOAD_ROOT, dir, fileName).toFile());
        ExcelWriter writer = EasyExcelFactory.getWriter(fileOutputStream, ExcelTypeEnum.XLS, true);
        writer.write1(data,sheet);
        // 5.合并单元格
        for (int i = 1; i <= failDatas.size(); i = i + index) {
            writer.merge(i,i + index - 1,0,0);
            writer.merge(i,i + index - 1,1,1);
            writer.merge(i,i + index - 1,2,2);
        }
        writer.finish();
        return fileName;
    }
}

执行原理:

ExcelAnalyserImpl是解析器的真正实现,整合了v07好人v03,解析的时候会根据getSaxAnalyser来选择使用哪种版本的解析器。

通过ExcelAnalyserImpl()构造方法,将inputstream(也就是file文件)和自定义的监听器eventListener(继承于AnalysisEventListener) 存入ExcelAnalyserImpl类的AnalysisContext属性

package com.alibaba.excel.analysis;

import com.alibaba.excel.analysis.v03.XlsSaxAnalyser;
import com.alibaba.excel.analysis.v07.XlsxSaxAnalyser;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.context.AnalysisContextImpl;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelAnalysisException;
import com.alibaba.excel.metadata.Sheet;
import com.alibaba.excel.modelbuild.ModelBuildEventListener;
import com.alibaba.excel.support.ExcelTypeEnum;

import java.io.InputStream;
import java.util.List;

/**
 * @author jipengfei
 */
public class ExcelAnalyserImpl implements ExcelAnalyser {

    private AnalysisContext analysisContext;

    private BaseSaxAnalyser saxAnalyser;

    public ExcelAnalyserImpl(InputStream inputStream, ExcelTypeEnum excelTypeEnum, Object custom,
                             AnalysisEventListener eventListener, boolean trim) {
        analysisContext = new AnalysisContextImpl(inputStream, excelTypeEnum, custom,
            eventListener, trim);
    }

    private BaseSaxAnalyser getSaxAnalyser() {
        if (saxAnalyser != null) {
            return this.saxAnalyser;
        }
        try {
            if (analysisContext.getExcelType() != null) {
                switch (analysisContext.getExcelType()) {
                    case XLS:
                        this.saxAnalyser = new XlsSaxAnalyser(analysisContext);
                        break;
                    case XLSX:
                        this.saxAnalyser = new XlsxSaxAnalyser(analysisContext);
                        break;
                }
            } else {
                try {
                    this.saxAnalyser = new XlsxSaxAnalyser(analysisContext);
                } catch (Exception e) {
                    if (!analysisContext.getInputStream().markSupported()) {
                        throw new ExcelAnalysisException(
                            "Xls must be available markSupported,you can do like this <code> new "
                                + "BufferedInputStream(new FileInputStream(\"/xxxx\"))</code> ");
                    }
                    this.saxAnalyser = new XlsSaxAnalyser(analysisContext);
                }
            }
        } catch (Exception e) {
            throw new ExcelAnalysisException("File type error,io must be available markSupported,you can do like "
                + "this <code> new BufferedInputStream(new FileInputStream(\\\"/xxxx\\\"))</code> \"", e);
        }
        return this.saxAnalyser;
    }

    @Override
    public void analysis(Sheet sheetParam) {
        analysisContext.setCurrentSheet(sheetParam);
        analysis();
    }

    @Override
    public void analysis() {
        BaseSaxAnalyser saxAnalyser = getSaxAnalyser();
        appendListeners(saxAnalyser);
        saxAnalyser.execute();
        analysisContext.getEventListener().doAfterAllAnalysed(analysisContext);
    }

    @Override
    public List<Sheet> getSheets() {
        BaseSaxAnalyser saxAnalyser = getSaxAnalyser();
        saxAnalyser.cleanAllListeners();
        return saxAnalyser.getSheets();
    }

    private void appendListeners(BaseSaxAnalyser saxAnalyser) {
        saxAnalyser.cleanAllListeners();
        if (analysisContext.getCurrentSheet() != null && analysisContext.getCurrentSheet().getClazz() != null) {
            saxAnalyser.appendLister("model_build_listener", new ModelBuildEventListener());
        }
        if (analysisContext.getEventListener() != null) {
            saxAnalyser.appendLister("user_define_listener", analysisContext.getEventListener());
        }
    }

}
package com.alibaba.excel.analysis;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.event.AnalysisEventRegisterCenter;
import com.alibaba.excel.event.OneRowAnalysisFinishEvent;
import com.alibaba.excel.metadata.Sheet;
import com.alibaba.excel.util.TypeUtil;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * @author jipengfei
 */
public abstract class BaseSaxAnalyser implements AnalysisEventRegisterCenter, ExcelAnalyser {

    protected AnalysisContext analysisContext;

    private LinkedHashMap<String, AnalysisEventListener> listeners = new LinkedHashMap<String, AnalysisEventListener>();

    /**
     * execute method
     */
    protected abstract void execute();


    @Override
    public void appendLister(String name, AnalysisEventListener listener) {
        if (!listeners.containsKey(name)) {
            listeners.put(name, listener);
        }
    }

    @Override
    public void analysis(Sheet sheetParam) {
        execute();
    }

    @Override
    public void analysis() {
        execute();
    }

    /**
     */
    @Override
    public void cleanAllListeners() {
        listeners = new LinkedHashMap<String, AnalysisEventListener>();
    }

    @Override
    public void notifyListeners(OneRowAnalysisFinishEvent event) {
        analysisContext.setCurrentRowAnalysisResult(event.getData());
        /** Parsing header content **/
        if (analysisContext.getCurrentRowNum() < analysisContext.getCurrentSheet().getHeadLineMun()) {
            if (analysisContext.getCurrentRowNum() <= analysisContext.getCurrentSheet().getHeadLineMun() - 1) {
                analysisContext.buildExcelHeadProperty(null,
                    (List<String>)analysisContext.getCurrentRowAnalysisResult());
            }
        } else {
            List<String> content = converter((List<String>)event.getData());
            /** Parsing Analyze the body content **/
            analysisContext.setCurrentRowAnalysisResult(content);
            if (listeners.size() == 1) {
                analysisContext.setCurrentRowAnalysisResult(content);
            }
            /**  notify all event listeners **/
            for (Map.Entry<String, AnalysisEventListener> entry : listeners.entrySet()) {
                entry.getValue().invoke(analysisContext.getCurrentRowAnalysisResult(), analysisContext);
            }
        }
    }

    private List<String> converter(List<String> data) {
        List<String> list = new ArrayList<String>();
        if (data != null) {
            for (String str : data) {
                list.add(TypeUtil.formatFloat(str));
            }
        }
        return list;
    }

}

 通过ExcelAnalyserImpl类的getSaxAnalyser()方法获取Excel是.xls还是.xlsx类型后,将ExcelAnalyserImpl的analysisContext属性赋给BaseSaxAnalyser类的analysisContext属性,再用appendListeners()方法封装默认的监听器和自定义的监听器到BaseSaxAnalyser类 的listeners属性中,到此为止,BaseSaxAnalyser的两个属性:analysisContext、listeners已经分别存有file文件、excel枚举类型和监听器等值

 接下来讲一个DefaultHandler类(核心类),该类是SAX事件解析器,底层在磁盘中解析文件并一行一行的读取,并且会依次执行startDocument()、startElement()、characters()、endElement()、endDocument()方法

package org.xml.sax.helpers;

import java.io.IOException;

import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.Attributes;
import org.xml.sax.EntityResolver;
import org.xml.sax.DTDHandler;
import org.xml.sax.ContentHandler;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;



public class DefaultHandler
    implements EntityResolver, DTDHandler, ContentHandler, ErrorHandler
{

    public InputSource resolveEntity (String publicId, String systemId)
        throws IOException, SAXException
    {
        return null;
    }

    public void notationDecl (String name, String publicId, String systemId)
        throws SAXException
    {
        // no op
    }


    public void unparsedEntityDecl (String name, String publicId,
                                    String systemId, String notationName)
        throws SAXException
    {
        // no op
    }

    public void setDocumentLocator (Locator locator)
    {
        // no op
    }

    public void startDocument ()
        throws SAXException
    {
        // no op
    }

    public void endDocument ()
        throws SAXException
    {
        // no op
    }


    public void startPrefixMapping (String prefix, String uri)
        throws SAXException
    {
        // no op
    }

    public void endPrefixMapping (String prefix)
        throws SAXException
    {
        // no op
    }


    public void startElement (String uri, String localName,
                              String qName, Attributes attributes)
        throws SAXException
    {
        // no op
    }


    public void endElement (String uri, String localName, String qName)
        throws SAXException
    {
        // no op
    }


    public void characters (char ch[], int start, int length)
        throws SAXException
    {
        // no op
    }

    public void ignorableWhitespace (char ch[], int start, int length)
        throws SAXException
    {
        // no op
    }

    
    public void processingInstruction (String target, String data)
        throws SAXException
    {
        // no op
    }


    public void skippedEntity (String name)
        throws SAXException
    {
        // no op
    }

    public void warning (SAXParseException e)
        throws SAXException
    {
        // no op
    }

    public void error (SAXParseException e)
        throws SAXException
    {
        // no op
    }

    public void fatalError (SAXParseException e)
        throws SAXException
    {
        throw e;
    }

}

// end of DefaultHandler.java

通过XlsxRowHandler类继承DefaultHandler类,将每一行读取的行数和数据分别存入curCol和curRowContent属性中

  读取完之后,通过后置方法endElement()方法,唤醒最初自己定义的监听器ExcelListener,并将读取到的行数、数据等赋给该监听器

package com.alibaba.excel.analysis.v07;

import com.alibaba.excel.annotation.FieldType;
import com.alibaba.excel.constant.ExcelXmlConstants;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventRegisterCenter;
import com.alibaba.excel.event.OneRowAnalysisFinishEvent;
import com.alibaba.excel.util.PositionUtils;
import org.apache.poi.xssf.model.SharedStringsTable;
import org.apache.poi.xssf.usermodel.XSSFRichTextString;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;

import java.util.Arrays;

import static com.alibaba.excel.constant.ExcelXmlConstants.*;

/**
 *
 * @author jipengfei
 */
public class XlsxRowHandler extends DefaultHandler {

    private String currentCellIndex;

    private FieldType currentCellType;

    private int curRow;

    private int curCol;

    private String[] curRowContent = new String[20];

    private String currentCellValue;

    private SharedStringsTable sst;

    private AnalysisContext analysisContext;

    private AnalysisEventRegisterCenter registerCenter;

    public XlsxRowHandler(AnalysisEventRegisterCenter registerCenter, SharedStringsTable sst,
                          AnalysisContext analysisContext) {
        this.registerCenter = registerCenter;
        this.analysisContext = analysisContext;
        this.sst = sst;

    }

    @Override
    public void startElement(String uri, String localName, String name, Attributes attributes) throws SAXException {

        setTotalRowCount(name, attributes);

        startCell(name, attributes);

        startCellValue(name);

    }

    private void startCellValue(String name) {
        if (name.equals(CELL_VALUE_TAG) || name.equals(CELL_VALUE_TAG_1)) {
            // initialize current cell value
            currentCellValue = "";
        }
    }

    private void startCell(String name, Attributes attributes) {
        if (ExcelXmlConstants.CELL_TAG.equals(name)) {
            currentCellIndex = attributes.getValue(ExcelXmlConstants.POSITION);
            int nextRow = PositionUtils.getRow(currentCellIndex);
            if (nextRow > curRow) {
                curRow = nextRow;
                // endRow(ROW_TAG);
            }
            analysisContext.setCurrentRowNum(curRow);
            curCol = PositionUtils.getCol(currentCellIndex);

            String cellType = attributes.getValue("t");
            currentCellType = FieldType.EMPTY;
            if (cellType != null && cellType.equals("s")) {
                currentCellType = FieldType.STRING;
            }
        }
    }

    private void endCellValue(String name) throws SAXException {
        // ensure size
        if (curCol >= curRowContent.length) {
            curRowContent = Arrays.copyOf(curRowContent, (int)(curCol * 1.5));
        }
        if (CELL_VALUE_TAG.equals(name)) {

            switch (currentCellType) {
                case STRING:
                    int idx = Integer.parseInt(currentCellValue);
                    currentCellValue = new XSSFRichTextString(sst.getEntryAt(idx)).toString();
                    currentCellType = FieldType.EMPTY;
                    break;
            }
            curRowContent[curCol] = currentCellValue;
        } else if (CELL_VALUE_TAG_1.equals(name)) {
            curRowContent[curCol] = currentCellValue;
        }
    }

    @Override
    public void endElement(String uri, String localName, String name) throws SAXException {
        endRow(name);
        endCellValue(name);
    }

    @Override
    public void characters(char[] ch, int start, int length) throws SAXException {
        currentCellValue += new String(ch, start, length);
    }


    private void setTotalRowCount(String name, Attributes attributes) {
        if (DIMENSION.equals(name)) {
            String d = attributes.getValue(DIMENSION_REF);
            String totalStr = d.substring(d.indexOf(":") + 1, d.length());
            String c = totalStr.toUpperCase().replaceAll("[A-Z]", "");
            analysisContext.setTotalCount(Integer.parseInt(c));
        }

    }

    private void endRow(String name) {
        if (name.equals(ROW_TAG)) {
            registerCenter.notifyListeners(new OneRowAnalysisFinishEvent(curRowContent,curCol));
            curRowContent = new String[20];
        }
    }

}

 此时回到ExcelListener监听器,便可以去取到每一行的数据存入datas属性中,并且对比导入的表头和模板表头是否一致等

 到此,可以读到excel的每一行数据,用于业务处理。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值