仓颉 std.binary LittleEndianOrder 接口详解:小端序字节序列转换实战指南

原创2026-10-13 18:47:501,628 阅读
文章标签:人工智能AI 技能文档开发工具

仓颉 std.binary LittleEndianOrder 接口详解:小端序字节序列转换实战指南

本篇技术指南围绕仓颉标准库 std.binary 包中的 LittleEndianOrder<T> 接口展开,完整讲解其接口定义、两个核心转换方法(readLittleEndian / writeLittleEndian)、12 个内置类型扩展(从 Bool 到 UInt64 的整型与浮点型),并结合仓库 API 文档中的可验证示例给出可直接运行的小端序读写代码。读完本文,你将能够在仓颉程序中以标准小端序(Little-endian)将内存值与 Array<Byte> 字节序列互转,正确处理缓冲区长度不足导致的 IllegalArgumentException,并理解小端序与大端序在字节排列上的本质差异。

一、端序背景与接口定位

在仓颉标准库中,std.binary 包提供了“仓颉数据类型与二进制字节序列间的互相转换”能力,分为大端序、小端序两种转换类型,另提供数据类型自身字节顺序反转的接口(详见 std/binary/binary_package_overview.md)。

所谓端序(Endianness,又称字节顺序、尾序),描述的是多字节对象在存储器或数字通信链路中的字节排列顺序:

  • 小端序(Little-endian):将一个多位数的低位字节存储在内存的低地址端,高位字节存储在内存的高地址端。
  • 大端序(Big-endian):与上述相反,高位字节存储在低地址端。

LittleEndianOrder<T> 正是 std.binary 包中负责“小端序字节序列转换”的接口,与其对应的大端序接口 BigEndianOrder<T>、字节反转接口 SwapEndianOrder<T> 共同构成完整的二进制转换工具集。在 x86 等主流小端序硬件平台上解析网络协议、文件格式或嵌入式设备数据时,本接口是首选工具。

二、接口定义与成员函数

LittleEndianOrder<T> 是泛型接口,其完整声明位于 binary_package_interfaces.md:

public interface LittleEndianOrder<T> {
    func writeLittleEndian(buffer: Array<Byte>): Int64
    static func readLittleEndian(buffer: Array<Byte>): T
}

接口只包含两个成员,职责清晰对称:一个负责“写入”(值 → 字节序列),一个负责“读取”(字节序列 → 值)。

1. static func readLittleEndian

static func readLittleEndian(buffer: Array<Byte>): T
  • 功能:从字节数组中以小端序的方式读取一个 T 值。
  • 参数:buffer: Array<Byte> —— 缓冲区,用于存放待读取的数据。其中 Byte 是内置类型 UInt8 的别名(见 core_package_types.md),数组类型 Array<T> 是仓颉原生数组(见 core_package_structs.md)。
  • 返回值:T 值。
  • 异常:当 buffer 太小、不足以读出 T 值时,抛出 IllegalArgumentException。

由于是 static 方法,读取时以 类型名.readLittleEndian(buffer) 的形式调用,例如 Int32.readLittleEndian(buffer)。

2. func writeLittleEndian

func writeLittleEndian(buffer: Array<Byte>): Int64
  • 功能:将 T 值以小端序的方式写入字节数组中。
  • 参数:buffer: Array<Byte> —— 缓冲区,用于存放待写入的数据。
  • 返回值:Int64 —— 实际写入的数据的字节数(这一点在编写自定义协议时非常有用,可通过返回值精确切片出有效字节)。
  • 异常:当 buffer 太小、不足以存储 T 值时,抛出 IllegalArgumentException。

由于是实例方法,写入时以 值.writeLittleEndian(buffer) 的形式调用,例如 0x1234i16.writeLittleEndian(buffer)。

三、12 个内置类型的接口扩展总览

LittleEndianOrder<T> 接口本身并不直接实例化,而是通过仓颉的 extend 扩展机制为 12 个内置类型逐一实现。每个扩展的父类型均为 LittleEndianOrder<对应类型>,覆盖了仓颉全部常用定宽标量类型:

扩展声明 值宽(字节) 读取原型 写入原型
extend Bool <: LittleEndianOrder<Bool> 1 readLittleEndian(buffer) -> Bool writeLittleEndian(buffer) -> Int64
extend Int8 <: LittleEndianOrder<Int8> 1 readLittleEndian(buffer) -> Int8 writeLittleEndian(buffer) -> Int64
extend UInt8 <: LittleEndianOrder<UInt8> 1 readLittleEndian(buffer) -> UInt8 writeLittleEndian(buffer) -> Int64
extend Int16 <: LittleEndianOrder<Int16> 2 readLittleEndian(buffer) -> Int16 writeLittleEndian(buffer) -> Int64
extend UInt16 <: LittleEndianOrder<UInt16> 2 readLittleEndian(buffer) -> UInt16 writeLittleEndian(buffer) -> Int64
extend Float16 <: LittleEndianOrder<Float16> 2 readLittleEndian(buffer) -> Float16 writeLittleEndian(buffer) -> Int64
extend Int32 <: LittleEndianOrder<Int32> 4 readLittleEndian(buffer) -> Int32 writeLittleEndian(buffer) -> Int64
extend UInt32 <: LittleEndianOrder<UInt32> 4 readLittleEndian(buffer) -> UInt32 writeLittleEndian(buffer) -> Int64
extend Float32 <: LittleEndianOrder<Float32> 4 readLittleEndian(buffer) -> Float32 writeLittleEndian(buffer) -> Int64
extend Int64 <: LittleEndianOrder<Int64> 8 readLittleEndian(buffer) -> Int64 writeLittleEndian(buffer) -> Int64
extend UInt64 <: LittleEndianOrder<UInt64> 8 readLittleEndian(buffer) -> UInt64 writeLittleEndian(buffer) -> Int64
extend Float64 <: LittleEndianOrder<Float64> 8 readLittleEndian(buffer) -> Float64 writeLittleEndian(buffer) -> Int64

各内置类型的位宽与取值范围可参考 core_package_intrinsics.md:例如 Int16 表示 16 位有符号整型(范围为 [-2^15, 2^15 - 1])、Float16 符合 IEEE 754 半精度格式(binary16)、Float32 为单精度(binary32)、Float64 为双精度(binary64)。下表为实际读写所需的最小缓冲区长度:

类型 最小 buffer 长度 类型 最小 buffer 长度
Bool / Int8 / UInt8 1 字节 Int32 / UInt32 / Float32 4 字节
Int16 / UInt16 / Float16 2 字节 Int64 / UInt64 / Float64 8 字节

四、整型的小端序读写实战

整型的字节序转换最直观:小端序下,最低有效字节排在数组最前面。下面以仓库 API 文档中的可运行示例为蓝本(见 binary_package_interfaces.md),逐一验证各整型的读写。

1. Int8 / UInt8:单字节无字节序差异

8 位类型只有 1 个字节,小端序与大端序结果完全一致:

import std.binary.*
import std.unittest.*
import std.unittest.testmacro.*

main() {
    // 读取
    let buffer: Array<Byte> = [0x12]
    let n = Int8.readLittleEndian(buffer)
    @Assert(n, 0x12)

    // 写入
    let wbuffer = Array<Byte>(8, repeat: 0)
    let w = 0x12i8.writeLittleEndian(wbuffer)
    @Assert(w, 1)
    @Assert(wbuffer[..w] == [0x12])
}

UInt8 的行为与 Int8 完全相同:UInt8.readLittleEndian([0x12]) 返回 0x12u8,0x12u8.writeLittleEndian(buffer) 写入 1 字节并返回 1。

2. Int16 / UInt16:两字节低位在前

以值 0x1234 为例,小端序字节序列为 [0x34, 0x12](低位字节 0x34 在前),与对应大端序示例中的 [0x12, 0x34] 正好相反:

import std.binary.*
import std.unittest.*
import std.unittest.testmacro.*

main() {
    // 读取:字节序列 [0x34, 0x12] 还原为 0x1234
    let buffer: Array<Byte> = [0x34, 0x12]
    let n = Int16.readLittleEndian(buffer)
    @Assert(n, 0x1234i16)

    // 写入:0x1234 以小端序写出为 [0x34, 0x12],返回写入字节数 2
    let wbuffer = Array<Byte>(8, repeat: 0)
    let w = 0x1234i16.writeLittleEndian(wbuffer)
    @Assert(w, 2)
    @Assert(wbuffer[..w] == [0x34, 0x12])
}

UInt16 读写行为与 Int16 一致:UInt16.readLittleEndian([0x34, 0x12]) 返回 0x1234u16,0x1234u16.writeLittleEndian(buffer) 写出 [0x34, 0x12]。

3. Int32 / UInt32:四字节逐级反转

值 0x12345678 在小端序下的内存布局为 [0x78, 0x56, 0x34, 0x12]——注意这是大端序 [0x12, 0x34, 0x56, 0x78] 的完全反转:

import std.binary.*
import std.unittest.*
import std.unittest.testmacro.*

main() {
    // 读取
    let buffer: Array<Byte> = [0x78, 0x56, 0x34, 0x12]
    let n = Int32.readLittleEndian(buffer)
    @Assert(n, 0x12345678)

    // 写入:返回 4,buffer 前 4 字节为 [0x78, 0x56, 0x34, 0x12]
    let wbuffer = Array<Byte>(8, repeat: 0)
    let w = 0x12345678i32.writeLittleEndian(wbuffer)
    @Assert(w, 4)
    @Assert(wbuffer[..w] == [0x78, 0x56, 0x34, 0x12])
}

UInt32 同理:读取 [0x78, 0x56, 0x34, 0x12] 得 0x12345678u32,写入返回 4 字节。

4. Int64 / UInt64:八字节整体倒序

64 位整型需要 8 字节缓冲区,小端序下整段字节序列按“低字节在前”排列:

import std.binary.*
import std.unittest.*
import std.unittest.testmacro.*

main() {
    // 读取:0x1234567890123456 的小端序表示
    let buffer: Array<Byte> = [0x56, 0x34, 0x12, 0x90, 0x78, 0x56, 0x34, 0x12]
    let n = Int64.readLittleEndian(buffer)
    @Assert(n, 0x1234567890123456)

    // 写入:返回 8,写出序列与读取输入完全一致(读写互为逆操作)
    let wbuffer = Array<Byte>(8, repeat: 0)
    let w = 0x1234567890123456i64.writeLittleEndian(wbuffer)
    @Assert(w, 8)
    @Assert(wbuffer[..w] == [0x56, 0x34, 0x12, 0x90, 0x78, 0x56, 0x34, 0x12])
}

UInt64 行为一致,可参考 binary_package_interfaces.md 中的对应示例。

五、浮点型的小端序读写实战

浮点型同样遵循 IEEE 754 位模式进行字节序转换:先把浮点值按其精度规范(Float16/binary16、Float32/binary32、Float64/binary64)解析为固定位数的二进制位模式,再按小端序逐字节排列。以下示例均以 12.5 为测试值,其在各精度下的位模式为:

  • 12.5 的半精度(Float16)位模式为 0x4A40;
  • 12.5 的单精度(Float32)位模式为 0x41480000;
  • 12.5 的双精度(Float64)位模式为 0x4029000000000000。

1. Float16:两字节半精度

import std.binary.*
import std.unittest.*
import std.unittest.testmacro.*

main() {
    // 读取:0x4A40 的小端序表示 [0x40, 0x4A]
    let buffer: Array<Byte> = [0x40, 0x4A]
    let n = Float16.readLittleEndian(buffer)
    @Assert(n, 12.5)

    // 写入:返回 2,写出 [0x40, 0x4A]
    let wbuffer = Array<Byte>(8, repeat: 0)
    let w = 12.5f16.writeLittleEndian(wbuffer)
    @Assert(w, 2)
    @Assert(wbuffer[..w] == [0x40, 0x4A])
}

2. Float32:四字节单精度

import std.binary.*
import std.unittest.*
import std.unittest.testmacro.*

main() {
    // 读取:0x41480000 的小端序表示 [0x00, 0x00, 0x48, 0x41]
    let buffer: Array<Byte> = [0x00, 0x00, 0x48, 0x41]
    let n = Float32.readLittleEndian(buffer)
    @Assert(n, 12.5)

    // 写入:返回 4
    let wbuffer = Array<Byte>(8, repeat: 0)
    let w = 12.5f32.writeLittleEndian(wbuffer)
    @Assert(w, 4)
    @Assert(wbuffer[..w] == [0x00, 0x00, 0x48, 0x41])
}

3. Float64:八字节双精度

import std.binary.*
import std.unittest.*
import std.unittest.testmacro.*

main() {
    // 读取:0x4029000000000000 的小端序表示
    let buffer: Array<Byte> = [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x40]
    let n = Float64.readLittleEndian(buffer)
    @Assert(n, 12.5)

    // 写入:返回 8
    let wbuffer = Array<Byte>(8, repeat: 0)
    let w = 12.5f64.writeLittleEndian(wbuffer)
    @Assert(w, 8)
    @Assert(wbuffer[..w] == [0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0x40])
}

六、Bool 的小端序读写

Bool 值在字节序列中以单字节承载:字节值为 0x01 表示 true(0x00 表示 false)。由于只有 1 字节,无字节序之分:

import std.binary.*
import std.unittest.*
import std.unittest.testmacro.*

main() {
    // 读取
    let buffer: Array<Byte> = [0x1]
    let n = Bool.readLittleEndian(buffer)
    @Assert(n, true)

    // 写入:返回 1,写出 [0x01]
    let wbuffer = Array<Byte>(8, repeat: 0)
    let w = true.writeLittleEndian(wbuffer)
    @Assert(w, 1)
    @Assert(wbuffer[..w] == [0x01])
}

七、缓冲区长度约定与异常处理

使用本接口时有两点必须注意的缓冲区约定:

  1. 写入端的缓冲区准备:官方示例均先以 Array<Byte>(8, repeat: 0) 分配 8 字节缓冲区再写入。实际使用时,缓冲区长度应不小于目标类型的最小字节数(见第三节表格)。writeLittleEndian 返回的 Int64 是实际写入字节数,可用 buffer[..n] 精确切片取出有效数据,避免把未写入的填充零一并打包进协议。

  2. 读取端的最小长度:readLittleEndian 要求 buffer 至少包含目标类型所需的字节数。当 buffer 太小、不足以读出(或不足以存储)T 值时,两个方法都会抛出 IllegalArgumentException(表示参数非法的异常类,父类型为 Exception)。因此从外部数据源(网络、文件)读取时,建议先校验数据长度,或在调用处捕获该异常,避免协议解析中途崩溃。

八、与同包接口的协同使用

LittleEndianOrder<T> 并非孤立存在,在 std.binary 包中它与另外两个接口配合,覆盖字节序处理的全场景(接口索引见 readme.md):

  • BigEndianOrder<T>:大端序字节序列转换接口,提供 readBigEndian / writeBigEndian。在网络协议(如 TCP/IP 头、DNS 报文)这类以“网络字节序”传递数据的场景中使用;与本文接口的字节排列完全相反。
  • SwapEndianOrder<T>:反转字节顺序接口,提供 swapBytes(),用于在内存中直接反转 Int8 ~ UInt64 等整型值的字节顺序,典型用途是跨平台换算主机字节序,无需经过 Array<Byte> 中转。

在异构系统间交换二进制数据时,可依据通信双方的平台字节序选择对应接口:LittleEndianOrder 适合解析 x86/ARM 等小端序主机生成的数据,或主动向对方输出小端序数据;若不确定对方字节序,建议先通过协议头字段协商,再统一选用本文接口或 BigEndianOrder 完成编解码。

九、小结

LittleEndianOrder<T> 以两个对称的方法覆盖了仓颉 12 种内置标量类型与 Array<Byte> 字节序列之间的小端序互转:readLittleEndian 负责按“低位字节在前”还原数值,writeLittleEndian 负责把数值按小端序写出并返回实际字节数。结合本文给出的 12 个类型示例,你可以直接将其落地到自定义二进制协议编解码、文件格式读写与跨平台数据交换等场景;若遇到大端序需求,切换至同包的 BigEndianOrder<T> 即可,其余调用模式完全一致。

参考文档

登录后查看全文
DocAgent