該文章為本系列的第一篇
第二篇為 : Java POI操作Excel(Event Model)
第三篇為 : Java POI操作Excel(Event User Model)
第四篇為 : 使用POI封裝一個(gè)輕量級(jí)Excel解析框架
前言
在B端系統(tǒng)中,通常都會(huì)提供Excel導(dǎo)入導(dǎo)出數(shù)據(jù)的功能.所以使用Java對(duì)Excel進(jìn)行操作是每個(gè)Java Web工程師必備的技能.而在Java領(lǐng)域,Apache POI是優(yōu)秀的Excel操作庫.這篇文章開始,我們就使用POI一步一步的進(jìn)行Excel的操作,到最后自制一個(gè)簡(jiǎn)單的基于POI的Excel解析框架.
POI簡(jiǎn)介
POI-HSSF and POI-XSSF - Java API To Access Microsoft Excel Format Files
HSSF is the POI Project's pure Java implementation of the Excel '97(-2007) file format. XSSF is the POI Project's pure Java implementation of the Excel 2007 OOXML (.xlsx) file format.
POI針對(duì)目前存在的兩種版本的Excel有不同的實(shí)現(xiàn)方式.而對(duì)于相同版本的Excel又分為Dom解析和Sax解析兩種.而這兩種在內(nèi)存使用,性能各方面均有不同.官方文檔中給出了具體的比較,如下圖前四列.第五列則是對(duì)Excel2007版本提供的節(jié)約內(nèi)存的導(dǎo)出方式進(jìn)行了介紹.
POI UserModel
根據(jù)上面的表格我們可以看到,所謂的UserModel實(shí)際上就類似于Dom方式的解析.學(xué)過Xml解析的朋友都會(huì)知道所謂的Dom解析就是將文件全部讀入內(nèi)存,對(duì)文件內(nèi)部的結(jié)構(gòu)進(jìn)行建模成一顆Dom樹的過程.接下來我們看一下POI對(duì)Excel的建模的Dom樹是什么結(jié)構(gòu)的.
POI 官網(wǎng)Demo
官網(wǎng)中對(duì)這個(gè)頁面的標(biāo)題是 Busy Developers' Guide to HSSF and XSSF Features
所以我們通過這個(gè)網(wǎng)頁Demo的學(xué)習(xí),即可完成對(duì)POI的入門.
快速入門
開發(fā)環(huán)境
Java版本 :1.8.0_40
Maven版本:3.3.9
POI版本 :3.15
創(chuàng)建一個(gè)工作簿(Workbook)
POI中使用Workbook對(duì)Excel對(duì)象進(jìn)行建模.
public class Demo01 {
public static void main(String[] args) throws Exception {
Workbook excel1997 = new HSSFWorkbook(); // excel 1997
FileOutputStream fileOut = new FileOutputStream("workbook.xls");
excel1997.write(fileOut);
fileOut.close();
Workbook excel2007 = new XSSFWorkbook(); // excel 2007
fileOut = new FileOutputStream("workbook.xlsx");
excel2007.write(fileOut);
fileOut.close();
}
}
打開創(chuàng)建的Excel
Workbook對(duì)象是UserModel解析方式中整個(gè)Dom樹的根對(duì)象.而Workbook類本身是一個(gè)接口,規(guī)范了Workbook對(duì)象的基本功能.所以除非我們是要使用特定版本Excel的特性,一般都是面向Workbook接口編程.
創(chuàng)建一個(gè)工作表(sheet)
public static void main(String[] args) throws Exception {
Workbook wb = new HSSFWorkbook();
Sheet sheet1 = wb.createSheet("new sheet");
Sheet sheet2 = wb.createSheet("second sheet");
// Note that sheet name is Excel must not exceed 31 characters
// and must not contain any of the any of the following characters:
// 0x0000
// 0x0003
// colon (:)
// backslash (\)
// asterisk (*)
// question mark (?)
// forward slash (/)
// opening square bracket ([)
// closing square bracket (])
// You can use org.apache.poi.ss.util.WorkbookUtil#createSafeSheetName(String nameProposal)}
// for a safe way to create valid names, this utility replaces invalid characters with a space (' ')
String safeName = WorkbookUtil.createSafeSheetName("[O'Brien's sales*?]"); // returns " O'Brien's sales "
Sheet sheet3 = wb.createSheet(safeName);
FileOutputStream fileOut = new FileOutputStream("workbook02.xls");
wb.write(fileOut);
fileOut.close();
}
打開創(chuàng)建的Excel
Sheet對(duì)象是Workbook對(duì)象下的一級(jí)對(duì)象,代表工作表.而通過上面的程序Demo我們已經(jīng)可以知道在創(chuàng)建Sheet的時(shí)候,要注意的工作表的命名約束.以及如何使用安全的方式創(chuàng)建工作表.
創(chuàng)建單元格
public static void main(String[] args) throws Exception {
Workbook wb = new HSSFWorkbook();
//Workbook wb = new XSSFWorkbook();
CreationHelper createHelper = wb.getCreationHelper();
Sheet sheet = wb.createSheet("new sheet");
// Create a row and put some cells in it. Rows are 0 based.
Row row = sheet.createRow((short)0);
// Create a cell and put a value in it.
Cell cell = row.createCell(0);
cell.setCellValue(1);
// Or do it on one line.
row.createCell(1).setCellValue(1.2);
row.createCell(2).setCellValue(
createHelper.createRichTextString("This is a string"));
row.createCell(3).setCellValue(true);
// Write the output to a file
FileOutputStream fileOut = new FileOutputStream("workbook03.xls");
wb.write(fileOut);
fileOut.close();
}
打開創(chuàng)建的Excel
通過代碼我們可以知道Sheet下一級(jí)的對(duì)象不是Cell而是Row,而Row的下一級(jí)對(duì)象是Cell.在部分代碼很好理解.我們重點(diǎn)介紹一下這個(gè)RichTextString.Excel中的字符串,都會(huì)存在一個(gè)Table(數(shù)組)中,而在解析Excel的時(shí)候我們會(huì)發(fā)現(xiàn)解析到的文本類型的單元格的value是數(shù)字,而這個(gè)數(shù)字實(shí)際上就是該文本在RichText Table中的下標(biāo).
小結(jié)
至此,我們了解了POI對(duì)Excel操作的UserModel時(shí)Dom樹的整體結(jié)構(gòu).如下圖
而我們?cè)诮馕鯡xcel的時(shí)候,即可按照這種方式,從上到下,對(duì)我們需要的數(shù)據(jù)進(jìn)行解析.
解析
要解析的Excel
編碼
public static void main(String[] args) throws Exception {
InputStream inp = null;
try {
inp = new FileInputStream("workbook04.xls");
Workbook wb = WorkbookFactory.create(inp);
Sheet sheet = wb.getSheetAt(0);
Iterator<Row> rowIterator = sheet.rowIterator();
while (rowIterator.hasNext()) {
Row r = rowIterator.next();
if (r == null) {
System.out.println("Empty Row");
continue;
}
for (int i = r.getFirstCellNum(); i < r.getLastCellNum(); i++) {
Cell cell = r.getCell(i);
String cellValue = "";
switch (cell.getCellType()) {
case Cell.CELL_TYPE_STRING:
cellValue = cell.getRichStringCellValue().getString();
break;
case Cell.CELL_TYPE_NUMERIC:
if (DateUtil.isCellDateFormatted(cell)) {
cellValue = cell.getDateCellValue().toString();
} else {
cellValue = String.valueOf(cell.getNumericCellValue());
}
break;
case Cell.CELL_TYPE_BOOLEAN:
cellValue = String.valueOf(cell.getBooleanCellValue());
break;
case Cell.CELL_TYPE_FORMULA:
cellValue = String.valueOf(cell.getCellFormula());
break;
case Cell.CELL_TYPE_BLANK:
break;
default:
}
System.out.println("CellNum:" + i + " => CellValue:" + cellValue);
}
}
} finally {
if (inp != null) {
inp.close();
}
}
}
輸出結(jié)果
CellNum:0 => CellValue:1.0
CellNum:1 => CellValue:Sun Jul 16 00:00:00 CST 2017
CellNum:2 => CellValue:This is a String
小結(jié)
- 使用WorkbookFactory來創(chuàng)建Workbook.從而不引入具體的Workbook實(shí)現(xiàn).達(dá)到解耦的效果
- 遍歷Row和Cell的時(shí)候既可以使用Iterator,也可以使用for循環(huán)的方式.其中實(shí)際有數(shù)據(jù)的是獲取到的最后一行(或一列)的前一行(一列)
- 針對(duì)不同類型的CellType,取值方式并不相同.要使用恰當(dāng)?shù)姆绞?
總結(jié)
這篇文章作為Excel解析的第一篇,對(duì)POI進(jìn)行了簡(jiǎn)單的介紹.然后通過幾個(gè)簡(jiǎn)單小例子對(duì)POI的UserModel進(jìn)行了分析與講解.總體來說,UserModel方式解析Excel比較簡(jiǎn)單.但是作為代價(jià),這種方式對(duì)內(nèi)存的占用是不小的.這也是我們?cè)趯?duì)技術(shù)選型時(shí)要考慮的因素.
本文只作為Excel解析的入門,并不能涉及UserModel的方方面面,更多內(nèi)容還需要去官網(wǎng)查看Demo進(jìn)行學(xué)習(xí).而使用文檔進(jìn)行學(xué)習(xí)也是程序員的必備技能之一.需要我們不斷的訓(xùn)練自己這方面的能力.