Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第十章 万物皆可编码

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, i1281/2/4/8/16 字节有符号整数
整数u8, u16, u32, u64, u1281/2/4/8/16 字节无符号整数
浮点f32, f644/8 字节IEEE 754 浮点数
布尔bool1 字节truefalse
字符char4 字节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 就是网络通信中对象的身份证。

对象标识符 ObjectIdentifier OID

OID 采用点分十进制表示,例如 1.3.6.1.5.5.7.1.1 表示“权威信息访问“(Authority Information Access)证书扩展。

Rust 中的面向对象

Rust 并非传统的面向对象语言,但通过 structenumtrait 可以优雅地实现面向对象的核心思想:

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]$。校验码映射表为:

余数012345678910
校验码10X98765432
#![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_REGEXWEIGHTVERIFY_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地址
v3MD5 哈希(命名空间+名称)确定性生成,相同输入产生相同UUID
v4随机数最常用,完全随机
v5SHA-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年
机器ID10 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方案对比

方案长度生成方式有序性分布式支持适用场景
自增ID4~8字节数据库自增严格有序需要中心化单体数据库主键
UUID v436字符(含连字符)随机数无序天然支持通用唯一标识
UUID v136字符(含连字符)时间戳+MAC时间有序天然支持需要排序的场景
雪花算法8字节(i64)时间戳+机器ID+序列号趋势递增需分配机器ID分布式系统主键
nanoid21字符(可配置)密码学随机无序天然支持URL、短链接、前端

10.5 一切皆文件

Unix 哲学:一切皆文件

Unix 操作系统有一个优雅的设计哲学——“一切皆文件”(Everything is a file)。在 Unix/Linux 系统中,无论是普通文件、目录、设备(如 /dev/null/dev/tty)、管道、套接字,还是 /proc 下的进程信息,都可以通过统一的文件操作接口(openreadwriteclose)来访问。

这种设计的优势在于:

  • 统一接口:所有资源使用相同的 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)
语言RustPython
并行执行原生多线程单线程(部分操作可用并行)
惰性求值支持不支持
内存占用较低(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)
  • 知识层:逻辑编码(规则引擎、知识图谱)
  • 智慧层:这一层或许已经超出了“编码“的范畴——智慧涉及直觉、经验和创造力,这些是否可以被编码,至今仍是哲学与人工智能领域的开放问题。

编码的边界:什么不能被编码?

尽管“万物皆可编码“是一个强大的理念,但编码并非万能。以下是一些编码的边界:

  1. 主观体验(Qualia):你看到的“红色“和我看到的“红色“是同一种感觉吗?这种主观体验难以被精确编码。
  2. 情感与直觉:虽然可以用文字描述情感,但情感的微妙之处(如“淡淡的忧伤“)很难被完全编码。
  3. 无限精度:实数 $\pi$ 的精确值无法被有限编码完全表示,只能用近似值。
  4. 创造性思维:编码可以表达规则,但真正的创造力(如贝多芬的交响曲)似乎超越了纯粹的编码。
  5. 意识本身:意识是否可以被编码为算法?这是心智哲学中最深刻的未解之谜之一。

正如哲学家维特根斯坦所言:“对于不可言说之物,必须保持沉默。“编码的边界,或许正是人类智慧与机器智能的分界线。

10.8 总结

本章从“万物皆可编码“的理念出发,系统介绍了编码的各个层次。

编码层次对比

层次核心概念编码方式Rust 体现
数据比特流二进制 0/1u8, Vec<u8>
类型语义化数据IEEE 754, UTF-8, 补码i32, f64, char, String
对象数据+行为struct/enum/traitstruct, impl, trait
文件持久化存储文件格式规范std::fs, File
协议标准化交换通信协议规范HTTP, TCP, JSON

ID 方案对比

方案长度有序性分布式性能适用场景
自增ID4~8字节严格有序需中心化极高单体数据库
UUID v436字符无序天然支持中等通用标识
雪花算法8字节趋势递增需分配ID分布式主键
nanoid21字符无序天然支持URL/前端

10.9 练习题

练习1:编写一个 Rust 函数,接收一个 u8 值,分别输出其二进制表示、八进制表示和十六进制表示,并解释补码如何表示负数。

练习2:使用 structtrait 实现一个简单的“动物“体系:定义 Animal trait(包含 speak 方法),并为 DogCatBird 三个结构体分别实现该 trait。

练习3:编写一个函数,验证给定的18位身份证号码是否合法。要求支持批量验证,并统计合法与不合法的数量。

练习4:使用 uuid crate 生成 1000 个 UUID v4,统计其中以 0 开头的 UUID 数量,并验证其是否接近理论概率 $1/16$。

练习5:实现一个简化版的雪花算法 ID 生成器,要求支持自定义机器ID,并验证生成的 ID 是否全局唯一(生成 10000 个 ID 检查是否有重复)。

练习6:使用 csvserde crate,读取一个 CSV 文件,筛选出满足特定条件的记录,并将结果写入新的 CSV 文件。

练习7:使用 serde_json 实现以下功能:将一个 Rust 结构体序列化为 JSON 字符串,再从 JSON 字符串反序列化为 Rust 结构体,验证数据的完整性。

练习8:思考题:有人说“一切皆可编码“,但也有人认为“意识不可编码“。请结合本章内容和 DIKW 金字塔,谈谈你对“编码的边界“的看法。字数不少于300字。