POI
Apache POI
是Apache軟件基金會的開放源碼函式庫,POI提供API給Java程序對Microsoft Office格式檔案讀和寫的功能。
結構:
- HSSF - 提供讀寫Microsoft Excel格式檔案的功能。
- XSSF - 提供讀寫Microsoft Excel OOXML格式檔案的功能。
- HWPF - 提供讀寫Microsoft Word格式檔案的功能。
- HSLF - 提供讀寫Microsoft PowerPoint格式檔案的功能。
- HDGF - 提供讀寫Microsoft Visio格式檔案的功能。
Excel 03版本和07版本
03版本的Excel,即後綴名爲.xls的。07版本的Excel,即後綴名爲.xlsx的。
Excel03:只支持xls類型的文檔,最多65536行、256列。
Excel07:除了支來持xls類型文檔,還支持xlsx類型的文檔,最多1048576行、6384列。
Excel對象
工作簿:一個Excel文件
工作表:一個Excel文件中的表
行、列:每一行、每一列
POI操作Excel
引入相關依賴
<!-- xls(Excel 03版本) -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>3.9</version>
</dependency>
<!-- xlsx(Excel 07版本) -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>3.9</version>
</dependency>
<!-- 日期格式化工具 -->
<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10.1</version>
</dependency>
<!-- 單元測試 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
基本寫操作
1、03版本
public class ExcelWriteTest {
// 文件輸入位置
public static String Path = "D:\\";
@Test
public void testWrite03() throws IOException {
// 創建一個工作簿
Workbook workbook = new HSSFWorkbook();
// 根據工作簿創建一個工作表
Sheet sheet = workbook.createSheet("柳成蔭");
// 根據工作表創建一行
Row row0 = sheet.createRow(0);
// 根據行創建一個單元格
Cell cell00 = row0.createCell(0);
cell00.setCellValue("今日聽歌數"); // 即Excel裏的座標(0,0),第一行第一列的格子
Row row1 = sheet.createRow(1);
Cell cell10 = row1.createCell(0);
cell10.setCellValue(10); // 即Excel裏的座標(1,0),第二行第一列的格子
Cell cell01 = row0.createCell(1);
cell01.setCellValue("統計時間"); // (0,1)
Cell cell11 = row1.createCell(1);
cell11.setCellValue(new DateTime().toString("yyyy-MM-dd HH:mm:ss")); // (1,1)
// 生成一張表
OutputStream outputStream = new FileOutputStream(Path + "03版本測試.xls");
workbook.write(outputStream);
// 關閉流
outputStream.close();
System.out.println("03版本Excel生成完畢!");
}
}
2、07版本
相對上面03版本,代碼沒有什麼變化,就是創建工作簿的對象(XSSFWorkbook)變了和文件後綴(.xlsx)變了
@Test
public void testWrite07() throws IOException {
// 創建一個工作簿 - 07
Workbook workbook = new XSSFWorkbook();
// 根據工作簿創建一個工作表
Sheet sheet = workbook.createSheet("柳成蔭");
// 根據工作表創建一行
Row row0 = sheet.createRow(0);
// 根據行創建一個單元格
Cell cell00 = row0.createCell(0);
cell00.setCellValue("今日聽歌數"); // 即Excel裏的座標(0,0),第一行第一列的格子
Row row1 = sheet.createRow(1);
Cell cell10 = row1.createCell(0);
cell10.setCellValue(10); // 即Excel裏的座標(1,0),第二行第一列的格子
Cell cell01 = row0.createCell(1);
cell01.setCellValue("統計時間"); // (0,1)
Cell cell11 = row1.createCell(1);
cell11.setCellValue(new DateTime().toString("yyyy-MM-dd HH:mm:ss")); // (1,1)
// 生成一張表
OutputStream outputStream = new FileOutputStream(Path + "07版本測試.xlsx");
workbook.write(outputStream);
// 關閉流
outputStream.close();
System.out.println("07版本Excel生成完畢!");
}
大數據量的寫入
1、03版本
@Test
public void testWrite03BigData() throws IOException{
// 開始寫入時間
long begin = System.currentTimeMillis();
// 創建工作簿
Workbook workbook = new HSSFWorkbook();
// 創建表
Sheet sheet = workbook.createSheet("九月清晨");
for (int rowNum = 0; rowNum < 65536; rowNum++){
Row row = sheet.createRow(rowNum);
for (int cellNum = 0; cellNum < 256; cellNum++){
Cell cell = row.createCell(cellNum);
cell.setCellValue(cellNum);
}
}
OutputStream outputStream = new FileOutputStream(Path + "03bigData.xls");
workbook.write(outputStream);
outputStream.close();
// 結束寫入時間
long end = System.currentTimeMillis();
System.out.println("寫入完畢,花費時間:");
System.out.println((double) (end - begin)/1000);
}
2、07版本
在03版本基礎上,只需修改對象(XSSFWorkbook)和文件後綴名(.xlsx)即可。
相對03版本,寫入相同的數據花費時間更長,但是07能寫入的數據更多。
3、07版本升級版 - SXSSFWorkbook
它可以寫入非常大的數據量,如100萬條甚至更多,寫數據速度極快,佔用內存更少。
但是它會在寫入過程中產生臨時文件,默認是100條數據被保存在內存中,如果超過這個數量,則最前面的數據被寫入臨時文件,如果想要自定義內存中數據的數量,可以使用SXSSFWorkbook(數量)
。
@Test
public void testWrite07BigDataPlus() throws IOException{
// 開始寫入時間
long begin = System.currentTimeMillis();
// 創建工作簿
Workbook workbook = new SXSSFWorkbook();
// 創建表
Sheet sheet = workbook.createSheet("九月清晨");
for (int rowNum = 0; rowNum < 65536; rowNum++){
Row row = sheet.createRow(rowNum);
for (int cellNum = 0; cellNum < 256; cellNum++){
Cell cell = row.createCell(cellNum);
cell.setCellValue(cellNum);
}
}
OutputStream outputStream = new FileOutputStream(Path + "07bigDataPlus.xlsx");
workbook.write(outputStream);
outputStream.close();
((SXSSFWorkbook) workbook).dispose(); // 刪除生成的臨時文件
// 結束寫入時間
long end = System.currentTimeMillis();
System.out.println("寫入完畢,花費時間:");
System.out.println((double) (end - begin)/1000);
}
我這個電腦有點撈,花費時間比較長,上面那個低配版本寫了很久都沒寫完所以纔沒截圖的。
基本讀操作
讀操作,需要根據類型來讀,也就是需要判斷類型。這裏就以07版爲例:
下面代碼可以單獨封裝出來,根據類型判斷。Excel表裏的每一行內容實際也就是一個對象,也可以讀出來之後封裝到對象裏。
@Test
public void testCellType() throws Exception{
// 獲取文件流
InputStream inputStream = new FileInputStream("D:\\test.xlsx");
// 創建工作簿
Workbook workbook = new XSSFWorkbook(inputStream);
// 創建工作表 - 下標爲0
Sheet sheet = workbook.getSheetAt(0);
// 獲取標題內容 - 第一行
Row rowTitle = sheet.getRow(0);
if (rowTitle != null){
// 獲取列數
int cellCount = rowTitle.getPhysicalNumberOfCells();
// 遍歷列數取出每一個單元格
for(int cellNum = 0; cellNum < cellCount; cellNum++){
// 獲取列
Cell cell = rowTitle.getCell(cellNum);
if(cell != null){
String cellValue = cell.getStringCellValue();
System.out.print(cellValue + "|");
}
}
System.out.println();
}
// 獲取行數
int rowCount = sheet.getPhysicalNumberOfRows();
for (int rowNum = 1; rowNum < rowCount; rowNum++){
// 獲取行
Row row = sheet.getRow(rowNum);
if (row != null){
// 獲取列數
int cellCount = row.getPhysicalNumberOfCells();
for (int cellNum = 0; cellNum < cellCount; cellNum++){ // 遍歷列
Cell cell = row.getCell(cellNum);
String value = "";
if (cell != null){
int cellType = cell.getCellType(); // 獲取單元格數據類型
switch (cellType){
case HSSFCell.CELL_TYPE_STRING: // 字符串類型
System.out.print("【String類型】");
value = cell.getStringCellValue();
break;
case HSSFCell.CELL_TYPE_BOOLEAN: // 布爾類型
System.out.println("【boolean類型】");
value = String.valueOf(cell.getBooleanCellValue());
break;
case HSSFCell.CELL_TYPE_BLANK: // 爲空
break;
case HSSFCell.CELL_TYPE_NUMERIC: // 數字(日期、普通數字)
if (HSSFDateUtil.isCellDateFormatted(cell)){ // 日期
System.out.println("【Date日期】");
value = new DateTime(cell.getDateCellValue()).toString("yyyy-MM-dd");
}else{ // 數字
System.out.println("【純數字】");
cell.setCellType(HSSFCell.CELL_TYPE_STRING);
value = String.valueOf(cell.toString());
}
break;
case HSSFCell.CELL_TYPE_ERROR:
System.out.println("【數據類型錯誤】");
break;
}
System.out.println(value);
}
}
}
}
}
結果:
EsayExcel - 推薦使用
Java解析、生成Excel比較有名的框架有Apache poi、jxl。但他們都存在一個嚴重的問題就是非常的耗內存,poi有一套SAX模式的API可以一定程度的解決一些內存溢出的問題,但POI還是有一些缺陷,比如07版Excel解壓縮以及解壓後存儲都是在內存中完成的,內存消耗依然很大。easyexcel重寫了poi對07版Excel的解析,能夠原本一個3M的excel用POI sax依然需要100M左右內存降低到幾M,並且再大的excel不會出現內存溢出,03版依賴POI的sax模式。在上層做了模型轉換的封裝,讓使用者更加簡單方便。
EasyExcel是阿里巴巴開源的一個excel處理框架,以使用簡單、節省內存著稱。EasyExcel能大大減少佔用內存的主要原因是在解析Excel時沒有將文件數據一次性全部加載到內存中,而是從磁盤上一行行讀取數據,逐個解析。
EasyExcel採用一行一行的解析模式,並將一行的解析結果以觀察者的模式通知處理(AnalysisEventListener)。
寫操作
先創建一個實體類
@Data
public class User {
@ExcelProperty("學生編號")
private Long id;
@ExcelProperty("學生姓名")
private String name;
@ExcelProperty("入學時間")
private Date date;
@ExcelProperty("入學成績")
private Double grade;
@ExcelProperty("是否入學")
private Boolean isOk;
@ExcelIgnore // 忽略
private String sex;
}
創建寫的測試類
@Test
public void testWrite(){
String fileName = "D:\\write.xlsx";
EasyExcel.write(fileName, User.class).sheet("學生列表").doWrite(getDate());
}
/**
* 獲取數據
*/
public static List<User> getDate(){
List<User> list = new ArrayList<User>();
User user;
for (int i = 1; i <= 10; i++){
user = new User();
user.setId((long)i);
user.setName("學生" + i + "號");
user.setDate(new Date());
user.setGrade(99.99);
user.setIsOk(i%2==0);
user.setSex((i%2==0)?"男":"女");
list.add(user);
System.out.println(user);
}
return list;
}
結果:
讀操作
先創建一個監聽器
public class ExcelListener extends AnalysisEventListener<User> {
private List<User> users = new ArrayList<User>();
// 讓外界可以調用
public List<User> getUser() {
return users;
}
// 一行一行讀取Excel內容
@Override
public void invoke(User user, AnalysisContext analysisContext) {
System.out.println("讀取中...");
users.add(user); // 加入到集合
}
// 讀取表頭內容
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
System.out.println("表頭:" + headMap);
}
// 讀取完成之後做什麼事
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
System.out.println("讀完了");
}
}
測試代碼
@Test
public void testRead() {
// 實現Excel讀操作
String fileName = "D:\\write.xlsx";
ExcelListener excelListener = new ExcelListener();
EasyExcel.read(fileName,User.class,excelListener).sheet().doRead();
List<User> users = excelListener.getUser();
for (User user : users) {
System.out.println(user);
}
}
結果(sex已經被忽略,所以爲null):
實際開發中Excel的的讀取
因爲在實際開發中,我們通常要調用某個服務模塊,把從Excel裏讀取的數據存入到數據庫。因此,修改監聽器代碼如下:
public class ExcelListener extends AnalysisEventListener<User> {
// 用於控制存儲數據庫
private static int BATCH_COUNT = 100;
// 用於存儲數據
private List<User> users = new ArrayList<User>();
// 在這個監聽器類沒有辦法通過註解的方式注入Service,只能通過參數傳遞
private UserService userService;
public ExcelListener(){}
public ExcelListener(UserService userService){
this.userService = userService;
}
// 一行一行讀取Excel內容
@Override
public void invoke(User user, AnalysisContext analysisContext) {
users.add(user);
// 達到BATCH_COUNT了,需要存入一次數據,防止數據太多在內存,導致OOM
if(users.size() >= BATCH_COUNT){
userService.saveUsers(users); // 保存一次
users.clear(); // 清空List集合
}
}
// 讀取表頭內容
@Override
public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
System.out.println("表頭:" + headMap);
}
// 讀取完成之後做什麼事
@Override
public void doAfterAllAnalysed(AnalysisContext analysisContext) {
System.out.println("讀完了");
}
}