第十章 万物皆可编码
10.1 概述
“万物皆可编码”——这并非一句夸张的口号,而是计算机科学的基石。从最底层的电信号高低(0和1),到我们日常使用的文字、图片、音频、视频,再到复杂的文档格式、网络协议、数据库结构,无一不是编码的产物。
所谓编码,就是将信息从一种形式转换为另一种形式的过程。在计算机的世界里,一切信息最终都会被转换为二进制序列——由0和1组成的比特流。正如香农(Claude Shannon)在1948年的划时代论文《通信的数学理论》中所揭示的:信息是可以被量化和编码的。
编码的层次可以概括为以下递进关系:
$$ \text{数据} \xrightarrow{\text{类型化}} \text{类型} \xrightarrow{\text{抽象化}} \text{对象} \xrightarrow{\text{持久化}} \text{文件} \xrightarrow{\text{标准化}} \text{协议} $$
- 数据:最原始的比特流,没有语义。
- 类型:赋予数据以语义,如“这是一个整数“或“这是一个字符串“。
- 对象:将类型与行为绑定,形成具有状态和操作的实体。
- 文件:将对象持久化到存储介质中。
- 协议:在文件格式之上建立标准化的交换规则。
本章将从基本类型出发,逐步深入到面向对象编程、唯一标识符、文件系统、文件格式,最终探讨万物编码的哲学意义。
10.2 基本类型与编码
正如之前章节的介绍,通过编码技术,我们可以实现对数字、文字、符号、时间等数据进行编码以便在计算机中进行各种处理。因此才有了整型、浮点型、字符串、数组、切片、哈希表、元组等基本类型,这些构成了编程语言的类型系统。
Rust 基本类型一览
Rust基本类型包括:
| 类型分类 | 具体类型 | 内存大小 | 说明 |
|---|---|---|---|
| 整数 | i8, i16, i32, i64, i128 | 1/2/4/8/16 字节 | 有符号整数 |
| 整数 | u8, u16, u32, u64, u128 | 1/2/4/8/16 字节 | 无符号整数 |
| 浮点 | f32, f64 | 4/8 字节 | IEEE 754 浮点数 |
| 布尔 | bool | 1 字节 | true 或 false |
| 字符 | char | 4 字节 | Unicode 标量值 |
| 元组 | (T1, T2, ...) | 各元素之和 | 固定长度复合类型 |
| 数组 | [T; N] | size_of::<T>() * N | 固定长度同类型集合 |
| 切片 | &[T] | 胖指针(2个usize) | 动态长度视图 |
| 字符串 | String / &str | 可变 | UTF-8 编码字节数组 |
类型在内存中的编码方式
整数的编码采用补码(Two’s Complement)表示法。对于 $n$ 位有符号整数,其取值范围为:
$$ [-2^{n-1},\ 2^{n-1} - 1] $$
例如,i8 的取值范围是 $[-128, 127]$,i32 的取值范围是 $[-2^{31},\ 2^{31}-1]$。
浮点数的编码遵循 IEEE 754 标准。以 f32 为例,其 32 位由三部分组成:
$$ \text{float} = (-1)^S \times 2^{E-127} \times (1 + M) $$
其中 $S$ 为符号位(1 bit),$E$ 为指数位(8 bits),$M$ 为尾数位(23 bits)。
字符串的编码在 Rust 中采用 UTF-8 编码。UTF-8 是一种变长编码方案:
- ASCII 字符(U+0000 ~ U+007F):1 字节
- 拉丁扩展字符(U+0080 ~ U+07FF):2 字节
- 大部分常用汉字(U+0800 ~ U+FFFF):3 字节
- Emoji 等罕见字符(U+10000 ~ U+10FFFF):4 字节
fn main() {
// 整数在内存中的表示
let a: i32 = -1;
println!("i32 -1 的内存表示: {:032b}", a as u32); // 补码:全1
// 浮点数的精度
let pi: f64 = std::f64::consts::PI;
println!("PI = {:.15}", pi);
// 字符与字符串的UTF-8编码
let ch = '中';
println!("字符 '{}' 的Unicode码点: U+{:04X}", ch, ch as u32);
println!("字符 '{}' 的UTF-8字节: {:?}", ch, ch.len_utf8()); // 3字节
let s = String::from("Hello 世界");
println!("字符串 '{}' 的字节数: {}", s, s.len()); // 12字节
println!("字符串 '{}' 的字符数: {}", s, s.chars().count()); // 7个字符
}
10.3 面向对象与编码
类与对象的概念
“物以类聚,人以群分。” 面向对象编程(Object-Oriented Programming, OOP)通过抽象方法,提取同类事物的关键信息,形成“类“(Class)。类是对象的蓝图或模板,而对象是类的具体实例。
面向对象编程的三大核心特性:
| 特性 | 英文 | 含义 |
|---|---|---|
| 封装 | Encapsulation | 将数据与操作绑定,隐藏内部实现细节 |
| 继承 | Inheritance | 子类复用父类的属性和行为 |
| 多态 | Polymorphism | 同一接口,不同实现 |
对象标识符(OID)
按照 GB/T 17969.1 (ISO/IEC 9834-1) 的定义,对象是指“通信和信息处理世界中的任何事物,它是可标识(可以命名)的,同时它可被注册“。对象标识符(Object Identifier,OID)是与对象相关联的用来无歧义地标识对象的全局唯一的值,可保证对象在通信与信息处理中正确地定位和管理。通俗地讲,OID 就是网络通信中对象的身份证。
OID 采用点分十进制表示,例如 1.3.6.1.5.5.7.1.1 表示“权威信息访问“(Authority Information Access)证书扩展。
Rust 中的面向对象
Rust 并非传统的面向对象语言,但通过 struct、enum 和 trait 可以优雅地实现面向对象的核心思想:
use std::fmt;
// struct 替代"类"——封装数据
struct Person {
name: String,
age: u32,
}
// impl 块——封装行为(方法)
impl Person {
fn new(name: &str, age: u32) -> Self {
Person {
name: name.to_string(),
age,
}
}
fn greet(&self) {
println!("你好,我是{},今年{}岁。", self.name, self.age);
}
}
// trait 替代"接口"——实现多态
trait Describable {
fn describe(&self) -> String;
}
impl Describable for Person {
fn describe(&self) -> String {
format!("Person(name={}, age={})", self.name, self.age)
}
}
// enum 实现代数数据类型
enum Shape {
Circle { radius: f64 },
Rectangle { width: f64, height: f64 },
Triangle { base: f64, height: f64 },
}
impl Shape {
fn area(&self) -> f64 {
match self {
Shape::Circle { radius } => std::f64::consts::PI * radius * radius,
Shape::Rectangle { width, height } => width * height,
Shape::Triangle { base, height } => 0.5 * base * height,
}
}
}
impl fmt::Display for Shape {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
Shape::Circle { radius } => write!(f, "Circle(r={})", radius),
Shape::Rectangle { width, height } => write!(f, "Rect(w={}, h={})", width, height),
Shape::Triangle { base, height } => write!(f, "Tri(b={}, h={})", base, height),
}
}
}
fn main() {
let p = Person::new("张三", 30);
p.greet();
println!("{}", p.describe());
let shapes = vec![
Shape::Circle { radius: 5.0 },
Shape::Rectangle { width: 4.0, height: 6.0 },
Shape::Triangle { base: 3.0, height: 8.0 },
];
for s in &shapes {
println!("{} 的面积 = {:.2}", s, s.area());
}
}
10.4 唯一标识符(ID)
同一类型,拥有数以亿计的对象,如何区分这些独一无二的个体呢?其实要做到这一点并不难,只要给这些个体一个全局唯一的“身份“即可。身份证号、手机号、IP地址、统一社会信用代码——这些都是唯一标识符的现实应用。
10.4.1 身份证号校验
中国居民身份证号码为18位,其中前17位为本体码,最后1位为校验码。校验码的计算基于加权因子取模11的算法:
$$ C = \left( \sum_{i=1}^{17} a_i \times w_i \right) \bmod 11 $$
其中 $a_i$ 为第 $i$ 位数字,$w_i$ 为对应的加权因子 $[7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2]$。校验码映射表为:
| 余数 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 校验码 | 1 | 0 | X | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 |
#![allow(unused)]
fn main() {
use lazy_static::lazy_static;
use regex::Regex;
use std::collections::HashMap;
lazy_static! {
static ref IDENTIFIER_REGEX: Regex = Regex::new("^([0-9ABCDEFGY]{1})([1239]{1})([0-9ABCDEFGHJKLMNPQRTUWXY]{6})([0-9ABCDEFGHJKLMNPQRTUWXY]{9})([0-9ABCDEFGHJKLMNPQRTUWXY])$").unwrap();
static ref REGEX_18_ID_CARD_NO: Regex = Regex::new(r"^[0-9]{17}[0-9X]$").unwrap();
static ref ID_CARD_POWER: Vec<usize> = vec![7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
static ref WEIGHT: Vec<usize> = vec![1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28];
static ref VERIFY_CODE_MAP: HashMap<char,usize> = {
let verify_code = "0123456789ABCDEFGHJKLMNPQRTUWXY";
let verify_code_vec :Vec<char> = verify_code.chars().collect();
let mut verify_code_map = HashMap::new();
for (i,item) in verify_code_vec.iter().enumerate().take(verify_code.len()){
verify_code_map.insert(*item,i);
}
println!("{:?}",verify_code_map);
verify_code_map
};
// 公司名称需要排除的字符串,不包含中英文括号
static ref REGEX_NOT_COMPANY_NAME: Regex = Regex::new(r###"[`~!@#$%^&*+=|{}':;',\\.<>《》/?~!@#¥%……&*——+|\-{}\[\]【】';:""'。,、?]"###).unwrap();
// 金钱(千分位)
static ref MONEY_REGEX: Regex = Regex::new(r"^(-)?\d{1,3}(,\d{3})*(.\d+)?$").unwrap();
}
/// 18位身份证号校验
fn is_18_id_card(id_card_no: &str) -> bool {
if !REGEX_18_ID_CARD_NO.is_match(id_card_no) {
println!("{} isn't match regex", id_card_no);
return false;
}
let id_card_no_chars: Vec<char> = id_card_no.chars().collect();
let mut sum = 0;
for i in 0..17 {
let ch = id_card_no_chars[i] as usize - 48;
sum += ch * ID_CARD_POWER[i];
}
let check_code = match sum % 11 {
10 => '2',
9 => '3',
8 => '4',
7 => '5',
6 => '6',
5 => '7',
4 => '8',
3 => '9',
2 => 'X',
1 => '0',
0 => '1',
_ => 'N',
};
check_code == id_card_no_chars[17]
}
let id_card_no = "450101200012314321";
if is_18_id_card(id_card_no) {
println!("{} is a valid ID card number", id_card_no);
} else {
println!("{} is not a valid ID card number", id_card_no);
}
}
10.4.2 统一社会信用代码校验
统一社会信用代码是18位的标识代码,同样采用加权因子校验算法。上述代码中的 IDENTIFIER_REGEX、WEIGHT 和 VERIFY_CODE_MAP 即为统一社会信用代码校验所需的核心数据:
IDENTIFIER_REGEX:匹配18位统一社会信用代码的格式WEIGHT:加权因子数组 $[1, 3, 9, 27, 19, 26, 16, 17, 20, 29, 25, 13, 8, 24, 10, 30, 28]$VERIFY_CODE_MAP:校验字符映射表(不包含 I、O、Z、S、V)
10.4.3 UUID
UUID(Universally Unique Identifier,通用唯一识别码)是一个128位的标识符,通常以32个十六进制数字表示,格式为 8-4-4-4-12,例如:
550e8400-e29b-41d4-a716-446655440000
UUID 的理论空间为 $2^{128} \approx 3.4 \times 10^{38}$,足够为地球上的每一粒沙子分配一个唯一标识符。
UUID 版本对比:
| 版本 | 生成方式 | 特点 |
|---|---|---|
| v1 | 时间戳 + MAC地址 | 基于时间,可排序,但暴露MAC地址 |
| v3 | MD5 哈希(命名空间+名称) | 确定性生成,相同输入产生相同UUID |
| v4 | 随机数 | 最常用,完全随机 |
| v5 | SHA-1 哈希(命名空间+名称) | 确定性生成,比v3更安全 |
use uuid::{Uuid, Version, Variant};
fn main() {
// 生成 v4 UUID(随机)
let uuid_v4 = Uuid::new_v4();
println!("UUID v4: {}", uuid_v4);
println!("版本: {:?}", uuid_v4.get_version()); // Some(Version::Random)
println!("变体: {:?}", uuid_v4.get_variant()); // Some(Variant::RFC4122)
// 生成 v1 UUID(基于时间戳)
let uuid_v1 = Uuid::new_v1(&[1, 2, 3, 4, 5, 6]);
println!("UUID v1: {}", uuid_v1);
// 从字符串解析 UUID
let parsed = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000");
match parsed {
Ok(u) => println!("解析成功: {}, 版本: {:?}", u, u.get_version()),
Err(e) => println!("解析失败: {}", e),
}
// 生成 v5 UUID(基于命名空间和名称的确定性UUID)
let namespace = Uuid::NAMESPACE_DNS;
let uuid_v5 = Uuid::new_v5(namespace, b"example.com");
println!("UUID v5 (example.com): {}", uuid_v5);
// 相同输入始终产生相同结果
let uuid_v5_again = Uuid::new_v5(namespace, b"example.com");
assert_eq!(uuid_v5, uuid_v5_again);
println!("确定性验证: v5 UUID 相同输入产生相同结果 ✓");
}
Cargo.toml 依赖:
[dependencies]
uuid = { version = "1", features = ["v1", "v4", "v5"] }
10.4.4 雪花算法(Snowflake)
雪花算法是 Twitter 开源的分布式ID生成算法,其核心思想是:将64位的 i64 划分为多个段,每段代表不同的语义信息。
$$ \text{Snowflake ID} = \underbrace{\text{符号位}}{1\text{bit}} \ | \ \underbrace{\text{时间戳}}{41\text{bits}} \ | \ \underbrace{\text{机器ID}}{10\text{bits}} \ | \ \underbrace{\text{序列号}}{12\text{bits}} $$
| 段 | 位数 | 说明 |
|---|---|---|
| 符号位 | 1 bit | 始终为0,保证ID为正数 |
| 时间戳 | 41 bits | 毫秒级时间戳,可用约69年 |
| 机器ID | 10 bits | 最多支持1024台机器 |
| 序列号 | 12 bits | 同一毫秒内最多4096个ID |
41位时间戳的表示范围:
$$ 2^{41} - 1 = 2199023255551 \text{ 毫秒} \approx 69.73 \text{ 年} $$
use std::sync::atomic::{AtomicU16, Ordering};
use std::time::{SystemTime, UNIX_EPOCH};
/// 雪花算法ID生成器
struct Snowflake {
machine_id: u16, // 机器ID (0 ~ 1023)
sequence: AtomicU16, // 序列号 (0 ~ 4095)
last_timestamp: AtomicU64, // 上次生成ID的时间戳
epoch: u64, // 起始时间戳(毫秒)
}
impl Snowflake {
fn new(machine_id: u16) -> Self {
// 自定义纪元:2024-01-01 00:00:00 UTC
let epoch = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis() as u64;
Snowflake {
machine_id: machine_id & 0x3FF, // 确保不超过10位
sequence: AtomicU16::new(0),
last_timestamp: AtomicU64::new(0),
epoch,
}
}
fn next_id(&self) -> i64 {
let current_timestamp = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis() as u64;
let last = self.last_timestamp.load(Ordering::SeqCst);
if current_timestamp == last {
// 同一毫秒内,递增序列号
let seq = self.sequence.fetch_add(1, Ordering::SeqCst);
if seq >= 4095 {
// 序列号溢出,等待下一毫秒
panic!("Sequence overflow: too many IDs in one millisecond");
}
self.make_id(last, seq)
} else {
// 新的毫秒,重置序列号
self.sequence.store(0, Ordering::SeqCst);
self.last_timestamp.store(current_timestamp, Ordering::SeqCst);
self.make_id(current_timestamp, 0)
}
}
fn make_id(&self, timestamp: u64, sequence: u16) -> i64 {
// 时间戳左移22位(10位机器ID + 12位序列号)
let ts_part = (timestamp - self.epoch) << 22;
// 机器ID左移12位(12位序列号)
let machine_part = (self.machine_id as i64) << 12;
// 序列号
let seq_part = sequence as i64;
ts_part | machine_part | seq_part
}
}
fn main() {
let snowflake = Snowflake::new(1); // 机器ID为1
// 连续生成10个ID
for _ in 0..10 {
let id = snowflake.next_id();
println!("Snowflake ID: {}", id);
}
// 解析ID
let id = snowflake.next_id();
let timestamp_part = (id >> 22) + snowflake.epoch as i64;
let machine_part = ((id >> 12) & 0x3FF) as u16;
let sequence_part = (id & 0xFFF) as u16;
println!("\n解析 ID = {}", id);
println!(" 时间戳部分: {}", timestamp_part);
println!(" 机器ID: {}", machine_part);
println!(" 序列号: {}", sequence_part);
}
10.4.5 nanoid
nanoid 是一种轻量级、安全、URL友好的唯一ID生成器。与UUID相比,nanoid生成的ID更短(默认21个字符),且使用URL安全的字符集。
nanoid 的核心思想是:使用密码学安全的随机数生成器,从一个自定义字符集中随机选取字符,组成指定长度的字符串。
use nanoid::nanoid;
fn main() {
// 生成默认21字符的 nanoid
let id1 = nanoid!();
println!("nanoid (默认21字符): {}", id1);
// 生成指定长度的 nanoid
let id2 = nanoid!(10);
println!("nanoid (10字符): {}", id2);
// 使用自定义字符集
let id3 = nanoid!(16, &['a', 'b', 'c', 'd', 'e', 'f', '1', '2', '3']);
println!("nanoid (自定义字符集): {}", id3);
}
Cargo.toml 依赖:
[dependencies]
nanoid = "0.4"
10.4.6 各ID方案对比
| 方案 | 长度 | 生成方式 | 有序性 | 分布式支持 | 适用场景 |
|---|---|---|---|---|---|
| 自增ID | 4~8字节 | 数据库自增 | 严格有序 | 需要中心化 | 单体数据库主键 |
| UUID v4 | 36字符(含连字符) | 随机数 | 无序 | 天然支持 | 通用唯一标识 |
| UUID v1 | 36字符(含连字符) | 时间戳+MAC | 时间有序 | 天然支持 | 需要排序的场景 |
| 雪花算法 | 8字节(i64) | 时间戳+机器ID+序列号 | 趋势递增 | 需分配机器ID | 分布式系统主键 |
| nanoid | 21字符(可配置) | 密码学随机 | 无序 | 天然支持 | URL、短链接、前端 |
10.5 一切皆文件
Unix 哲学:一切皆文件
Unix 操作系统有一个优雅的设计哲学——“一切皆文件”(Everything is a file)。在 Unix/Linux 系统中,无论是普通文件、目录、设备(如 /dev/null、/dev/tty)、管道、套接字,还是 /proc 下的进程信息,都可以通过统一的文件操作接口(open、read、write、close)来访问。
这种设计的优势在于:
- 统一接口:所有资源使用相同的 API 操作
- 组合性:通过管道将多个程序串联
- 简洁性:用少量原语解决大量问题
文件读写
#![allow(unused)]
fn main() {
use std::fs::File;
use std::io::{BufReader, Read, Write};
// 将文件读取为字节数组
if let Ok(cipher_data) = std::fs::read("why-rust.crypto") {
let plaintext = cipher
.decrypt(nonce, cipher_data.as_ref())
.expect("decryption failure!");
println!("{}", String::from_utf8(plaintext).unwrap());
}
/// 逐行读取文件
#[test]
fn read_file_lines() {
match File::open("why-rust.txt") {
Ok(f) => {
let reader = BufReader::new(f);
let lines = reader.lines();
for line in lines.map(|x| x.unwrap()) {
println!("{}", line);
}
}
Err(e) => panic!("can't open this file :{}", e),
}
}
///将文件读取为一个字符串
match std::fs::read_to_string("provinces.json") {
Ok(data) => {
println!("open provinces.json...");
let v: Vec<AdministrativeDivisions> = serde_json::from_str(&data).unwrap();
for ad in v {
println!("{:?}={:?}", ad.code, ad.name);
}
}
Err(e) => panic!("can't open this file : {}", e),
}
// 将字节数组写入文件
let mut all_bytes = Vec::<u8>::with_capacity(128);
all_bytes.extend_from_slice(&key);
all_bytes.extend_from_slice(&iv);
all_bytes.extend_from_slice(&encrypt_bytes);
std::fs::write("poem.crypto", &all_bytes);
}
文件编码:文本文件 vs 二进制文件
文件从编码角度可分为两大类:
| 类型 | 特征 | 读取方式 | 示例 |
|---|---|---|---|
| 文本文件 | 可读字符编码(UTF-8等) | read_to_string() | .txt, .csv, .json, .md |
| 二进制文件 | 任意字节序列 | read() | .exe, .png, .pdf, .xlsx |
文本文件本质上是“对人友好“的二进制文件——它们同样是由字节组成的,只不过这些字节按照特定的字符编码(如 UTF-8、GBK)可以被人类直接阅读。而二进制文件则采用特定格式编码,需要专门的解析器才能理解其内容。
10.6 文件格式与编码
文件格式本质
文件格式的本质就是编码规则——约定如何将特定类型的数据组织为字节序列。不论是简单的 txt、xml、json 等格式的数据还是复杂的 doc、xls、pdf、OFD 等格式,都是某种编码规则的体现。
办公三件套:Word,Excel,PPT
微软的 Office 办公三件套:Word,Excel,PowerPoint 成为办公领域的标准,每年为微软创造上百亿美元的收入。国内金山办公经过三十多年的追赶,其产品 WPS 终于在办公软件拥有一席之地、站稳了脚跟。
Excel / CSV / JSON 处理
Excel 读取
calamine 是 Rust 实现的 Excel 工具,目前只支持读操作(不支持写操作)。
#![allow(unused)]
fn main() {
use calamine::DataType::{
Bool, DateTime, DateTimeIso, Duration, DurationIso, Empty, Error, Float, String,
};
use calamine::{
open_workbook, open_workbook_auto, Ods, Reader, Sheet, SheetType, SheetVisible, Xls, Xlsb, Xlsx,
};
use calamine::{CellErrorType::*, DataType};
#[test]
fn any_sheets_xlsx() {
let path = format!(
"{}/examples/file/any_sheets.xlsx",
env!("CARGO_MANIFEST_DIR")
);
println!("{}", &path);
let mut workbook: Xlsx<_> = open_workbook(path).unwrap();
let range = workbook.worksheet_range("Visible").unwrap();
let total_cells = range.get_size().0 * range.get_size().1;
let non_empty_cells: usize = range.used_cells().count();
println!(
"Found {} cells in 'Sheet1', including {} non empty cells",
total_cells, non_empty_cells
);
// alternatively, we can manually filter rows
assert_eq!(
non_empty_cells,
range
.rows()
.flat_map(|r| r.iter().filter(|&c| c != &DataType::Empty))
.count()
);
}
}
CSV 读写
CSV(Comma-Separated Values)是最简单的结构化文本格式,用逗号分隔各列数据。
use csv::{Reader, Writer, ReaderBuilder, WriterBuilder};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct RocketRecord {
#[serde(rename = "发射序号")]
serial_no: u32,
#[serde(rename = "发射日期")]
launch_date: String,
#[serde(rename = "发射地点")]
launch_site: String,
#[serde(rename = "运载火箭")]
rocket: String,
#[serde(rename = "卫星/航天器")]
satellite: String,
}
/// 写入CSV文件
fn write_csv() -> Result<(), Box<dyn std::error::Error>> {
let mut wtr = WriterBuilder::new()
.has_headers(true)
.from_path("rocket_records.csv")?;
let records = vec![
RocketRecord {
serial_no: 1,
launch_date: "2024-01-17".to_string(),
launch_site: "酒泉卫星发射中心".to_string(),
rocket: "长征二号F".to_string(),
satellite: "天舟七号".to_string(),
},
RocketRecord {
serial_no: 2,
launch_date: "2024-03-21".to_string(),
launch_site: "文昌航天发射场".to_string(),
rocket: "长征八号".to_string(),
satellite: "鹊桥二号".to_string(),
},
];
for record in &records {
wtr.serialize(record)?;
}
wtr.flush()?;
println!("CSV 文件写入成功!");
Ok(())
}
/// 读取CSV文件
fn read_csv() -> Result<(), Box<dyn std::error::Error>> {
let mut rdr = ReaderBuilder::new()
.flexible(true)
.from_path("rocket_records.csv")?;
for result in rdr.deserialize() {
let record: RocketRecord = result?;
println!("{:?}", record);
}
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
write_csv()?;
println!("--- 读取CSV ---");
read_csv()?;
Ok(())
}
Cargo.toml 依赖:
[dependencies]
csv = "1.3"
serde = { version = "1", features = ["derive"] }
JSON 序列化与反序列化
JSON(JavaScript Object Notation)是当今最流行的数据交换格式。在 Rust 生态中,serde_json 是处理 JSON 的事实标准。
use serde::{Deserialize, Serialize};
use serde_json::{json, Value, from_str, to_string_pretty};
#[derive(Debug, Serialize, Deserialize)]
struct Province {
code: String,
name: String,
level: u8,
#[serde(skip_serializing_if = "Option::is_none")]
parent_code: Option<String>,
}
/// 序列化:Rust 结构体 → JSON 字符串
fn serialize_example() -> Result<(), Box<dyn std::error::Error>> {
let provinces = vec![
Province {
code: "110000".to_string(),
name: "北京市".to_string(),
level: 1,
parent_code: None,
},
Province {
code: "110101".to_string(),
name: "东城区".to_string(),
level: 2,
parent_code: Some("110000".to_string()),
},
];
// 序列化为紧凑JSON
let json_str = serde_json::to_string(&provinces)?;
println!("紧凑JSON: {}", json_str);
// 序列化为美化JSON
let json_pretty = serde_json::to_string_pretty(&provinces)?;
println!("美化JSON:\n{}", json_pretty);
// 动态构建JSON
let data = json!({
"type": "FeatureCollection",
"features": [
{
"type": "Feature",
"properties": { "name": "北京市", "code": "110000" },
"geometry": { "type": "Point", "coordinates": [116.4, 39.9] }
}
]
});
println!("动态JSON: {}", data);
Ok(())
}
/// 反序列化:JSON 字符串 → Rust 结构体
fn deserialize_example() -> Result<(), Box<dyn std::error::Error>> {
let json_data = r#"
[
{"code": "440000", "name": "广东省", "level": 1},
{"code": "440100", "name": "广州市", "level": 2, "parent_code": "440000"}
]
"#;
let provinces: Vec<Province> = from_str(json_data)?;
for p in &provinces {
println!("{} - {} (level: {})", p.code, p.name, p.level);
}
// 反序列化为动态 Value
let v: Value = from_str(json_data)?;
if let Some(arr) = v.as_array() {
for item in arr {
println!("名称: {}", item["name"]);
}
}
Ok(())
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("=== 序列化 ===");
serialize_example()?;
println!("\n=== 反序列化 ===");
deserialize_example()?;
Ok(())
}
Cargo.toml 依赖:
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"
PDF / OFD 格式简介
- PDF (Portable Document Format) 可携带文件格式,由 Adobe 公司于1992年发布,已成为全球通用的文档交换标准。
- OFD 版式文档国家标准,是中国自主研发的电子文件格式,旨在替代 PDF 在政务和电子公文领域的应用。
Pandas vs Polars
| 特性 | Polars (Rust) | Pandas (Python) |
|---|---|---|
| 语言 | Rust | Python |
| 并行执行 | 原生多线程 | 单线程(部分操作可用并行) |
| 惰性求值 | 支持 | 不支持 |
| 内存占用 | 较低(Arrow格式) | 较高 |
| 性能 | 极高 | 中等 |
| 生态成熟度 | 快速增长中 | 非常成熟 |
| 学习曲线 | 较平缓 | 中等 |
10.7 万物编码的哲学
DIKW 金字塔
从数据到智慧的递进,可以用 DIKW 金字塔来描述:
$$ \text{Data(数据)} \xrightarrow{\text{赋予含义}} \text{Information(信息)} \xrightarrow{\text{总结规律}} \text{Knowledge(知识)} \xrightarrow{\text{洞察本质}} \text{Wisdom(智慧)} $$
| 层级 | 含义 | 编码示例 |
|---|---|---|
| Data | 原始事实,无上下文 | 42, "hello", 0xFF |
| Information | 有组织、有意义的数据 | {"temp": 42, "unit": "°C"} |
| Knowledge | 信息之间的关系与模式 | if temp > 37 then fever |
| Wisdom | 运用知识做出判断 | 该患者需要就医 |
编码贯穿了 DIKW 的每一个层级:
- 数据层:二进制编码(ASCII、UTF-8、IEEE 754)
- 信息层:结构化编码(JSON、XML、CSV)
- 知识层:逻辑编码(规则引擎、知识图谱)
- 智慧层:这一层或许已经超出了“编码“的范畴——智慧涉及直觉、经验和创造力,这些是否可以被编码,至今仍是哲学与人工智能领域的开放问题。
编码的边界:什么不能被编码?
尽管“万物皆可编码“是一个强大的理念,但编码并非万能。以下是一些编码的边界:
- 主观体验(Qualia):你看到的“红色“和我看到的“红色“是同一种感觉吗?这种主观体验难以被精确编码。
- 情感与直觉:虽然可以用文字描述情感,但情感的微妙之处(如“淡淡的忧伤“)很难被完全编码。
- 无限精度:实数 $\pi$ 的精确值无法被有限编码完全表示,只能用近似值。
- 创造性思维:编码可以表达规则,但真正的创造力(如贝多芬的交响曲)似乎超越了纯粹的编码。
- 意识本身:意识是否可以被编码为算法?这是心智哲学中最深刻的未解之谜之一。
正如哲学家维特根斯坦所言:“对于不可言说之物,必须保持沉默。“编码的边界,或许正是人类智慧与机器智能的分界线。
10.8 总结
本章从“万物皆可编码“的理念出发,系统介绍了编码的各个层次。
编码层次对比
| 层次 | 核心概念 | 编码方式 | Rust 体现 |
|---|---|---|---|
| 数据 | 比特流 | 二进制 0/1 | u8, Vec<u8> |
| 类型 | 语义化数据 | IEEE 754, UTF-8, 补码 | i32, f64, char, String |
| 对象 | 数据+行为 | struct/enum/trait | struct, impl, trait |
| 文件 | 持久化存储 | 文件格式规范 | std::fs, File |
| 协议 | 标准化交换 | 通信协议规范 | HTTP, TCP, JSON |
ID 方案对比
| 方案 | 长度 | 有序性 | 分布式 | 性能 | 适用场景 |
|---|---|---|---|---|---|
| 自增ID | 4~8字节 | 严格有序 | 需中心化 | 极高 | 单体数据库 |
| UUID v4 | 36字符 | 无序 | 天然支持 | 中等 | 通用标识 |
| 雪花算法 | 8字节 | 趋势递增 | 需分配ID | 高 | 分布式主键 |
| nanoid | 21字符 | 无序 | 天然支持 | 高 | URL/前端 |
10.9 练习题
练习1:编写一个 Rust 函数,接收一个 u8 值,分别输出其二进制表示、八进制表示和十六进制表示,并解释补码如何表示负数。
练习2:使用 struct 和 trait 实现一个简单的“动物“体系:定义 Animal trait(包含 speak 方法),并为 Dog、Cat、Bird 三个结构体分别实现该 trait。
练习3:编写一个函数,验证给定的18位身份证号码是否合法。要求支持批量验证,并统计合法与不合法的数量。
练习4:使用 uuid crate 生成 1000 个 UUID v4,统计其中以 0 开头的 UUID 数量,并验证其是否接近理论概率 $1/16$。
练习5:实现一个简化版的雪花算法 ID 生成器,要求支持自定义机器ID,并验证生成的 ID 是否全局唯一(生成 10000 个 ID 检查是否有重复)。
练习6:使用 csv 和 serde crate,读取一个 CSV 文件,筛选出满足特定条件的记录,并将结果写入新的 CSV 文件。
练习7:使用 serde_json 实现以下功能:将一个 Rust 结构体序列化为 JSON 字符串,再从 JSON 字符串反序列化为 Rust 结构体,验证数据的完整性。
练习8:思考题:有人说“一切皆可编码“,但也有人认为“意识不可编码“。请结合本章内容和 DIKW 金字塔,谈谈你对“编码的边界“的看法。字数不少于300字。