仓颉 std.binary LittleEndianOrder 接口详解:小端序字节序列转换实战指南
仓颉 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])
}
七、缓冲区长度约定与异常处理
使用本接口时有两点必须注意的缓冲区约定:
-
写入端的缓冲区准备:官方示例均先以
Array<Byte>(8, repeat: 0)分配 8 字节缓冲区再写入。实际使用时,缓冲区长度应不小于目标类型的最小字节数(见第三节表格)。writeLittleEndian返回的Int64是实际写入字节数,可用buffer[..n]精确切片取出有效数据,避免把未写入的填充零一并打包进协议。 -
读取端的最小长度:
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> 即可,其余调用模式完全一致。