1.0、100.0001.23E8Date 也可能是數值序列號 (如 44927)public final class ExcelCellUtils {
private static final DecimalFormat DEFAULT_DECIMAL_FMT = new DecimalFormat("0.###");
private static final DecimalFormat SCIENTIFIC_FMT = new DecimalFormat("0.####################");
private static final DataFormatter DATA_FORMATTER = new DataFormatter(Locale.CHINA); // 內建格式化器
private ExcelCellUtils() {}
/**
* 獲取單元格「顯示值」的統一入口,自動處理數值、日期、公式、科學計數法
* @param cell POI Cell 對象
* @return 字符串值(去除多餘 .0、科學計數法還原、日期格式化)
*/
public static String getDisplayValue(Cell cell) {
if (cell == null) return "";
// 公式單元格取緩存值
if (cell.getCellType() == CellType.FORMULA) {
try {
return getDisplayValue(cell.getCachedFormulaResultType(), cell);
} catch (Exception e) {
return cell.getCellFormula(); // 降級返回公式文本
}
}
return getDisplayValue(cell.getCellType(), cell);
}
private static String getDisplayValue(CellType type, Cell cell) {
switch (type) {
case STRING:
return cell.getStringCellValue().trim();
case NUMERIC:
if (DateUtil.isCellDateFormatted(cell)) {
// 日期類型:統一格式化為 yyyy-MM-dd HH:mm:ss
Date date = cell.getDateCellValue();
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(date);
}
// 數值類型:去除尾部 .0,修復科學計數法
return formatNumeric(cell.getNumericCellValue());
case BOOLEAN:
return String.valueOf(cell.getBooleanCellValue());
case BLANK:
return "";
case ERROR:
return "[ERROR:" + cell.getErrorCellValue() + "]";
default:
return "";
}
}
/**
* 數值格式化核心邏輯:
* 1. 整數值 (如 100.0) -> "100"
* 2. 小數值 (如 3.1400) -> "3.14"
* 3. 科學計數法 (1.23E8) -> "123000000"
* 4. 超長整數 (ID/手機號) -> 完整字符串,不丟失精度
*/
private static String formatNumeric(double value) {
// 先用科學計數法格式化器判斷是否為科學計數法輸出
String scientific = SCIENTIFIC_FMT.format(value);
if (scientific.contains("E") || scientific.contains("e")) {
// 確為科學計數法,用 BigDecimal 精確還原
return new BigDecimal(value).toPlainString();
}
// 非科學計數法,用默認格式化器去除尾部 0
String formatted = DEFAULT_DECIMAL_FMT.format(value);
// 若為整數 (無小數點或小數部分全 0),去除 .0
if (formatted.endsWith(".0")) {
return formatted.substring(0, formatted.length() - 2);
}
return formatted;
}
/**
* 獲取數值型單元格的精確數值 (BigDecimal),用於計算場景
*/
public static BigDecimal getNumericValue(Cell cell) {
if (cell == null || cell.getCellType() != CellType.NUMERIC) return null;
if (DateUtil.isCellDateFormatted(cell)) return null; // 日期不作數值
return new BigDecimal(cell.getNumericCellValue());
}
/**
* 獲取日期型單元格的 Date 對象
*/
public static Date getDateValue(Cell cell) {
if (cell == null) return null;
if (cell.getCellType() == CellType.NUMERIC && DateUtil.isCellDateFormatted(cell)) {
return cell.getDateCellValue();
}
// 兼容文本格式日期
if (cell.getCellType() == CellType.STRING) {
String str = cell.getStringCellValue().trim();
for (String pattern : DATE_PATTERNS) {
try { return new SimpleDateFormat(pattern).parse(str); }
catch (ParseException ignored) {}
}
}
return null;
}
private static final String[] DATE_PATTERNS = {
"yyyy-MM-dd", "yyyy/MM/dd", "yyyy.MM.dd",
"yyyy-MM-dd HH:mm:ss", "yyyy/MM/dd HH:mm:ss"
};
}
public class StreamingExcelReader implements AutoCloseable {
private final OPCPackage pkg;
private final XSSFReader reader;
private final SharedStringsTable sst;
private final StylesTable styles;
private final DataFormatter formatter = new DataFormatter(Locale.CHINA);
public StreamingExcelReader(InputStream inputStream) throws IOException, OpenXML4JException {
this.pkg = OPCPackage.open(inputStream);
this.reader = new XSSFReader(pkg);
this.sst = reader.getSharedStringsTable();
this.styles = reader.getStylesTable();
}
public void processSheet(int sheetIndex, SheetRowHandler handler) throws Exception {
try (InputStream sheetStream = reader.getSheet("rId" + (sheetIndex + 1))) {
XMLReader parser = XMLReaderFactory.createXMLReader();
SheetHandler sheetHandler = new SheetHandler(styles, sst, formatter, handler);
parser.setContentHandler(sheetHandler);
parser.parse(new InputSource(sheetStream));
}
}
@Override
public void close() throws IOException {
pkg.close();
}
// 內部 SAX 處理器,逐行回調
private static class SheetHandler extends DefaultHandler { /* 省略 SAX 事件處理邏輯 */ }
@FunctionalInterface
public interface SheetRowHandler {
void handleRow(int rowNum, List<String> cellValues);
}
}
CellStyle 放入 Map 復用,避免超過 64k 樣式限制導致文件損壞。SXSSFWorkbook (流式寫入,僅保留滾動窗口在內存),配合 trackAllColumnsForAutoSizing() 自動列寬。cell.setCellValue(double) 會寫為數值型,避免 Excel 警告「數字儲存為文本」。DataFormat 獲取內建格式 ID (如 14 = yyyy-MM-dd),而非自定義格式字符串。| 現象 | 原因 | 解決 |
|---|---|---|
| 長數字 (18位+) 變科學計數法/末位變 0 | Excel 數值型只保 15 位有效數字 | 設置單元格格式為「文本」(@) 或寫入前加單引號 ';POI: cell.setCellStyle(textStyle) + cell.setCellValue("123456789012345678") |
| 日期讀出亂碼/數字 | 未判斷 DateUtil.isCellDateFormatted | 必須先判斷日期格式,再調用 getDateCellValue() |
| 公式單元格讀出舊值 | 未重新計算公式 | 寫入後調用 workbook.getCreationHelper().createFormulaEvaluator().evaluateAll();或打開文件手動刷新 |
| 大文件 OOM | 用戶模型 (XSSFWorkbook) 全量載入內存 | 改用 SXSSFWorkbook (寫) 或 Event API / SAX (讀) |
Excel 解析的核心難點在於「單元格類型判斷」與「格式化還原」。封裝統一的 getDisplayValue() 入口,內部處理數值去尾、科學計數法還原、日期統一格式、公式緩存值讀取,可覆蓋 95% 以上業務場景。大文件場景務必採用流式 API (SXSSF/Event API),並建立樣式復用機制。願此工具類助你輕鬆應對各類 Excel 業務。