读取Excel
思路:先读取整个Excel,即工作簿,再依次读取其中的每个工作表Sheet,最后读取工作表内的表格。
一、读取工作簿
利用流读取指定目录中的工作簿,并写入内存。
/** * Constructs a XSSFWorkbook object, by buffering the whole stream into memory * and then opening an {@link OPCPackage} object for it. * * <p>Using an {@link InputStream} requires more memory than using a File, so * if a {@link File} is available then you should instead do something like * <pre><code> * OPCPackage pkg = OPCPackage.open(path); * XSSFWorkbook wb = new XSSFWorkbook(pkg); * // work with the wb object * ...... * pkg.close(); // gracefully closes the underlying zip file * </code></pre> */ public XSSFWorkbook(InputStream is) throws IOException { super(PackageHelper.open(is)); beforeDocumentRead(); // Build a tree of POIXMLDocumentParts, this workbook being the root load(XSSFFactory.getInstance()); // some broken Workbooks miss this... if(!workbook.isSetBookViews()) { CTBookViews bvs = workbook.addNewBookViews(); CTBookView bv = bvs.addNewWorkbookView(); bv.setActiveTab(0); } }
针对源码,先生产InputStream,再读取:
InputStream is = new FileInputStream(filePath); XSSFWorkbook workbook = new XSSFWorkbook(is);
二、读取工作薄中工作表,依次读取,先要知道到底有几个? 此处,需要对XSSFWorkbook类有基本的了解。(建议看一下源码)
int countSheet = workbook.getNumberOfSheets();
for循环读取每个工作表。
思考:需要怎样的数据结构才能优雅的存放其中的数据?
思考一下工作表中存放的数据:主流的是表格和表格下的数据。
进一步思考这个表格的数据用什么样的数据结构?
表格进一步分析,表格有表头和非表头数据组成,由于表头需要对应实体映射,需要单独处理。
因此表头采用Map存储(此处下面需要继续分析),同样一行数据也用Map处理,多行数据放入List
至此:工作表的数据结构清晰了。
表头:Map<String,object>;
非数据多行:List<Map,Object>;
工作表名称:String;
/** * Excel解析后相关的Excel * <一句话功能简述> * <功能详细描述> * * @author * @version [版本号, 2019年6月26日] * @see [相关类/方法] * @since [产品/模块版本] */ public class ExcelSheetStruct { /** * sheet页名称 */ private String sheetName; /** * 列表表头 */ private Map<String,String> headerMap; /** * 列表数据 */ private List<Map<String,Object>> data; // 省略get和set }
三、工作表的数据读取
for (int numSheet = 0; numSheet < countSheet; numSheet++) { // 获取Sheet,创建Sheet数据结构 XSSFSheet xssfSheet = workbook.getSheetAt(numSheet); ExcelSheetStruct excelSheetStruct = new ExcelSheetStruct(); excelSheetStruct.setSheetName(xssfSheet.getSheetName()); }
再获取表格数据,先得获取有效区域。
思考:到底是先从行开始,还是先从列开始,即第一维度是行还是列?
此处先从行开始,因为行在Sheet中是数字。(注:有效行是当前行数字减1)
//处理有数据区域的表格 int firstRowNum = xssfSheet.getFirstRowNum(); int lastRowNum = xssfSheet.getLastRowNum(); // 处理表头和非表头,第一维度“行” for (int row = firstRowNum; row <= lastRowNum; row++) {}
再for循环内,获取列号(第二维度),就能唯一定位到数据,进行获取。
这边先获取表头
/** * 根据所给的首行,生成具体的数据结构 * <一句话功能简述> * <功能详细描述> * @param xssfRow * @return * @see [类、类#方法、类#成员] * @author */ public static Map<String, String> generateHeadLine(XSSFRow xssfRow) { Map<String, String> headerMap = new HashMap<>(); // 获取开始列和结束列 int firstCellNum = xssfRow.getFirstCellNum(); int lastCellNum = xssfRow.getLastCellNum(); for (int cellNum = firstCellNum; cellNum < lastCellNum; cellNum++) { Cell cell = xssfRow.getCell(cellNum, XSSFRow.CREATE_NULL_AS_BLANK); // key——列号,value——数据 headerMap.put(String.valueOf(cellNum), cell.getStringCellValue()); } return headerMap; }
同理,获取非表头。此处把表头和下面列的数据进行关联。即Map<String,Object> = HashMap<"表头","表头当前列下面的一个数据(当前cell的值)">
/** * 对数据行的处理,根据头行数据,将数据与行列表结合起来 * <一句话功能简述> * <功能详细描述> * @param xssfRow * @return * @see [类、类#方法、类#成员] * @author */ public static Map<String, Object> generateDataLine(XSSFRow xssfRow, Map<String, String> headerLine) { Map<String, Object> mapDate = new HashMap<>(); // 此处也是读取列,用上面有不同之处,这边不是顺序读取,是按照表头存储的数据的key(列号) for (String key : headerLine.keySet()) { Cell cell = xssfRow.getCell(Integer.parseInt(key), XSSFRow.CREATE_NULL_AS_BLANK); cell.setCellType(Cell.CELL_TYPE_STRING); // HashMap<"表头的值","当前的值"> mapDate.put(headerLine.get(key), cell.getStringCellValue()); } return mapDate; }
读取完一行非表头数据需要继续读取,最后添加到非标头数据结构中List<Map>;
//循环处理该sheet页中的单行数据 for (int row = firstRowNum; row <= lastRowNum; row++) { XSSFRow xssfRow = xssfSheet.getRow(row); if (row == firstRowNum) { // 处理完表头数据,把它添加到Sheet中 excelSheetStruct.setHeaderMap(generateHeadLine(xssfRow)); continue; } Map<String, Object> dateLine = generateDataLine(xssfRow, excelSheetStruct.getHeaderMap()); // 整合非表头数据 dataList.add(dateLine); } // 处理完非表头数据,把它添加到Sheet中 allDataList.add(excelSheetStruct);
至此数据单个Sheet处理完毕,回顾代码:
/** * 读取Excel生成相应的数据集 * <一句话功能简述> * <功能详细描述> * @param filePath * @return * @throws IOException * @see [类、类#方法、类#成员] * @author */ public static List<ExcelSheetStruct> readXlsx(String filePath) throws IOException { List<ExcelSheetStruct> allDataList = new ArrayList<>(); XSSFWorkbook workbook = null; InputStream is = new FileInputStream(filePath); workbook = new XSSFWorkbook(is); int countSheet = workbook.getNumberOfSheets(); for (int numSheet = 0; numSheet < countSheet; numSheet++) { ExcelSheetStruct excelSheetStruct = new ExcelSheetStruct(); List<Map<String, Object>> dataList = new ArrayList<>(); excelSheetStruct.setData(dataList); XSSFSheet xssfSheet = workbook.getSheetAt(numSheet); excelSheetStruct.setSheetName(xssfSheet.getSheetName()); //处理有数据区域的表格 int firstRowNum = xssfSheet.getFirstRowNum(); int lastRowNum = xssfSheet.getLastRowNum(); if (lastRowNum == 0) { continue; } //循环处理该sheet页中的单行数据 for (int row = firstRowNum; row <= lastRowNum; row++) { XSSFRow xssfRow = xssfSheet.getRow(row); if (row == firstRowNum) { excelSheetStruct.setHeaderMap(generateHeadLine(xssfRow)); continue; } Map<String, Object> dateLine = generateDataLine(xssfRow, excelSheetStruct.getHeaderMap()); dataList.add(dateLine); } allDataList.add(excelSheetStruct); } workbook.close(); is.close(); return allDataList; }