起点:数据传输是会出错的
数据从 A 传到 B,中间经过线路、网络、磁盘,任何一个环节都可能让某一位翻转(0 变 1 或 1 变 0)。
A 发送:1 0 1 1 0 1 0 0
↓
传输过程中被干扰
↓
B 收到:1 0 1 1 1 1 0 0
↑
这一位变了
B 收到的数据和 A 发的不一样,但 B 不知道。如果直接用这个错误数据,程序就会出 bug,文件就会损坏。
所以需要一种机制,让 B 能发现数据有没有错。
这就是校验码要解决的问题。
三种校验码,解决的问题逐步升级
奇偶校验 → 能发现错误,但不知道哪位错了
海明码 → 能发现错误,而且知道哪位错了,能纠正
CRC → 能发现错误,检错能力最强,但不能纠正
它们之间不是替代关系,而是针对不同场景的不同工具。
一、奇偶校验
原理
在数据末尾加 1 位,让整个数据中 1 的个数是偶数(偶校验)或奇数(奇校验)。
例子
数据 1011001,偶校验:
数一下 1 的个数:1+0+1+1+0+0+1 = 4 个(偶数)
偶校验要求偶数,已经满足,所以校验位 = 0
发送:1011001 0
↑ 校验位
数据 1011011,偶校验:
数一下 1 的个数:1+0+1+1+0+1+1 = 5 个(奇数)
偶校验要求偶数,不满足,所以校验位 = 1(补一个,变成 6 个)
发送:1011011 1
↑ 校验位
接收方怎么检查
收到数据后,数 1 的个数:
偶数个 1 → 没错
奇数个 1 → 出错了
能力和局限
能检测:1 位错误(1 位翻转,奇偶性一定变)
能检测:3 位、5 位...(奇数个位翻转,奇偶性一定变)
不能检测:2 位错误(两个位同时翻转,奇偶性不变,互相抵消)
不能纠正:知道错了,但不知道哪位错
一句话总结
奇偶校验就是给数据加一个"计数校验",1 的个数对不上就是错了。简单,但能力有限。
二、海明码
为什么需要它
奇偶校验只能告诉你"错了",不能告诉你"哪位错了"。
如果能知道哪位错了,就能直接纠正——把那一位取反就行。
海明码的目标:用多个校验位,精确锁定出错位置。
核心思路
用多个校验位,每个校验位检查数据的不同子集。出错时,把所有"报错"的校验位编号拼起来,就是出错位置。
第一步:校验位放在哪,需要几个
校验位放在位置编号是 2 的幂的地方:位置 1、2、4、8、16...
需要几个校验位取决于数据长度:
2^r ≥ 数据位数 + 校验位数 + 1
以 4 位数据为例,需要 3 个校验位(2³ = 8 ≥ 7+1):
位置: 1 2 3 4 5 6 7
内容: P₁ P₂ D₁ P₃ D₂ D₃ D₄
第二步:每个校验位管哪些位置
规则:把位置编号转成二进制,第 k 位是 1 的位置就归 Pₖ 管。
位置 1 = 001 → 归 P₁
位置 2 = 010 → 归 P₂
位置 3 = 011 → 归 P₁、P₂
位置 4 = 100 → 归 P₃
位置 5 = 101 → 归 P₁、P₃
位置 6 = 110 → 归 P₂、P₃
位置 7 = 111 → 归 P₁、P₂、P₃
整理:
P₁ 管:1, 3, 5, 7
P₂ 管:2, 3, 6, 7
P₃ 管:4, 5, 6, 7
不管数据多长,规则永远是这一个:Pₖ 管二进制第 k 位是 1 的所有位置。
第三步:发送方填校验位
数据 D₁=1, D₂=0, D₃=1, D₄=1,先填进去:
位置: 1 2 3 4 5 6 7
内容: P₁ P₂ 1 P₃ 0 1 1
规则:每个校验位管的那组位置中,1 的个数必须是偶数。
P₁ 管位置 1,3,5,7 → 位置3=1, 位置5=0, 位置7=1 → 2个1(偶数)→ P₁ = 0
P₂ 管位置 2,3,6,7 → 位置3=1, 位置6=1, 位置7=1 → 3个1(奇数)→ P₂ = 1
P₃ 管位置 4,5,6,7 → 位置5=0, 位置6=1, 位置7=1 → 2个1(偶数)→ P₃ = 0
发送出去:
位置: 1 2 3 4 5 6 7
内容: 0 1 1 0 0 1 1
第四步:接收方怎么定位错误
收到数据后,重新检查每组的奇偶性:
P₁ 组(位置1,3,5,7):1 的个数是偶数 → 没问题 → S₁ = 0
P₂ 组(位置2,3,6,7):1 的个数是奇数 → 有问题 → S₂ = 1
P₃ 组(位置4,5,6,7):1 的个数是奇数 → 有问题 → S₃ = 1
把结果拼起来:
S₃S₂S₁ = 110 = 6 → 位置 6 出错
把位置 6 取反,纠正完成。
为什么拼二进制就能定位
因为校验位的分组规则就是按二进制设计的:
S₁ = 1 → 出错位置的二进制第 1 位是 1
S₂ = 1 → 出错位置的二进制第 2 位是 1
S₃ = 0 → 出错位置的二进制第 3 位是 0
拼起来:110 = 位置 6
分组规则和定位规则是配套设计的,不是巧合。
能力和局限
能检测:1 位错误
能纠正:1 位错误(精确定位,取反纠正)
不能纠正:2 位同时出错(定位会指向一个错误的位置)
一句话总结
海明码用多个校验位按二进制分组,每组检查奇偶性,报错的组拼起来就是出错位置。能纠正 1 位错误。
三、CRC 循环冗余校验
为什么需要它
奇偶校验和海明码都有局限:
奇偶校验:2 位同时出错就检测不到
海明码:只能纠正 1 位错误
在网络传输、磁盘存储这些场景中,经常会出现连续多位出错(比如信号突然中断一小段)。需要一种检错能力更强的方法。
CRC 的目标:不管错了 1 位还是多位,都能检测出来。
核心思路
发送方和接收方约定一个除数(叫生成多项式)。
发送方用数据除以这个除数,把余数附在数据后面一起发出去。
接收方收到后,用同样的除数再除一次:
余数为 0 → 数据没变 → 没错
余数不为 0 → 数据变了 → 出错
什么是模 2 除法
模 2 除法就是每一位做异或(XOR),没有进位和借位。
XOR 规则:
0 XOR 0 = 0
0 XOR 1 = 1
1 XOR 0 = 1
1 XOR 1 = 0
简单说就是:两位相同为 0,不同为 1。
完整过程
假设数据是 1101,约定的除数是 1011(4 位)。
发送方:
第一步:数据后面补零,补的个数 = 除数位数 - 1 = 3
1101 000
第二步:用模 2 除法除以 1011
1 1 1 0
────────
1011 ) 1 1 0 1 0 0 0
1 0 1 1
───────
1 1 0 0
1 0 1 1
───────
1 1 1 0
1 0 1 1
───────
1 0 1 0
1 0 1 1
───────
0 0 1 0 ← 余数 = 010
每一步都是 XOR:比如 1101 XOR 1011 = 0110
第三步:把余数 010 替换掉后面的零
发送:1101 010
↑
余数(校验码)
接收方:
收到 1101010,用同一个除数 1011 去除:
余数 = 000 → 没错 ✓
如果传输中任何一位变了,比如变成 1111010:
用 1011 去除 → 余数 ≠ 000 → 出错 ✗
为什么能检测多位错误
发送的数据一定整除(因为余数是我们自己填上去的)。
传输中任何一位变了,相当于数据被翻转了。翻转后的数据除以同一个除数,余数几乎不可能是 0。
这是因为除数(生成多项式)是精心选择的数学对象,它保证了:
任何 1 位翻转 → 余数 ≠ 0
任何连续 ≤ n 位翻转 → 余数 ≠ 0(n = 校验码位数)
任何奇数个位翻转 → 余数 ≠ 0
这不是靠人去检查的,是数学性质保证的。就像一把钥匙开一把锁,钥匙上任何一个齿磨掉一点就打不开。
能力和局限
检错能力很强:能检测多位错误、突发错误
不能纠正:知道错了,但不知道哪位错
一句话总结
CRC 用数学除法算出一个校验码附在数据后面,接收方重新除一次,余数不为零就是错了。检错能力最强,但不能纠正。
三种校验码对比
| 奇偶校验 | 海明码 | CRC | |
|---|---|---|---|
| 校验位数量 | 1 位 | 多位 | 多位 |
| 能检测 1 位错误 | 能 | 能 | 能 |
| 能检测多位错误 | 不能 | 不能 | 能 |
| 能纠正错误 | 不能 | 能纠正 1 位 | 不能 |
| 底层原理 | 数 1 的个数 | 分组奇偶 + 二进制定位 | 多项式除法 |
| 适用场景 | 简单传输 | 内存(ECC) | 网络、磁盘 |
三者的关系
奇偶校验:1 个校验位,检查整体奇偶性
↓ 能力不够,想知道哪位错了
海明码:多个校验位,按二进制分组,定位错误
↓ 能力不够,想检测多位错误
CRC:用数学除法,检错能力最强
它们不是互相替代,而是各有适用场景。计算机系统中经常组合使用。