中華人民共和國居民身份證 (二代證) 號碼為 18 位,包含地址碼(6)、出生日期碼(8)、順序碼(3)、校驗碼(1)。校驗碼基於 **ISO 7064:1983.MOD 11-2** 標準計算,能有效檢測輸入錯誤、偽造號碼。本文詳細推導算法、提供完整 Java 實現、解析 15 位舊版號碼轉換、討論資料庫存儲優化策略。
| 位置 | 長度 | 含義 | 標準 |
|---|---|---|---|
| 1-6 | 6 | 地址碼 (行政區劃) | GB/T 2260 |
| 7-14 | 8 | 出生日期 (YYYYMMDD) | GB/T 7408 (無分隔符) |
| 15-17 | 3 | 順序碼 (同地址同日期) | 奇數男、偶數女 |
| 18 | 1 | 校驗碼 (0-9 或 X) | ISO 7064 MOD 11-2 |
前 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)。
Y = S mod 11,結果範圍 0-10。
| Y (模值) | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 校驗碼 | 1 | 0 | X | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 |
注意:模值 2 對應字符 **'X'** (大寫羅馬數字 10),非數字。
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」。這是一種**以空間換時間**的去範式化設計:
| 誤區/情況 | 正確處理 |
|---|---|
| 校驗碼 '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 位轉換)。