首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 讀取 CSV 實戰:拒絕手工解析,擁抱專業庫與原始數據還原

     編輯:Java 相關知識

CSV 看似簡單,實則暗藏特殊字符處理陷阱:引號包裹、嵌入換行、雙引號轉義、編碼混雜。手工 `split(",")` 註定翻車。本文實戰演示專業庫 **uniVocity-parsers** (現代首選) 與 **Apache Commons CSV**,還原原始數據,配合參數化查詢安全入庫。

CSV 解析陷阱速覽


"張三","25","北京"           // 普通字段
"李四,工程師","30","上海"    // 字段含逗號 → 必須雙引號包裹
"王五","他說:\"你好\"","28"   // 字段含雙引號 → 雙引號轉義為兩個雙引號
"趙六","第一行
第二行","35"                // 字段含換行 → 雙引號包裹跨行
"錢七",,"40"                // 空字段

手工解析需狀態機處理:引號內逗號不分隔、雙雙引號轉義、跨行合併、編碼識別... 輪子造不圓,專業庫已完美解決。

方案一:uniVocity-parsers (性能王者、API 現代、功能最全)



    com.univocity
    univocity-parsers
    2.9.1

核心代碼:還原原始數據、大文件流式處理、類型轉換


public List parseCsvRaw(Path filePath, Charset charset) throws IOException {
    CsvParserSettings settings = new CsvParserSettings();
    settings.setLineSeparatorDetectionEnabled(true);       // 自動識別 \n/\r\n/\r
    settings.setDelimiterDetectionEnabled(true);           // 自動檢測分隔符 (逗號/分號/製表符)
    settings.setMaxCharsPerColumn(-1);                     // 無長度限制
    settings.setMaxColumns(-1);
    settings.setEmptyValue("");                            // 空字段返回空串而非 null
    settings.setNullValue("");                             // null 字段返回空串
    settings.setKeepEscapeSequences(false);                // 移除轉義序列,還原原始內容
    settings.setIgnoreLeadingWhitespaces(true);
    settings.setIgnoreTrailingWhitespaces(true);
    settings.setHeaderExtractionEnabled(false);            // 不提取表頭,全作數據行
    
    // 關鍵:保持原始字符串,不做任何類型轉換、日期解析、修剪
    settings.setInputBufferSize(1024 * 1024);              // 1MB 緩衝區
    
    try (CsvParser parser = new CsvParser(settings);
         Reader reader = Files.newBufferedReader(filePath, charset)) {
        
        return parser.parseAll(reader); // 一次性讀全量 (中小文件)
        // 大文件流式處理見下文
    }
}

// 流式處理大文件 (避免 OOM)
public void streamProcessLargeCsv(Path filePath, Charset charset, Consumer rowHandler) throws IOException {
    CsvParserSettings settings = new CsvParserSettings();
    settings.setLineSeparatorDetectionEnabled(true);
    settings.setDelimiterDetectionEnabled(true);
    settings.setEmptyValue("");
    settings.setNullValue("");
    settings.setKeepEscapeSequences(false);
    
    try (CsvParser parser = new CsvParser(settings);
         Reader reader = Files.newBufferedReader(filePath, charset)) {
        
        parser.beginParsing(reader);
        String[] row;
        while ((row = parser.parseNext()) != null) {
            rowHandler.accept(row); // 逐行處理:入庫、轉換、過濾、聚合
        }
        parser.stopParsing();
    }
}

進階:直接映射到 Java Bean (類型安全)


@Parse
public class UserCsvDto {
    @Trim
    @LowerCase
    @NotNull
    private String username;
    
    @Convert(converter = LocalDateConverter.class, pattern = "yyyy-MM-dd")
    private LocalDate birthday;
    
    @Parsed(index = 2) // 按列索引映射
    private String email;
    
    // getter/setter
}

// 解析映射
BeanListProcessor processor = new BeanListProcessor<>(UserCsvDto.class);
CsvParserSettings settings = new CsvParserSettings();
settings.setProcessor(processor);
settings.setHeaderExtractionEnabled(true); // 第一行作表頭,按列名匹配

new CsvParser(settings).parse(new FileReader("users.csv"));
List users = processor.getBeans();

方案二:Apache Commons CSV (輕量、標準庫風格、Spring Boot 內傳遞依賴)



    org.apache.commons
    commons-csv
    1.10.0


public List parseWithCommonsCsv(Path filePath, Charset charset) throws IOException {
    CSVFormat format = CSVFormat.DEFAULT
        .withDelimiter(',')
        .withQuote('"')
        .withEscape('"')           // 雙引號轉義
        .withIgnoreEmptyLines(false)
        .withTrim(false)           // 關鍵:不修剪空白,保持原始
        .withNullString("")        // null 字段返回空串
        .withRecordSeparator("\n"); // 或自動檢測
    
    try (Reader reader = Files.newBufferedReader(filePath, charset);
         CSVParser parser = format.parse(reader)) {
        
        return parser.getRecords().stream()
            .map(record -> record.values().toArray(new String[0]))
            .collect(Collectors.toList());
    }
}

方案對比選型指南

維度uniVocity-parsersApache Commons CSVOpenCSV
性能 (大文件)⭐⭐⭐⭐⭐ 極快⭐⭐⭐ 良好⭐⭐ 較慢
功能豐富度⭐⭐⭐⭐⭐ Bean映射、類型轉換、固定寬度、多字符分隔符⭐⭐⭐ 基礎完備⭐⭐⭐ 基礎+Bean映射
API 現代度⭐⭐⭐⭐ Fluent、Processor 模式⭐⭐⭐ 標準 Iterator⭐⭐ 較舊
依賴體積~500KB~150KB~200KB
Spring Boot 整合手動引入常隨 spring-boot-starter 伝遞手動引入
推薦場景高性能、複雜 CSV、Bean 映射、大文件流式輕量、簡單解析、已有依賴歷史項目維護

關鍵最佳實踐:原始數據入庫安全鏈路


// 1. 解析獲取原始字符串數組 (無任何轉義殘留)
String[] rawFields = parseCsvRaw(path, StandardCharsets.UTF_8).get(rowIndex);

// 2. 業務層驗證/轉換 (而非解析層)
User user = User.fromCsv(rawFields); // 領域工廠方法,集中驗證邏輯

// 3. 參數化查詢入庫 (JDBC PreparedStatement / MyBatis / JPA / JdbcTemplate)
// 完全委託驅動程序處理特殊字符轉義、SQL 注入防護
String sql = "INSERT INTO users(name, bio, email) VALUES (?, ?, ?)";
jdbcTemplate.update(sql, user.getName(), user.getBio(), user.getEmail());
// 無需手工 replace("'", "''") 等操作!

核心原則:解析層只負責**結構還原** (行/列分割、引號/轉義處理),**不做業務清洗**;業務層做**語義驗證/轉換**;持久層用**參數化綁定**自動處理特殊字符。三層職責分離,安全、可測、可維護。

編碼檢測與兼容性提示

  • 優先要求數據源指定編碼 (UTF-8 最佳),避免猜測。
  • 需自動檢測時:`juniversalchardet`、`ICU4J`、`Apache Tika` 檢測 BOM/統計特徵。
  • 中文舊系統常見 `GBK`/`GB2312`/`CP936`,讀取時顯式指定 `StandardCharsets.ISO_8859_1` 再轉碼,或直接 `Charset.forName("GBK")`。
  • 異常行處理:`settings.setErrorHandler(new CsvErrorHandler() { ... })` 記錄錯誤行號、內容、異常,繼續解析後續行。

總結

CSV 解析**不要手寫**,選 **uniVocity-parsers** (性能/功能雙強) 或 **Commons CSV** (輕量/零額外依賴)。核心配置:`keepEscapeSequences=false`、`trim=false`、`emptyValue=""` 還原原始字符串。下游用參數化查詢入庫,徹底斷開「特殊字符導致 SQL 錯誤/注入」鏈路。專業工具專業用,業務邏輯乾淨純粹。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved