编程语言研究书库

标准库 — 数据结构、文件 I/O 与格式化

标准库 — 数据结构、文件 I/O 与格式化

这一章是标准库巡礼,一条主线贯穿:std 没有一件东西替你藏内存。 ArrayList 递 allocator、HashMap 要 deinit、读文件要自己 free、 格式化字符串要 allocPrint + free——第 9 章的手艺,在这里变成工具。 四个展区:容器、文件 I/O、格式化、杂项(mem/随机/时间/排序)。

1. 这一章讲什么

第 9 章你用裸 alloc 手搓了字符串拼接(catAlloc); 这一章告诉你:那种活儿 std 里有现成的(ArrayList、std.mem), 但用法哲学不变——内存的账,每一笔记在你头上。 原书给 ArrayList 的判词就是全章的判词:「Memory is a resource, not a right.」1

2. 顶层全景与主线例子

  容器   ArrayList(动态数组)│ HashMap(哈希表;字符串键用 StringHashMap)
  文件   openFile → readToEndAlloc → free;createFile → writeAll → close → sync
  文本   std.fmt.format / print / allocPrint + {s} {d} {x} {d:0>4}
  杂项   std.mem(copy/set/eql/replace/split/tokenize)│ rand(伪随机 vs 加密随机)
         │ time(时间戳/Timer,没有 DateTime)│ sort.block(原地、确定性)

  图说:每个展区第一句都是「你要什么自己说,内存自己还」。

主线例子(贯穿容器与文件两区):打开一个文件 → 读进 ArrayList → 逐行处理 → 写回新文件 → sync → 关。每一步旁边是具体函数和「谁 free」。

3. 展区一:容器

3.1 ArrayList:动态数组,道德立场 Included

  var list = ArrayList(u8).init(allocator);   // 交出 allocator = 签下租约
  try list.append(42);                         // 连追加一个数都可能 OOM,所以是 try
  defer list.deinit();                         // 不还 = 泄漏

init 只交租约不划地——第一个字节都不会提前分配; append 可能失败(内存不够),错误显式上抛2。 配套的容量操作为性能服务:ensureTotalCapacity(1000)(先圈地)、 ensureTotalCapacityPrecise(精确到字节,嵌入式友好)、 shrinkAndFree(裁掉多租的)3。 两个风格化 API:appendAssumeCapacity(我保证有容量,炸了算我的—— 缓冲区溢出即崩溃,是性能换责任的明牌);ArrayListUnmanaged (结构体里不藏 allocator,每次操作手动传——给内核/WebAssembly 那种「allocator 像违禁品一样流通」的场景)4。

插入与删除的代价账(原书讲得很实在):insert(0, 99) 在头部插入是 O(n), 后面的元素全体右移,万级列表上就是「新型延迟」5;删除两兄弟: orderedRemove(保序,像排队)和 swapRemove(把末尾元素拽进空档, 快、乱序,实时系统的选择)6。要给 C 接口用? toOwnedSliceSentinel(0) 吐出带哨兵的切片——「想要哨兵?给。但生命周期归你管」7。

3.2 HashMap:酒馆斗殴与墓碑

  var map = AutoHashMap(u32, u32).init(allocator);
  defer map.deinit();            // 忘了 = 囤内存的龙
  try map.put(42, 9001);
  if (map.get(42)) |val| { ... }      // 找不到返回 null → optional 解包
  _ = map.remove(42);                  // 删,但留下「墓碑」

AutoHashMap 自动给键算哈希,但自定义键类型要提供两个「咒语」: hash(键 → 数字)和 eql(两键是否相同)8。 字符串键有专门的坑:[]const u8 做键直接编译错误—— 字符串可以按内容哈希、也可以按地址哈希,Zig 拒绝替你猜;用 StringHashMap, 它按内容哈希,像图书管理员9。

**墓碑(tombstone)**机制:remove 不真擦除,留个标记—— 因为 Zig 用线性探测(linear probing,冲突时逐个看下一个槽位的算法), 需要墓碑防止搜索提前终止。墓碑堆积会让表变成「闹鬼墓地」,性能劣化; map.rehash() 一次大扫除10。高频增删的负载下定期 rehash,CPU 缓存会感谢你11。

3.3 主线例子走查(读 → 改 → 写)

  ① const file = try std.fs.cwd().openFile("in.txt", .{});
  ② defer file.close();
  ③ const data = try file.readToEndAlloc(allocator, 1 << 20);   // 上限 1 MB
  ④ defer allocator.free(data);
  ⑤ var list = std.ArrayList(u8).init(allocator);
  ⑥ defer list.deinit();
  ⑦ try list.appendSlice(data);          // 或逐行处理:分割、过滤、重组
  ⑧ const out = try std.fs.cwd().createFile("out.txt", .{});
  ⑨ defer out.close();
  ⑩ try out.writeAll(list.items);
  ⑪ try out.sync();                       // 关键写入后落盘

每笔内存的去向:③ 读出的 data 归你(④ 还);⑤ 的 list 归你(⑥ 还); ⑧ 的文件句柄归你(⑨ 关)。五个 defer,五个责任点——这就是第 9 章 「init/deinit 对」在 std 用户侧的 everyday 形态。

4. 展区二:文件 I/O

4.1 基本功

  • 开:std.fs.cwd().openFile("secret_plans.txt", .{ .read = true })—— 文件不存在、权限不足,全是显式错误,try/catch 伺候12;
  • 读:readToEndAlloc(allocator, 上限) 一口吞到文件尾(设上限防爆内存), 大文件改 buffered reader 按块读:readUntilDelimiterOrEof(&buffer, '\n') 逐行13;
  • 写:createFile + writeAll;写完 sync()(落盘;syncDataOnly 只刷数据), 防止「断电时数据还在缓冲区里」14;
  • 定位:seekTo(0) 倒带、getPos() 问位置、seekBy(-10) 相对跳—— 解析二进制格式的基本功15;
  • 权限:chmod(0o644)(属主读写、众人只读),stat() 查状态16;
  • 硬链接:link(a, b)——两个文件名同一个数据体,删一个另一个还在; 备份系统爱用17。

4.2 失落卷轴(std.fs 的进阶面,原书专节)

招干什么注意
标志位.append(追加)、.exclusive(已存在则失败)、.truncate(打开即清空)、.read+.write 组合「与文件系统的合同」
mmapstd.os.mmap 把文件映射成内存,改内存即改文件杂耍手雷:一步踏错就是段错误喷泉18
磁盘情报FileSystemStats 查剩余空间嵌入式写固件前必查19
符号链接symLink 建链;链可能悬空,用前先 stat 目标「It's a trap!」20
EINTR 重试writeWithRetry:遇 error.Interrupted 就 continue网络文件系统、娇贵硬件的必备礼数21

5. 展区三:std.fmt 格式化

三件套:format(writer, 串, 参数)(写给任意 writer)、 std.debug.print(直出 stdout,侦察兵)、 allocPrint(造一个堆上的字符串,用完 free)22。 占位符带类型:{s} 字符串、{d} 整数、{x}/{X} 十六进制、{b} 二进制、 {d:.3} 小数点后三位、{d:0>4} 右对齐零填充到 4 位、{d:*<4} 左对齐星号填充23。 类型不匹配是编译错误——原书的测验:{d} 配 "5"(字符串)直接不过, Zig 没有含糊的 %v24。

自定义类型的格式化:给结构体写一个 format 方法, 它就能进任何格式串——Monster{name="Lifetime Leak", hp=9999} 打印成 Lifetime Leak (HP: 9999)25。三个卖点(原书):编译期检查、 零隐藏分配(嵌入式/WASM 友好)、完全显式的控制26。

6. 展区四:随机、时间、排序、std.mem

6.1 随机:两把武器,别用错

std.Random.DefaultPrngstd.crypto.random
本质确定性伪随机,种子固定则序列固定操作系统级熵(/dev/urandom 一系)
用途游戏、模拟、可复现调试密码、令牌、「可预测即失败」处
坑种子可猜 = 「随机」可猜杀鸡用牛刀,慢

原书的双关:别召唤克苏鲁来开泡菜坛子27。

6.2 时间:没有 DateTime,是故意的

timestamp()(秒,1970 起)、nanoTimestamp()(纳秒,基准点随意,测时专用)、 Timer(起表/读数,纳秒精度)28。算 30 天试用:now + 30*24*60*60—— 原书顺便道歉:对不起二月,你很特别(天数不齐)。 溢出警报:大时长加法用饱和运算 +|(到顶就封顶,不崩不绕)29。 为什么不给 DateTime?时区、闰年、夏令时全是政治包袱,Zig 选择不给一刀切答案30。

6.3 排序与切分

std.sort.block(T, &arr, {}, comptime asc(T))——原地、无分配、确定性 (同样输入永远同样输出);自定义比较器就是一个 lessThan 函数, 降序写 a.score > b.score31。 std.mem.split(按定界符切,保留空段:CSV 的 "Zig,Rust,,C" 切出 4 段) 与 std.mem.tokenize(跳过空段、修剪空白,CLI 参数友好)的分工, 是解析代码的第一道口令32。

6.4 std.mem 速查(原书按用途组织)

copy(长度必须相等,防溢出的 memcpy)、set(填充/擦除敏感数据)、 eql(字节级相等,校验哈希用)、replace(堆上新串,记得 free)、 sliceAsBytes/bytesAsSlice(类型切片 ↔ 字节切片,序列化)、 alignForward(对齐到 16,SIMD 前用)、readInt/writeInt(显式大端/小端,解析 PNG 头)、 split/tokenize(见上)33。 原书给的挑战解(64 字节分块 + 原地替换 0xFF→0x00 + Timer 计时) 是这些工具的组合拳样板34。

7. 作者的判断与证据

可核的: 全部 API 签名、输出样例(Health: 0005、0xff、PI: 3.142)、 墓碑与 rehash 机制、EINTR 重试模式——都可跑可验231021。

立场: 全章修辞是「std 是道德立场不是工具箱」1;这是有倾向的写法 (std 也有便捷面,比如 AutoHashMap 的「Auto」),但方向和第 1 章一致,不算夸张。

8. 边界与局限

缺什么说明
ArrayList 的增长策略没讲翻倍还是 1.5 倍、均摊复杂度,全章只字未提
HashMap 的负载因子/扩容时机没讲rehash 何时自动发生,没有
文件 I/O 没有讲目录遍历、watchwalk、事件监听留给第 13 章 FileGuard 现学
std.fmt 的性能与 comptime 格式化细节浅comptime 格式串的编译期检查机制没展开
没有 JSON/序列化std.json 是标准库重头之一,本章完全没出现
时间没有讲单调钟 vs 墙上钟的区别Timer 是单调钟这一关键性质没点名(只说了「arbitrary point」)

9. 可带走的

主线例子一行: openFile → readToEndAlloc(带上限)→ free; ArrayList.init → append → deinit;createFile → writeAll → close → sync; 五个 defer 对应五笔账。

  1. ArrayList:init 递 allocator、append 要 try、deinit 必写; insert(0,x) 是 O(n);swapRemove 快且乱序、orderedRemove 慢且保序;
  2. HashMap:字符串键用 StringHashMap;remove 留墓碑,堆积了 rehash();
  3. 文件:读带上限、写后 sync()、句柄 defer close;进阶面记住 mmap(危险)、FileSystemStats(嵌入式)、symlink 会悬空、EINTR 要重试;
  4. std.fmt:{s}/{d}/{x} 类型分明,不匹配编译报错;{d:0>4} 对齐填充; 自定义类型写个 format 方法就能进格式串;
  5. 随机:可复现用 DefaultPrng(种子 42),安全用 std.crypto.random;
  6. 时间:没有 DateTime 是有意的;大时长加法用饱和 +| 防溢出;
  7. 切分:保留空段用 split,跳过空段用 tokenize;
  8. 排序:sort.block 原地、确定性,比较器自己写;
  9. std.mem:copy 长度强制相等(防溢出 memcpy)、eql 比字节、 sliceAsBytes 做序列化、readInt 显式端序;
  10. 全章一句话:std 的每一件工具,都把第 9 章的内存责任原样交回你手上。

10. 原文地图

主题原书章原文位置
ArrayList init/append/契约Chapter 10: The Standard Librarytext/17-ch10-chapter-10-the-standard-library.txt:41(搜「ArrayList(u8).init」) · text/17-ch10-chapter-10-the-standard-library.txt:50(搜「list.append(42)」)
容量三件套与 UnmanagedChapter 10: The Standard Librarytext/17-ch10-chapter-10-the-standard-library.txt:61(搜「ensureTotalCapacity」) · text/17-ch10-chapter-10-the-standard-library.txt:75(搜「ArrayListUnmanaged」)
insert O(n) 与删除两兄弟Chapter 10: The Standard Librarytext/17-ch10-chapter-10-the-standard-library.txt:86(搜「Prepends 99」) · text/17-ch10-chapter-10-the-standard-library.txt:91(搜「swapRemove」)
「资源不是权利」Chapter 10: The Standard Librarytext/17-ch10-chapter-10-the-standard-library.txt:106(搜「Memory is a resource」)
HashMap 基本操作Chapter 10: The Standard Librarytext/17-ch10-chapter-10-the-standard-library.txt:135(搜「AutoHashMap(u32, u32).init」) · text/17-ch10-chapter-10-the-standard-library.txt:154(搜「Tombstone」)
hash/eql 与 StringHashMapChapter 10: The Standard Librarytext/17-ch10-chapter-10-the-standard-library.txt:173(搜「compile error」) · text/17-ch10-chapter-10-the-standard-library.txt:177(搜「StringHashMap」)
墓碑与 rehashChapter 10: The Standard Librarytext/17-ch10-chapter-10-the-standard-library.txt:180(搜「tombstone」) · text/17-ch10-chapter-10-the-standard-library.txt:185(搜「map.rehash」)
openFile 与 defer closeChapter 10: The Standard Librarytext/17-ch10-chapter-10-the-standard-library.txt:242(搜「secret_plans.txt」)
readToEndAlloc 与缓冲读Chapter 10: The Standard Librarytext/17-ch10-chapter-10-the-standard-library.txt:270(搜「readToEndAlloc」) · text/17-ch10-chapter-10-the-standard-library.txt:324(搜「readUntilDelimiterOrEof」)
createFile/writeAll/sync/seekChapter 10: The Standard Librarytext/17-ch10-chapter-10-the-standard-library.txt:284(搜「createFile」) · text/17-ch10-chapter-10-the-standard-library.txt:335(搜「sync」) · text/17-ch10-chapter-10-the-standard-library.txt:345(搜「seekTo(0)」)
二进制 I/O extern structChapter 10: The Standard Librarytext/17-ch10-chapter-10-the-standard-library.txt:302(搜「extern struct」)
chmod 与硬链接Chapter 10: The Standard Librarytext/17-ch10-chapter-10-the-standard-library.txt:369(搜「0o644」) · text/17-ch10-chapter-10-the-standard-library.txt:384(搜「link」)
失落卷轴:mmap/stats/symlink/EINTRChapter 10: The Standard Librarytext/17-ch10-chapter-10-the-standard-library.txt:436(搜「mmap」) · text/17-ch10-chapter-10-the-standard-library.txt:452(搜「FileSystemStats」) · text/17-ch10-chapter-10-the-standard-library.txt:460(搜「symLink」) · text/17-ch10-chapter-10-the-standard-library.txt:472(搜「writeWithRetry」)
format/print/allocPrintChapter 10: The Standard Librarytext/17-ch10-chapter-10-the-standard-library.txt:517(搜「std.fmt.format」) · text/17-ch10-chapter-10-the-standard-library.txt:537(搜「allocPrint」)
对齐填充与进制Chapter 10: The Standard Librarytext/17-ch10-chapter-10-the-standard-library.txt:547(搜「{d:0>4}」) · text/17-ch10-chapter-10-the-standard-library.txt:553(搜「0x{x}」)
自定义 formatChapter 10: The Standard Librarytext/17-ch10-chapter-10-the-standard-library.txt:578(搜「pub fn format」)
{d} 配 "5" 的测验Chapter 10: The Standard Librarytext/17-ch10-chapter-10-the-standard-library.txt:599(搜「unread messages」)
两种随机Chapter 10: The Standard Librarytext/17-ch10-chapter-10-the-standard-library.txt:641(搜「DefaultPrng.init(42)」) · text/17-ch10-chapter-10-the-standard-library.txt:653(搜「randomBytes」)
时间 API 与 +Chapter 10: The Standard Library
split vs tokenizeChapter 10: The Standard Librarytext/17-ch10-chapter-10-the-standard-library.txt:696(搜「std.mem.split」) · text/17-ch10-chapter-10-the-standard-library.txt:705(搜「std.mem.tokenize」)
sort.block 与比较器Chapter 10: The Standard Librarytext/17-ch10-chapter-10-the-standard-library.txt:721(搜「sort.block」) · text/17-ch10-chapter-10-the-standard-library.txt:733(搜「sort by score」)
std.mem 速查Chapter 10: The Standard Librarytext/17-ch10-chapter-10-the-standard-library.txt:771(搜「std.mem.copy」) · text/17-ch10-chapter-10-the-standard-library.txt:804(搜「sliceAsBytes」) · text/17-ch10-chapter-10-the-standard-library.txt:819(搜「readInt」)

Footnotes

  1. 出处:「Chapter 10: The Standard Library」第 105–115 段(text/17-ch10-chapter-10-the-standard-library.txt:106,搜「Memory is a resource」;text/17-ch10-chapter-10-the-standard-library.txt:115,搜「every allocation a debt」)。 ↩ ↩2

  2. 出处:同章第 37–59 段(text/17-ch10-chapter-10-the-standard-library.txt:41,搜「ArrayList(u8).init」;text/17-ch10-chapter-10-the-standard-library.txt:52,搜「Out of memory」)。 ↩

  3. 出处:同章第 60–71 段(text/17-ch10-chapter-10-the-standard-library.txt:61,搜「ensureTotalCapacity」;text/17-ch10-chapter-10-the-standard-library.txt:66,搜「Precise」;text/17-ch10-chapter-10-the-standard-library.txt:69,搜「shrinkAndFree」)。 ↩

  4. 出处:同章第 54–59 与第 72–81 段(text/17-ch10-chapter-10-the-standard-library.txt:56,搜「appendAssumeCapacity」;text/17-ch10-chapter-10-the-standard-library.txt:75,搜「ArrayListUnmanaged」)。 ↩

  5. 出处:同章第 83–93 段(text/17-ch10-chapter-10-the-standard-library.txt:86,搜「Prepends 99」;text/17-ch10-chapter-10-the-standard-library.txt:89,搜「new form of latency」)。 ↩

  6. 出处:同章第 91–93 段(text/17-ch10-chapter-10-the-standard-library.txt:91,搜「swapRemove」)。 ↩

  7. 出处:同章第 95–103 段(text/17-ch10-chapter-10-the-standard-library.txt:97,搜「toOwnedSliceSentinel」)。 ↩

  8. 出处:同章第 159–178 段(text/17-ch10-chapter-10-the-standard-library.txt:163,搜「Turn keys into numbers」;text/17-ch10-chapter-10-the-standard-library.txt:165,搜「impostors」)。 ↩

  9. 出处:同章第 173–178 段(text/17-ch10-chapter-10-the-standard-library.txt:173,搜「compile error」;text/17-ch10-chapter-10-the-standard-library.txt:177,搜「StringHashMap」)。 ↩

  10. 出处:同章第 179–191 段(text/17-ch10-chapter-10-the-standard-library.txt:180,搜「tombstone」;text/17-ch10-chapter-10-the-standard-library.txt:181,搜「linear probing」;text/17-ch10-chapter-10-the-standard-library.txt:185,搜「map.rehash」)。 ↩ ↩2

  11. 出处:同章第 188–191 段(text/17-ch10-chapter-10-the-standard-library.txt:190,搜「CPU cache」)。 ↩

  12. 出处:同章第 233–262 段(text/17-ch10-chapter-10-the-standard-library.txt:242,搜「secret_plans.txt」)。 ↩

  13. 出处:同章第 265–276 与 319–328 段(text/17-ch10-chapter-10-the-standard-library.txt:270,搜「readToEndAlloc」;text/17-ch10-chapter-10-the-standard-library.txt:324,搜「readUntilDelimiterOrEof」)。 ↩

  14. 出处:同章第 280–339 段(text/17-ch10-chapter-10-the-standard-library.txt:284,搜「createFile」;text/17-ch10-chapter-10-the-standard-library.txt:335,搜「sync」)。 ↩

  15. 出处:同章第 343–351 段(text/17-ch10-chapter-10-the-standard-library.txt:345,搜「seekTo(0)」)。 ↩

  16. 出处:同章第 362–376 段(text/17-ch10-chapter-10-the-standard-library.txt:369,搜「0o644」)。 ↩

  17. 出处:同章第 380–387 段(text/17-ch10-chapter-10-the-standard-library.txt:384,搜「link」)。 ↩

  18. 出处:同章第 433–448 段(text/17-ch10-chapter-10-the-standard-library.txt:436,搜「mmap」;text/17-ch10-chapter-10-the-standard-library.txt:448,搜「segmentation fault fountain」)。 ↩

  19. 出处:同章第 450–456 段(text/17-ch10-chapter-10-the-standard-library.txt:452,搜「FileSystemStats」)。 ↩

  20. 出处:同章第 457–468 段(text/17-ch10-chapter-10-the-standard-library.txt:460,搜「symLink」;text/17-ch10-chapter-10-the-standard-library.txt:468,搜「Always check」)。 ↩

  21. 出处:同章第 470–483 段(text/17-ch10-chapter-10-the-standard-library.txt:472,搜「writeWithRetry」;text/17-ch10-chapter-10-the-standard-library.txt:475,搜「Interrupted」)。 ↩ ↩2

  22. 出处:同章第 509–538 段(text/17-ch10-chapter-10-the-standard-library.txt:517,搜「std.fmt.format」;text/17-ch10-chapter-10-the-standard-library.txt:537,搜「allocPrint」)。 ↩

  23. 出处:同章第 542–563 段(text/17-ch10-chapter-10-the-standard-library.txt:547,搜「{d:0>4}」;text/17-ch10-chapter-10-the-standard-library.txt:553,搜「0x{x}」;text/17-ch10-chapter-10-the-standard-library.txt:560,搜「{d:.3}」)。 ↩ ↩2

  24. 出处:同章第 597–608 段(text/17-ch10-chapter-10-the-standard-library.txt:599,搜「unread messages」)。 ↩

  25. 出处:同章第 568–593 段(text/17-ch10-chapter-10-the-standard-library.txt:578,搜「pub fn format」;text/17-ch10-chapter-10-the-standard-library.txt:589,搜「Boss:」)。 ↩

  26. 出处:同章第 610–616 段(text/17-ch10-chapter-10-the-standard-library.txt:612,搜「Compile-time checks」)。 ↩

  27. 出处:同章第 634–655 段(text/17-ch10-chapter-10-the-standard-library.txt:641,搜「DefaultPrng.init(42)」;text/17-ch10-chapter-10-the-standard-library.txt:653,搜「randomBytes」;text/17-ch10-chapter-10-the-standard-library.txt:651,搜「pickle jar」)。 ↩

  28. 出处:同章第 658–670 段(text/17-ch10-chapter-10-the-standard-library.txt:667,搜「timestamp()」;text/17-ch10-chapter-10-the-standard-library.txt:668,搜「nanoTimestamp」;text/17-ch10-chapter-10-the-standard-library.txt:670,搜「std.time.Timer」)。 ↩

  29. 出处:同章第 677–681 段(text/17-ch10-chapter-10-the-standard-library.txt:679,搜「saturating math」)。 ↩

  30. 出处:同章第 662–665 段(text/17-ch10-chapter-10-the-standard-library.txt:664,搜「political baggage」)。 ↩

  31. 出处:同章第 717–741 段(text/17-ch10-chapter-10-the-standard-library.txt:721,搜「sort.block」;text/17-ch10-chapter-10-the-standard-library.txt:740,搜「deterministic」)。 ↩

  32. 出处:同章第 689–714 与 820–836 段(text/17-ch10-chapter-10-the-standard-library.txt:696,搜「std.mem.split」;text/17-ch10-chapter-10-the-standard-library.txt:705,搜「std.mem.tokenize」)。 ↩

  33. 出处:同章第 758–836 段(text/17-ch10-chapter-10-the-standard-library.txt:771,搜「std.mem.copy」;text/17-ch10-chapter-10-the-standard-library.txt:784,搜「std.mem.eql」;text/17-ch10-chapter-10-the-standard-library.txt:804,搜「sliceAsBytes」;text/17-ch10-chapter-10-the-standard-library.txt:819,搜「readInt」)。 ↩

  34. 出处:同章第 838–861 段(text/17-ch10-chapter-10-the-standard-library.txt:841,搜「std.time.Timer.start」)。 ↩

On this page