在日常开发中,Excel文件读取是一个常见的需求,无论是数据处理、报表分析还是数据导入导出,都需要高效地处理Excel文件。本文将介绍几种Java中高效读取Excel数据的方法,并对比它们的性能特点。
Apache POI是Apache软件基金会的开源项目,提供了对Microsoft Office格式文件的读写功能,是Java领域最流行的Excel处理库。
阿里开源的EasyExcel基于POI进行封装优化,解决了POI的内存消耗问题,特别适合大数据量的读取。
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>5.2.3</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>5.2.3</version>
</dependency>
对于大型Excel文件,推荐使用事件模式读取,避免内存溢出:
public class ExcelSAXReader {
public void readExcel(String filePath) {
try (InputStream is = new FileInputStream(filePath)) {
Workbook workbook = WorkbookFactory.create(is);
Sheet sheet = workbook.getSheetAt(0);
// 使用迭代器遍历行
Iterator<Row> rowIterator = sheet.iterator();
while (rowIterator.hasNext()) {
Row row = rowIterator.next();
processRow(row);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void processRow(Row row) {
Iterator<Cell> cellIterator = row.cellIterator();
while (cellIterator.hasNext()) {
Cell cell = cellIterator.next();
System.out.print(getCellValue(cell) + "\t");
}
System.out.println();
}
private String getCellValue(Cell cell) {
return switch (cell.getCellType()) {
case STRING -> cell.getStringCellValue();
case NUMERIC ->
DateUtil.isCellDateFormatted(cell) ?
cell.getDateCellValue().toString() :
String.valueOf(cell.getNumericCellValue());
case BOOLEAN -> String.valueOf(cell.getBooleanCellValue());
case FORMULA -> cell.getCellFormula();
default -> "";
};
}
}
对于超大文件,使用XSSF and SAX方式:
public class ExcelStreamReader {
public void processLargeExcel(String filename) throws Exception {
OPCPackage pkg = OPCPackage.open(filename);
XSSFReader reader = new XSSFReader(pkg);
SharedStringsTable sst = reader.getSharedStringsTable();
XMLReader parser = fetchSheetParser(sst);
InputStream sheet2 = reader.getSheet("rId1");
InputSource sheetSource = new InputSource(sheet2);
parser.parse(sheetSource);
sheet2.close();
}
private XMLReader fetchSheetParser(SharedStringsTable sst) throws SAXException {
XMLReader parser = XMLReaderFactory.createXMLReader();
ContentHandler handler = new SheetHandler(sst);
parser.setContentHandler(handler);
return parser;
}
private static class SheetHandler extends DefaultHandler {
// 实现具体的SAX解析逻辑
}
}
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>3.3.2</version>
</dependency>
// 定义数据模型
@Data
public class DemoData {
@ExcelProperty(index = 0)
private String name;
@ExcelProperty(index = 1)
private Integer age;
@ExcelProperty(index = 2)
private Date birthday;
}
// 读取监听器
public class DemoDataListener extends AnalysisEventListener<DemoData> {
private static final int BATCH_COUNT = 100;
private List<DemoData> cachedDataList = new ArrayList<>(BATCH_COUNT);
@Override
public void invoke(DemoData data, AnalysisContext context) {
cachedDataList.add(data);
if (cachedDataList.size() >= BATCH_COUNT) {
saveData();
cachedDataList = new ArrayList<>(BATCH_COUNT);
}
}
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
saveData();
}
private void saveData() {
// 批量保存数据
if (!cachedDataList.isEmpty()) {
// 这里实现数据持久化逻辑
System.out.println("保存" + cachedDataList.size() + "条数据");
cachedDataList.clear();
}
}
}
// 使用EasyExcel读取
public class EasyExcelReader {
public void readExcel(String filePath) {
EasyExcel.read(filePath, DemoData.class, new DemoDataListener())
.sheet()
.doRead();
}
}
// 读取多个sheet
EasyExcel.read(filePath)
.sheet(0) // 第一个sheet
.doRead();
EasyExcel.read(filePath)
.sheet("Sheet1") // 按名称读取
.doRead();
// 读取部分列
EasyExcel.read(filePath, DemoData.class, new DemoDataListener())
.extraRead(CellExtraTypeEnum.COMMENT) // 读取注释
.sheet()
.doRead();
// 自定义转换器
public class CustomConverter implements Converter<String> {
@Override
public String convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
// 自定义转换逻辑
return "转换后的数据:" + cellData.getStringValue();
}
}
// 使用try-with-resources确保资源释放
try (InputStream is = new FileInputStream(file);
Workbook workbook = WorkbookFactory.create(is)) {
// 处理逻辑
}
// 避免在循环中创建对象
CellStyle cellStyle = workbook.createCellStyle();
for (Row row : sheet) {
Cell cell = row.createCell(0);
cell.setCellStyle(cellStyle); // 复用样式对象
}
// 使用数据类型检测优化处理逻辑
if (cell.getCellType() == CellType.NUMERIC) {
if (DateUtil.isCellDateFormatted(cell)) {
// 处理日期
} else {
// 处理数字
}
}
方案 | 内存占用 | 读取速度 | 适用场景 |
---|---|---|---|
POI DOM模式 | 高 | 慢 | 小文件,需要完整访问 |
POI SAX模式 | 低 | 快 | 大文件,顺序读取 |
EasyExcel | 极低 | 很快 | 超大文件,批量处理 |
选择合适的Excel读取方案需要根据具体场景决定:
无论选择哪种方案,都应该注意资源释放、异常处理和内存管理,确保程序的稳定性和性能。希望本文能帮助你在实际开发中选择合适的Excel读取方案。
注意事项: