buffer: |
  字符串缓冲区库允许对类字符串数据进行高性能操作。

  与 Lua 字符串（常量）不同，字符串缓冲区是可变的 8 位（二进制透明）字符序列。数据可以存储、格式化并编码到字符串缓冲区中，之后再转换、提取或解码。

  便捷的字符串缓冲区 API 简化了常见的字符串处理任务，否则这些任务通常需要创建大量中间字符串。字符串缓冲区通过消除冗余的内存拷贝、对象创建、字符串驻留以及垃圾回收开销来提升性能；与 FFI 库结合时，还能实现零拷贝操作。

  字符串缓冲区库还包含一个用于 Lua 对象的高性能序列化器。

  ## 流式序列化

  在某些场景下，希望对大型数据集进行分片序列化（也称 streaming）。该序列化格式可安全拼接并支持流式：可以把多个编码简单地追加到一个缓冲区，之后再分别解码：

  ```lua
  local buf = buffer.new()
  buf:encode(obj1)
  buf:encode(obj2)
  local copy1 = buf:decode()
  local copy2 = buf:decode()
  ```

  下面展示如何迭代一个流：

  ```lua
  while #buf ~= 0 do
    local obj = buf:decode()
    -- 对 obj 做些处理。
  end
  ```

  由于该序列化格式不会在编码前附加长度信息，网络应用可能还需要同时传输长度。

  该序列化格式主要供 LuaJIT 应用内部使用。序列化数据向上兼容，并可在所有支持的 LuaJIT 平台间移植。

  它是 8 位二进制格式，非人类可读，可能包含嵌入的零字节；并且会原样存储嵌入的 Lua 字符串对象（同样是 8 位透明）。编码后的数据可以安全拼接以用于流式处理，并可在之后按“顶层对象”逐个解码。

  该编码相对紧凑，但优化目标是最大性能而非最小空间占用；它也能很好地被常见的按字节数据压缩算法压缩。

  尽管本文档给出了说明，但该格式明确不打算作为跨语言结构化数据交换的“公共标准”（如 JSON 或 MessagePack）；请不要这样使用。

  规范以无上下文文法给出，以顶层对象为起点。备选项以 `|` 分隔，`*` 表示重复。分组是隐式的，或用 `{…}` 表示。终结符要么是以字节编码的十六进制数，要么带有 `.format` 后缀。

  ```
  object    → nil | false | true
  ```

buf: |
  缓冲区对象是由垃圾回收管理的 Lua 对象。通过 `buffer.new()` 创建后，它可以（也应该）在多个操作中复用。当最后一个对缓冲区对象的引用消失后，它最终会由垃圾回收器释放，包括其分配的缓冲区空间。

  缓冲区以 FIFO（先进先出）数据结构方式工作：数据可以被追加（写入）到缓冲区末尾，也可以从缓冲区头部被消费（读取）；这些操作可以任意混合。

  用于保存字符的缓冲区空间由系统自动管理——按需增长，并会回收已消费的空间。如需更细粒度的控制，可使用 `buffer.new(size)` 与 `buf:free()`。

  单个缓冲区的最大大小与 Lua 字符串的最大大小相同，略低于 2GB。对于超大数据规模，字符串和缓冲区都不是合适的数据结构——应使用 FFI 库直接映射内存或文件，直到操作系统的虚拟内存限制。

string.buffer.data: |
  要写入缓冲区的数据：字符串、数字，或带有 `__tostring` 元方法的对象 `obj`。

buf.put: |
  向缓冲区追加一个字符串 `str`、数字 `num`，或任何带有 `__tostring` 元方法的对象 `obj`。多个参数会按给定顺序依次追加。

  将一个缓冲区追加到另一个缓冲区是可行的，并会在内部进行短路处理，但仍然会发生一次拷贝；更好的做法是合并写入，尽量使用同一个缓冲区完成写入。

buf.putf: |
  将格式化后的参数追加到缓冲区。格式字符串支持与 `string.format()` 相同的选项。

buf.putcdata: |
  将 FFI cdata 对象指向的内存中 `len` 个字节追加到缓冲区。该对象需要可转换为（常量）指针。

buf.set: |
  该方法允许将一个字符串或 FFI cdata 对象以“零拷贝”的方式作为缓冲区来消费。它会在缓冲区中保存对传入字符串 `str` 或 FFI cdata 对象的引用，并释放原本为缓冲区分配的任何空间。

  调用该方法后，缓冲区的行为等同于调用 `buf:free():put(str)` 或 `buf:free():put(cdata, len)`，但只要缓冲区只被消费（读取），数据仅被引用而不会被复制。

  如果之后对缓冲区进行写入，则会把被引用的数据拷贝到内部缓冲区，并移除对象引用（写时复制语义）。

  保存的引用会作为垃圾回收器的锚点，确保原本传入的字符串或 FFI cdata 对象保持存活。

buf.reset: |
  重置（清空）缓冲区。已分配的缓冲区空间不会被释放，并可被复用。

buf.free: |
  释放缓冲区对象所占用的缓冲区空间。对象本身仍然存在，处于空状态并可被复用。

  注意：通常不需要使用该方法。缓冲区对象被回收时，垃圾回收器会自动释放其缓冲区空间。只有在你需要立即释放关联内存时才应使用此方法。

buf.reserve: |
  `reserve` 方法会在缓冲区中至少预留 `size` 字节的写入空间，并返回一个指向该空间的 `uint8_t *` FFI cdata 指针 `ptr`。

  可用长度（字节）会通过 `len` 返回：它至少为 `size`，但为了便于高效扩容，可能会更大。你可以利用这部分额外空间，也可以忽略 `len`，只使用 `size` 字节。

  该方法与 `buf:commit()` 配合，可对 C 风格的 read API 实现零拷贝写入：

  ```lua
  local MIN_SIZE = 65536
  repeat
    local ptr, len = buf:reserve(MIN_SIZE)
    local n = C.read(fd, ptr, len)
    if n == 0 then break end -- EOF.
    if n < 0 then error("read error") end
    buf:commit(n)
  until false
  ```

  预留的写入空间不会被初始化。在调用 `commit` 之前，至少需要把实际使用的 `used` 字节写入该空间。如果没有向缓冲区添加任何内容（例如发生错误），则无需调用 `commit`。

buf.reserve.return.1: |
  指向该空间的 `uint8_t *` FFI cdata 指针。

buf.reserve.return.2: |
  可用长度（字节）。

buf.commit: |
  将先前 `reserve` 返回的写入空间中实际使用的 `used` 字节追加到缓冲区数据中。

buf.skip: |
  从缓冲区中跳过（消费）`len` 个字节，最多到当前缓冲区数据长度为止。

buf.get: |
  消费缓冲区数据并返回一个或多个字符串。多个参数会按给定顺序依次消费缓冲区数据。

  - 不带参数调用时，消费整个缓冲区数据。
  - 以数字参数调用时，最多消费 `len` 字节。
  - 参数为 `nil` 时，会消费剩余的缓冲区空间（这通常只应作为最后一个参数使用）。

  注意：当长度为 0 或没有剩余缓冲区数据时，返回的是空字符串而不是 `nil`。

buf.tostring: |
  从缓冲区数据创建一个字符串，但不会消费数据；缓冲区保持不变。

  缓冲区对象还定义了 `__tostring` 元方法。这意味着缓冲区可以传给全局 `tostring()` 函数，以及许多可以用它代替字符串的函数。在诸如 `io.write()` 之类的函数中，一些关键的内部用法会被短路处理，以避免创建中间字符串对象。

buf.ref: |
  返回一个指向缓冲区数据的 `uint8_t *` FFI cdata 指针 `ptr`，并通过 `len` 返回缓冲区数据的字节长度。

  返回的指针可直接传给需要“缓冲区 + 长度”的 C 函数。你也可以对缓冲区数据进行逐字节读取（`local x = ptr[i]`）或写入（`ptr[i] = 0x40`）。

  结合 `buf:skip()` 方法，可以对 C 风格的 write API 实现零拷贝写入：

  ```lua
  repeat
    local ptr, len = buf:ref()
    if len == 0 then break end
    local n = C.write(fd, ptr, len)
    if n < 0 then error("write error") end
    buf:skip(n)
  until n >= len
  ```

  与 Lua 字符串不同，缓冲区数据不会隐式以 0 结尾。把 `ptr` 传给期望以 0 结尾字符串的 C 函数并不安全。如果你不使用 `len`，那很可能是在做错事。

buf.ref.return.1: |
  指向缓冲区数据的 `uint8_t *` FFI cdata 指针。

buf.ref.return.2: |
  缓冲区数据的字节长度。

buf.encode: |
  将 Lua 对象序列化（编码）到缓冲区。

  尝试序列化不支持的对象类型、循环引用或嵌套很深的表时，该函数可能会抛出错误。

buf.decode: |
  从缓冲区反序列化（解码）一个对象。

  返回的对象可以是任意受支持的 Lua 类型——甚至可以是 `nil`。

  当输入的数据格式错误或编码数据不完整时，该函数可能会抛出错误。

  解码后会把未消费的剩余数据保留在缓冲区中。

  如果尝试反序列化 FFI 类型，而 FFI 库未内建或尚未加载，则会抛出错误。

buffer.encode: |
  序列化（编码）Lua 对象 `obj`。

  尝试序列化不支持的对象类型、循环引用或嵌套很深的表时，该函数可能会抛出错误。

buffer.decode: |
  将字符串反序列化（解码）为 Lua 对象。

  返回的对象可以是任意受支持的 Lua 类型——甚至可以是 `nil`。

  当输入的数据格式错误或编码数据不完整时会抛出错误。
  当解码单个顶层对象后仍有剩余数据时也会抛出错误。

  如果尝试反序列化 FFI 类型，而 FFI 库未内建或尚未加载，则会抛出错误。

buffer.new: |
  创建一个新的缓冲区对象。

  可选参数 `size` 用于确保最小的初始缓冲区大小。当你预先知道所需缓冲区大小时，这严格来说只是一次优化；无论如何，缓冲区空间都会按需增长。

  可选的表参数 `options` 用于设置各种序列化选项。

string.buffer.serialization.opts: |
  序列化选项

  传给 `buffer.new()` 的 `options` 表可以包含以下成员（全部可选）：

  - `dict`：一个 Lua 表，保存一组在你要序列化的对象中经常作为表键出现的字符串字典。序列化时这些键会被紧凑地编码为索引。选择合适的字典可以节省空间并提升序列化性能。
  - `metatable`：一个 Lua 表，保存一组用于你要序列化的表对象的元表字典。

  `dict` 需要是字符串数组，`metatable` 需要是表数组；两者都从索引 1 开始且不能有空洞（中间不能出现 nil）。这些表会被锚定在缓冲区对象中，并在内部被改造为双向索引（不要自己这么做，只需传入普通数组）。在把它们传给 `buffer.new()` 之后，这些表不得再被修改。

  编码器与解码器使用的 `dict` 与 `metatable` 必须一致。把最常见的条目放在前面，并在末尾扩展以保证向后兼容——这样旧的编码仍然能被读取。你也可以将某些索引设为 false 来显式放弃向后兼容；解码使用了这些索引的旧编码时会抛出错误。

  编码时，如果某个元表不在 `metatable` 字典中，则会被忽略；解码会返回一个元表为 nil 的表。

  注意：解析并准备 `options` 表的开销相对较高。建议只创建一次缓冲区对象并在多次使用中复用。避免混用编码与解码缓冲区，因为 `buf:set()` 会释放已分配的缓冲区空间：

  ```lua
  local options = {
    dict = { "commonly", "used", "string", "keys" },
  }
  local buf_enc = buffer.new(options)
  local buf_dec = buffer.new(options)

  local function encode(obj)
    return buf_enc:reset():encode(obj):get()
  end

  local function decode(str)
    return buf_dec:set(str):decode()
  end
  ```

