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

中國身份證號碼驗證算法深度解析與 Java 實現

     編輯:Java 相關知識

身份證號碼校驗碼算法原理、Java 實現與工程應用指南

中國居民身份證號碼(18 位)包含地址碼、出生日期碼、順序碼、校驗碼四部分。本文深度解析校驗算法,提供生產就緒的 Java 實現,並探討數據清洗、性能優化等工程應用場景。

18 位身份證結構拆解

段落位數說明標準
地址碼1-6戶籍地行政區劃代碼GB/T 2260
出生日期碼7-14YYYYMMDD 格式GB/T 7408
順序碼15-17同地同日期順序,奇男偶女-
校驗碼18根據前 17 位計算ISO 7064:1983.MOD 11-2

校驗碼計算算法(MOD 11-2)

  1. 加權求和:S = Σ(Ai × Wi),i=0..16
    權重因子 Wi:[7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2]
  2. 取模:Y = S mod 11
  3. 查表映射:
Y 值012345678910
校驗碼10X98765432

Java 核心實現(線程安全、高性能)

public final class IdCardValidator {
    // 權重因子常量
    private static final int[] WEIGHTS = {7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2};
    // 校驗碼映射表,索引 = Y 值
    private static final char[] CHECK_CODES = {'1','0','X','9','8','7','6','5','4','3','2'};
    
    private IdCardValidator() {} // 工具類防實例化

    /**
     * 計算 17 位本體碼對應的校驗碼
     * @param id17 前 17 位數字字符串
     * @return 校驗碼字符
     * @throws IllegalArgumentException 輸入非 17 位數字
     */
    public static char calculateCheckCode(CharSequence id17) {
        if (id17 == null || id17.length() != 17) {
            throw new IllegalArgumentException("本體碼必須為 17 位數字");
        }
        
        int sum = 0;
        for (int i = 0; i < 17; i++) {
            char c = id17.charAt(i);
            if (c < '0' || c > '9') {
                throw new IllegalArgumentException("本體碼包含非數字字符: " + c);
            }
            sum += (c - '0') * WEIGHTS[i]; // 字符轉數字比 parseInt 快
        }
        
        return CHECK_CODES[sum % 11];
    }

    /**
     * 驗證 18 位身份證完整合法性
     * @param id18 18 位身份證號碼
     * @return true 表示格式正確且校驗碼匹配
     */
    public static boolean isValid(String id18) {
        if (id18 == null || id18.length() != 18) return false;
        
        // 前 17 位必須為數字
        for (int i = 0; i < 17; i++) {
            char c = id18.charAt(i);
            if (c < '0' || c > '9') return false;
        }
        
        // 第 18 位:數字或 X
        char last = id18.charAt(17);
        if (!((last >= '0' && last <= '9') || last == 'X' || last == 'x')) return false;
        
        // 校驗碼比對(忽略大小寫)
        char expected = calculateCheckCode(id18.subSequence(0, 17));
        return Character.toUpperCase(last) == expected;
    }

    /**
     * 從 18 位身份證提取出生日期
     * @return LocalDate,格式錯誤返回 null
     */
    public static java.time.LocalDate extractBirthDate(String id18) {
        if (!isValid(id18)) return null;
        try {
            int year = Integer.parseInt(id18.substring(6, 10));
            int month = Integer.parseInt(id18.substring(10, 12));
            int day = Integer.parseInt(id18.substring(12, 14));
            return java.time.LocalDate.of(year, month, day);
        } catch (Exception e) {
            return null;
        }
    }
    
    /**
     * 從 18 位身份證提取性別
     * @return 1=男,0=女,-1=無效
     */
    public static int extractGender(String id18) {
        if (!isValid(id18)) return -1;
        int seq = Integer.parseInt(id18.substring(14, 17));
        return (seq & 1) == 1 ? 1 : 0; // 奇數男,偶數女
    }
}

單元測試驗證

public class IdCardValidatorTest {
    @Test
    public void testCalculateCheckCode() {
        assertEquals('3', IdCardValidator.calculateCheckCode("14230219700101101"));
    }
    
    @Test
    public void testValidIdCards() {
        assertTrue(IdCardValidator.isValid("142302197001011013")); // 校驗碼 3
        assertTrue(IdCardValidator.isValid("11010119900307783X")); // 校驗碼 X
    }
    
    @Test
    public void testInvalidIdCards() {
        assertFalse(IdCardValidator.isValid("142302197001011012")); // 校驗碼錯誤
        assertFalse(IdCardValidator.isValid("14230219700101101"));  // 長度不足
        assertFalse(IdCardValidator.isValid("14230219700101101Y")); // 非法校驗碼
    }
}

工程應用場景與優化建議

1. 數據清洗管道

在 ETL/數據遷移中,將身份證驗證作為第一道關卡:

// Spark/Flink 處理邏輯
dataset.filter(row -> IdCardValidator.isValid(row.getString("id_card")))

2. 資料庫設計優化

文中提到的只存前 17 位策略值得參考:

  • 校驗碼可由前 17 位計算得出,存儲冗余
  • 節省存儲空間(特別是大表)
  • 關鍵優勢:INNER JOIN 時字符串比較更快,索引更小
  • 查詢時動態計算校驗碼組合完整號碼

3. 性能優化技巧

  • 使用 char[] 而非 String 索引訪問
  • 字符轉數字用 c - '0' 避免 Integer.parseInt() 開銷
  • 熱點路徑可預計算 WEIGHTS 到 long 位運算(SIMD 友好)
  • 大批量驗證考慮向量化或批次處理

4. 15 位舊版身份證兼容

15 位版本無校驗碼,出生年份為 2 位(如 90 表 1990)。轉換邏輯:

public static String upgrade15to18(String id15) {
    if (id15 == null || id15.length() != 15) return null;
    String id17 = id15.substring(0, 6) + "19" + id15.substring(6);
    char check = calculateCheckCode(id17);
    return id17 + check;
}

常見誤區提醒

  • 只驗證格式不驗證校驗碼:正則 ^\d{17}[\dX]$ 會通過大量偽造號碼
  • 忽略地址碼有效性:需關聯最新 GB/T 2260 行政區劃庫校驗
  • 忽略出生日期合理性:如 2025-02-30、年齡超 150 歲等
  • 大小寫敏感:校驗碼 'x' 和 'X' 都應接受
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved