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

中國二代身份證校驗算法完整解析:權重計算、校驗碼對照、Java 實現與邊界情況

     編輯:Java 相關知識

前言

中華人民共和國居民身份證 (二代證) 號碼為 18 位,包含地址碼(6)、出生日期碼(8)、順序碼(3)、校驗碼(1)。校驗碼基於 **ISO 7064:1983.MOD 11-2** 標準計算,能有效檢測輸入錯誤、偽造號碼。本文詳細推導算法、提供完整 Java 實現、解析 15 位舊版號碼轉換、討論資料庫存儲優化策略。

18 位身份證結構

位置長度含義標準
1-66地址碼 (行政區劃)GB/T 2260
7-148出生日期 (YYYYMMDD)GB/T 7408 (無分隔符)
15-173順序碼 (同地址同日期)奇數男、偶數女
181校驗碼 (0-9 或 X)ISO 7064 MOD 11-2

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

步驟 1:加權求和

前 17 位數字分別乘以固定權重因子 Wi:

位置 i:   1   2   3   4   5   6   7   8   9   10  11  12  13  14  15  16  17
權重 Wi:  7   9   10  5   8   4   2   1   6   3   7   9   10  5   8   4   2

公式:S = Σ (Ai × Wi) , i=1..17,其中 Ai 為第 i 位數字值 (0-9)。

步驟 2:取模 11

Y = S mod 11,結果範圍 0-10。

步驟 3:查表獲取校驗碼

Y (模值)012345678910
校驗碼10X98765432

注意:模值 2 對應字符 **'X'** (大寫羅馬數字 10),非數字。

完整 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'};
    // 18 位身份證正則 (基礎格式校驗)
    private static final Pattern ID18_PATTERN = Pattern.compile("^\\d{17}[\\dX]$");

    private IdCardValidator() {}

    /**
     * 計算前 17 位對應的校驗碼
     * @param id17 17 位數字字符串
     * @return 校驗碼字符 ('0'-'9' 或 'X')
     * @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("第 " + (i+1) + " 位非數字: " + c);
            }
            sum += (c - '0') * WEIGHTS[i];
        }
        return CHECK_CODES[sum % 11];
    }

    /**
     * 完整驗證 18 位身份證
     * @param id18 18 位身份證字符串
     * @return true: 格式正確且校驗碼匹配
     */
    public static boolean isValid(String id18) {
        if (id18 == null || !ID18_PATTERN.matcher(id18).matches()) {
            return false; // 格式不符
        }
        // 校驗出生日期合法性 (可選,但強烈建議)
        if (!isValidBirthDate(id18.substring(6, 14))) {
            return false;
        }
        // 校驗碼
        char expected = calculateCheckCode(id18.subSequence(0, 17));
        char actual = Character.toUpperCase(id18.charAt(17));
        return expected == actual;
    }

    /** 簡易日期合法性檢查 (含閏年) */
    private static boolean isValidBirthDate(String yyyymmdd) {
        try {
            int year = Integer.parseInt(yyyymmdd.substring(0, 4));
            int month = Integer.parseInt(yyyymmdd.substring(4, 6));
            int day = Integer.parseInt(yyyymmdd.substring(6, 8));
            if (year < 1900 || year > 2100) return false;
            if (month < 1 || month > 12) return false;
            int[] daysInMonth = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
            if (month == 2 && isLeapYear(year)) daysInMonth[1] = 29;
            return day >= 1 && day <= daysInMonth[month - 1];
        } catch (Exception e) {
            return false;
        }
    }
    private static boolean isLeapYear(int year) {
        return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
    }

    /** 15 位舊版身份證轉 18 位 (年份補 19/20 + 重新計算校驗碼) */
    public static String upgrade15to18(String id15) {
        if (id15 == null || !id15.matches("^\\d{15}$")) return null;
        // 15 位年份為 2 位,通常補 19 (1900-1999) 或 20 (2000-2099)
        // 這裡簡單補 19,實際應根據業務邏輯判斷
        String id17 = id15.substring(0, 6) + "19" + id15.substring(6);
        char checkCode = calculateCheckCode(id17);
        return id17 + checkCode;
    }

    public static void main(String[] args) {
        // 測試案例
        String id17 = "14230219700101101"; // 山西 大同 雲岡區 1970-01-01 順序101
        System.out.println("計算校驗碼: " + calculateCheckCode(id17)); // 輸出: 3

        String validId = id17 + calculateCheckCode(id17); // 142302197001011013
        System.out.println("完整驗證: " + isValid(validId)); // true

        String invalidId = "142302197001011014"; // 校驗碼錯誤
        System.out.println("錯誤校驗碼: " + isValid(invalidId)); // false

        // 15 位轉換
        String id15 = "142302700101101"; // 對應同上
        System.out.println("15 轉 18: " + upgrade15to18(id15)); // 142302197001011013
    }
}

資料庫存儲優化策略

原文提到「數據庫保留前 17 位,加速 JOIN」。這是一種**以空間換時間**的去範式化設計:

  • 存儲:`CHAR(17)` 存前 17 位本體碼 (定長、無校驗碼)。
  • 查詢**:`JOIN` 時直接比較 17 位定長字符串,**避免** `SUBSTRING(id, 1, 17)` 函數調用導致索引失效。
  • 校驗**:入庫時應用層驗證校驗碼,或資料庫觸發器/計算列驗證。
  • 風險**:冗余存儲需保證一致性;若業務頻繁需展示完整 18 位,需應用層拼接計算。

常見誤區與邊界情況

誤區/情況正確處理
校驗碼 'x' 小寫標準規定大寫 'X',驗證時 `toUpperCase()` 比較
15 位舊版身份證無校驗碼,年份 2 位 (如 90 表 1990);轉 18 位需補全世紀 (19/20) 並重新計算校驗碼
港澳台居民居住證不同編碼規則 (如 830000 開頭),不適用本算法
外國人永久居留身份證不同格式 (通常 18 位字母數字混合),不適用
地址碼變更 (行政區劃調整)舊地址碼仍合法,但需同步最新 GB/T 2260 庫用於歸屬地解析

總結

二代身份證校驗算法 (MOD 11-2) 設計精妙,權重因子交錯分布 (7,9,10,5,8,4,2,1,6,3,7,9,10,5,8,4,2) 能有效檢測單位錯位、相鄰數字交換等常見輸入錯誤。Java 實現關鍵點:**預編譯權重與映射表、CharSequence 避免子串複製、字符算術 `(c-'0')` 替代 `Integer.parseInt`、結合日期合法性與正則格式校驗**。生產系統建議封裝為工具類,單元測試覆蓋邊界值 (閏年、X 校驗碼、15 位轉換)。

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