utf16.Decode 返回 []rune 而非 string,需先将 UTF-16 字节流解析为 []uint16(手动处理 BOM 和字节序),再调用 utf16.Decode 得 []rune,最后用 string() 转为 UTF-8 字符串。

Go 里 utf16.Decode 解出来的不是字符串,是 []rune
这是最常卡住人的地方:你用 utf16.Decode 把 UTF-16 字节切片转出来,结果得到的是 []rune,不是 string。Go 的 string 底层是 UTF-8 编码的字节序列,不能直接把 UTF-16 码点数组当字符串用。
- 错误写法:
str := string(utf16.Decode(b))—— 这会把每个rune当成一个字节转,乱码且长度错 - 正确做法:先转成
[]rune,再用string()构造 UTF-8 字符串:string(utf16.Decode(b))实际上可行,但前提是b是已解码的 UTF-16 码元([]uint16),不是原始字节 - 真正常见场景是读取 Windows API 或某些二进制协议传来的 UTF-16LE 字节流,得先用
encoding/binary拆成[]uint16,再喂给utf16.Decode
处理 BOM 和大小端必须手动判断,unicode/utf16 不自动识别
utf16.Decode 只负责把 []uint16 转成 []rune,它不管字节序、也不看 BOM。如果你拿到的是原始字节(比如从文件读的 []byte),BOM 和端序得自己处理。
- 典型错误:直接对 UTF-16LE 文件调
binary.Read(..., binary.LittleEndian, &buf)却忘了先跳过 2 字节 BOM(0xFF 0xFE) - 安全做法:用
utf16.Decode前,先检查前两个字节是否为0xFF 0xFE(LE)或0xFE 0xFF(BE),再选对应端序解析[]uint16 - Windows 记事本默认存 UTF-16LE + BOM;Go 标准库没提供类似 Python 的
open(..., encoding='utf-16')自动探测,必须手撸
utf16.Encode 输出的是 []uint16,不是字节,别忘了转端序再写文件
想把 Go 字符串写成 UTF-16 文件?utf16.Encode 返回的是 []uint16,不是 []byte。你得自己决定端序,再用 encoding/binary 写出去。
- 漏掉 BOM:很多工具(如 Notepad)依赖 BOM 判断编码,不写就可能被当成 ANSI
- 端序写反:用
binary.LittleEndian写了却标成 UTF-16BE,或者反过来 - 推荐流程:
runes := []rune(str)→uint16s := utf16.Encode(runes)→write(0xFF, 0xFE)→binary.Write(..., binary.LittleEndian, uint16s)
代理对(surrogate pair)在 Go 中天然支持,但别误以为 len(string) 是字符数
UTF-16 用代理对表示 Unicode 中超出 BMP 的字符(比如 ?、大部分 emoji),utf16.Decode 会正确合并它们成单个 rune。这点 Go 做得很干净——但容易忽略的是后续操作。
立即学习“go语言免费学习笔记(深入)”;
-
len(s)返回的是 UTF-8 字节数,不是字符数;utf8.RuneCountInString(s)才是真实字符数 - 如果用
range遍历字符串,Go 自动按rune切,不会把代理对拆开——这是对的,但别拿index去做字节偏移假设 - 跨语言交互时(比如传给 C 函数),注意 C 层面对 UTF-16 的理解是否包含代理对;Go 的
uint16slice 是标准 UTF-16 编码,可直接传
实际写的时候,BOM 和端序处理最容易漏,一漏整个文件就打不开。别指望库替你猜,Go 的哲学是明确优于隐式。










