在 Java 中使用 Microsoft Excel
1. 概述
在本教學中,我們將示範如何使用Apache POI、JExcel 和 Fastexcel API 來處理 Excel 電子表格。
這些程式庫可用於動態讀取、寫入和修改 Excel 電子表格的內容,並提供將 Microsoft Excel 整合到 Java 應用程式中的有效方法。
2.Maven依賴
首先,我們需要將以下依賴項新增到pom.xml
檔案中:
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.5</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.5</version>
</dependency>
<dependency>
<groupId>org.jxls</groupId>
<artifactId>jxls-jexcel</artifactId>
<version>1.0.9</version>
</dependency>
<dependency>
<groupId>org.dhatim</groupId>
<artifactId>fastexcel-reader</artifactId>
<version>0.17.0</version>
</dependency>
<dependency>
<groupId>org.dhatim</groupId>
<artifactId>fastexcel</artifactId>
<version>0.17.0</version>
</dependency>
最新版本的poi 、 poi-ooxml 、 jxls-jexcel 、 fastexcel-reader和fastexcel都可以從 Maven Central 下載。
3.阿帕契興趣點
Apache POI 函式庫支援.xls
和.xlsx
文件,並且是比處理 Excel 文件的其他 Java 函式庫更複雜的函式庫。
它提供了用於對Excel
文件進行建模的Workbook
接口,以及對 Excel 文件的元素進行建模的Sheet
、 Row,
和Cell
接口,以及兩種文件格式的每個接口的實現。
使用較新的.xlsx
檔案格式時,我們將使用XSSFWorkbook
、 XSSFSheet
、 XSSFRow,
和XSSFCell
類別.
為了使用舊的.xls
格式,我們使用HSSFWorkbook
、 HSSFSheet
、 HSSFRow,
和HSSFCell
類別.
3.1.從 Excel 讀取
讓我們建立一個方法來開啟.xlsx
文件,然後從該文件的第一頁讀取內容。
讀取單元格內容的方法會根據單元格中資料的類型而變化。可以使用Cell
介面的getCellType()
方法來決定單元格內容的類型。
首先,讓我們從給定位置開啟檔案:
FileInputStream file = new FileInputStream(new File(fileLocation));
Workbook workbook = new XSSFWorkbook(file);
接下來,讓我們檢索文件的第一頁並迭代每一行:
Sheet sheet = workbook.getSheetAt(0);
Map<Integer, List<String>> data = new HashMap<>();
int i = 0;
for (Row row : sheet) {
data.put(i, new ArrayList<String>());
for (Cell cell : row) {
switch (cell.getCellType()) {
case STRING: ... break;
case NUMERIC: ... break;
case BOOLEAN: ... break;
case FORMULA: ... break;
default: data.get(new Integer(i)).add(" ");
}
}
i++;
}
Apache POI 有不同的方法來讀取每種類型的資料。讓我們來展開一下上面每個 switch case 的內容。
當單元格類型列舉值為STRING
時,將使用Cell
介面的getRichStringCellValue()
方法讀取內容:
data.get(new Integer(i)).add(cell.getRichStringCellValue().getString());
具有NUMERIC
內容類型的儲存格可以包含日期或數字,並按以下方式讀取:
if (DateUtil.isCellDateFormatted(cell)) {
data.get(i).add(cell.getDateCellValue() + "");
} else {
data.get(i).add(cell.getNumericCellValue() + "");
}
對於BOOLEAN
值,我們有getBooleanCellValue()
方法:
data.get(i).add(cell.getBooleanCellValue() + "");
當單元格類型為FORMULA
時,我們可以使用getCellFormula()
方法:
data.get(i).add(cell.getCellFormula() + "");
3.2.寫入 Excel
Apache POI 使用上一節中介紹的相同介面來寫入 Excel 文件,並且比 JExcel 具有更好的樣式支援。
讓我們建立一個方法,將人員清單寫入標題為“Persons”
工作表中。
首先,我們將建立包含“Name”
和“Age”
儲存格的標題行並設定樣式:
Workbook workbook = new XSSFWorkbook();
Sheet sheet = workbook.createSheet("Persons");
sheet.setColumnWidth(0, 6000);
sheet.setColumnWidth(1, 4000);
Row header = sheet.createRow(0);
CellStyle headerStyle = workbook.createCellStyle();
headerStyle.setFillForegroundColor(IndexedColors.LIGHT_BLUE.getIndex());
headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND);
XSSFFont font = ((XSSFWorkbook) workbook).createFont();
font.setFontName("Arial");
font.setFontHeightInPoints((short) 16);
font.setBold(true);
headerStyle.setFont(font);
Cell headerCell = header.createCell(0);
headerCell.setCellValue("Name");
headerCell.setCellStyle(headerStyle);
headerCell = header.createCell(1);
headerCell.setCellValue("Age");
headerCell.setCellStyle(headerStyle);
接下來,我們用不同的風格來寫表格的內容:
CellStyle style = workbook.createCellStyle();
style.setWrapText(true);
Row row = sheet.createRow(2);
Cell cell = row.createCell(0);
cell.setCellValue("John Smith");
cell.setCellStyle(style);
cell = row.createCell(1);
cell.setCellValue(20);
cell.setCellStyle(style);
最後,我們將內容寫入目前目錄中的“temp.xlsx”
檔案並關閉工作簿:
File currDir = new File(".");
String path = currDir.getAbsolutePath();
String fileLocation = path.substring(0, path.length() - 1) + "temp.xlsx";
FileOutputStream outputStream = new FileOutputStream(fileLocation);
workbook.write(outputStream);
workbook.close();
讓我們在JUnit
測試中測試上述方法,該測試將內容寫入temp.xlsx
文件,然後讀取同一文件以驗證它包含我們編寫的文字:
public class ExcelIntegrationTest {
private ExcelPOIHelper excelPOIHelper;
private static String FILE_NAME = "temp.xlsx";
private String fileLocation;
@Before
public void generateExcelFile() throws IOException {
File currDir = new File(".");
String path = currDir.getAbsolutePath();
fileLocation = path.substring(0, path.length() - 1) + FILE_NAME;
excelPOIHelper = new ExcelPOIHelper();
excelPOIHelper.writeExcel();
}
@Test
public void whenParsingPOIExcelFile_thenCorrect() throws IOException {
Map<Integer, List<String>> data
= excelPOIHelper.readExcel(fileLocation);
assertEquals("Name", data.get(0).get(0));
assertEquals("Age", data.get(0).get(1));
assertEquals("John Smith", data.get(1).get(0));
assertEquals("20", data.get(1).get(1));
}
}
4.JExcel
JExcel 函式庫是一個輕量級函式庫,優點是比 Apache POI 更容易使用,但缺點是它只提供對處理.xls
(1997-2003) 格式的 Excel 檔案的支援。
目前,不支援.xlsx
檔。
4.1.從 Excel 讀取
為了處理 Excel 文件,該程式庫提供了一系列代表 Excel 文件不同部分的類別。 Workbook
類別代表整個工作表集合。 Sheet
類別表示單一工作表, Cell
類別表示電子表格的單一儲存格。
讓我們編寫一個方法,從指定的 Excel 檔案建立工作簿,取得檔案的第一張工作表,然後遍歷其內容並將每一行新增至HashMap
:
public class JExcelHelper {
public Map<Integer, List<String>> readJExcel(String fileLocation)
throws IOException, BiffException {
Map<Integer, List<String>> data = new HashMap<>();
Workbook workbook = Workbook.getWorkbook(new File(fileLocation));
Sheet sheet = workbook.getSheet(0);
int rows = sheet.getRows();
int columns = sheet.getColumns();
for (int i = 0; i < rows; i++) {
data.put(i, new ArrayList<String>());
for (int j = 0; j < columns; j++) {
data.get(i)
.add(sheet.getCell(j, i)
.getContents());
}
}
return data;
}
}
4.2.寫入 Excel
為了寫入 Excel 文件,JExcel 庫提供了與上面使用的類似的類,這些類對電子表格文件進行建模: WritableWorkbook
、 WritableSheet,
和WritableCell
。
WritableCell
類別具有與可寫入的不同類型內容相對應的子類別: Label
、 DateTime
、 Number
、 Boolean
、 Blank,
和Formula
。
該庫還支援基本格式設置,例如控製字體、顏色和單元格寬度。
讓我們寫一個方法,在目前目錄中建立一個名為“temp.xls”
的工作簿,然後寫入與我們在 Apache POI 部分中所寫的相同內容。
首先,讓我們建立工作簿:
File currDir = new File(".");
String path = currDir.getAbsolutePath();
String fileLocation = path.substring(0, path.length() - 1) + "temp.xls";
WritableWorkbook workbook = Workbook.createWorkbook(new File(fileLocation));
接下來,我們建立第一個工作表並寫入 Excel 文件的標題,其中包含“Name”
和“Age”
儲存格:
WritableSheet sheet = workbook.createSheet("Sheet 1", 0);
WritableCellFormat headerFormat = new WritableCellFormat();
WritableFont font
= new WritableFont(WritableFont.ARIAL, 16, WritableFont.BOLD);
headerFormat.setFont(font);
headerFormat.setBackground(Colour.LIGHT_BLUE);
headerFormat.setWrap(true);
Label headerLabel = new Label(0, 0, "Name", headerFormat);
sheet.setColumnView(0, 60);
sheet.addCell(headerLabel);
headerLabel = new Label(1, 0, "Age", headerFormat);
sheet.setColumnView(0, 40);
sheet.addCell(headerLabel);
使用新的樣式,讓我們編寫我們創建的表格的內容:
WritableCellFormat cellFormat = new WritableCellFormat();
cellFormat.setWrap(true);
Label cellLabel = new Label(0, 2, "John Smith", cellFormat);
sheet.addCell(cellLabel);
Number cellNumber = new Number(1, 2, 20, cellFormat);
sheet.addCell(cellNumber);
記住寫入檔案並在最後關閉它非常重要,這樣它就可以被其他進程使用,使用Workbook
類別的write()
和close()
方法:
workbook.write();
workbook.close();
5.快速Excel
Fastexcel 是一個易於使用的函式庫,與 Apache POI 相比,功能有限且記憶體佔用較低。
它使用CompletableFuture
的多執行緒支援使其成為處理功能不廣泛的大型檔案時的絕佳選擇。目前,該庫僅支援基本樣式,不支援圖形。
5.1.從 Excel 讀取
讓我們編寫一個方法來存取 Excel 檔案並從其第一張表中讀取資料。讓我們將資料加入行索引的Map
作為鍵,並將該行的內容清單作為值:
public class FastexcelHelper {
public Map<Integer, List<String>> readExcel(String fileLocation) throws IOException {
Map<Integer, List<String>> data = new HashMap<>();
try (FileInputStream file = new FileInputStream(fileLocation); ReadableWorkbook wb = new ReadableWorkbook(file)) {
Sheet sheet = wb.getFirstSheet();
try (Stream<Row> rows = sheet.openStream()) {
rows.forEach(r -> {
data.put(r.getRowNum(), new ArrayList<>());
for (Cell cell : r) {
data.get(r.getRowNum()).add(cell.getRawValue());
}
});
}
}
return data;
}
}
在這裡,我們使用cell.getRawValue()
它將該單元格的值作為String.
或者,基於CellType
我們可以使用**Row
類別中的getCellAsBoolean(int cellIndex)
、 getCellAsDate(int cellIndex)
、 getCellAsString(int cellIndex)
或getCellAsNumber(int cellIndex)
方法來讀取單元格的內容。**
5.2.寫入 Excel
與上面提到的函式庫不同,
Fastexcel**使用與讀取 Excel 不同的介面來寫入 Excel 檔案**。
我們將首先從 OutputStream 建立Workbook
OutputStream.
然後,我們將在預設的第一個工作表中寫入“Name”
和“Age”
標題,並為儲存格新增樣式詳細資訊。接下來,讓我們再增加一行,其中包含人員的姓名和年齡:
public void writeExcel() throws IOException {
File currDir = new File(".");
String path = currDir.getAbsolutePath();
String fileLocation = path.substring(0, path.length() - 1) + "fastexcel.xlsx";
try (OutputStream os = Files.newOutputStream(Paths.get(fileLocation)); Workbook wb = new Workbook(os, "MyApplication", "1.0")) {
Worksheet ws = wb.newWorksheet("Sheet 1");
ws.width(0, 25);
ws.width(1, 15);
ws.range(0, 0, 0, 1).style().fontName("Arial").fontSize(16).bold().fillColor("3366FF").set();
ws.value(0, 0, "Name");
ws.value(0, 1, "Age");
ws.range(2, 0, 2, 1).style().wrapText(true).set();
ws.value(2, 0, "John Smith");
ws.value(2, 1, 20L);
}
}
讓我們寫一個小測試來驗證我們的程式碼:
@Test
public void whenParsingExcelFile_thenCorrect() throws IOException {
Map<Integer, List<String>> data = fastexcelHelper.readExcel(fileLocation);
assertEquals("Name", data.get(1).get(0));
assertEquals("Age", data.get(1).get(1));
assertEquals("John Smith", data.get(3).get(0));
assertEquals("20", data.get(3).get(1));
}
在這裡,我們也看到fastexcel-reader
庫中的Row
類別使用偏移量為1
的變數rowNum
。而fastexcel
庫中Worksheet
類別的value(int r, int c, Object value)
方法需要偏移量為0
的行索引。
六,結論
在本文中,我們了解如何使用Apache POI
API、 JExcel
API 和Fastexcel
API 從 Java 程式讀取和寫入 Excel 檔案。
在決定使用哪個函式庫時,我們應該考慮每個函式庫的優點和缺點。例如,Apache POI 功能豐富且支援圖形,但記憶體佔用量較高。相比之下,Fastexcel 功能有限,但比 Apache POI 消耗更少的記憶體。
本文的完整原始碼可以在 GitHub 上找到。