编程中,字符串一般使用 Unicode。但是,绝大部分人并不了解 Unicode,常常犯一些低级错误。本文列出一些问题,都常在实际情况下遇到。不讲过于技术性或理论性的东西,因为我不懂。
本文也为我自己备忘。和绝大部分人一样,我也不了解 Unicode,本文可能有错误。
资料
这个术语表很好用,简洁明了:Glossary。
或者直接看标准(文件很大!)。
字符
字符类型常常不是一般认知的字符。
以下几种通常用作字符类型:
- Code unit。通常是 UTF-16 的,也就是十六位无符号整数。也有 UTF-8 的,八位。
- Code point。0 到 10FFFF 的整数。
- Scalar value。Code point 去除代理字符,也就是 0 到 D7FF,或 E000 到 10FFFF。
Java 使用第一种。Haskell 使用第二种。Rust 使用第三种。
偶尔还有用 grapheme cluster 的,那更符合一般认知。
字符串
标准的字符串表示 scalar value 序列,表示为 code unit 序列。
字符串类型通常使用 UTF-16 或 UTF-8。许多使用 UTF-16 的语言(如 JavaScript)允许字符串格式错误,也就等同于十六位无符号整数序列(非正式名称 WTF-16)。
字符串操作
正确的字符串连接后依然正确,切分则不一定。WTF-16 随便怎么弄都依然是 WTF-16。
正确的字符串,首尾切除若干 code unit 后,若把一个字符切两半就会错误。但是,正确与否一定可以检测出来,即便错误,也一定可以剔除错误部分,不会连累其他字符,也不会多出莫名其妙的字符。不过 WTF-16 不行。
字符串匹配(子串查找)可以直接在 code unit 层面进行,如果字符串格式正确,结果也正确。
字符串的相等比较,应当正规化:NFC
、NFD
、NFKC
、NFKD
。正规化是上下文相关的,转换后连接不等于连接后转换。
字符操作
通过字符串的下标访问 scalar value 或者 code point,通常是线性复杂度的。如果复杂度很低,一般说明访问的其实是 code unit。
一般不需要通过字符串的下标访问字符(例如 scalar value)。如果用到了,一般说明方案不好。建议使用字符串匹配,或者正则表达式。
要获取字符串中的字符,建议获取 grapheme cluster。这通常都是最佳方案,例如在翻转字符串时。
绝对不要自己处理字符的性质,包括但不限于大小写的转换和判断。例如语法解析,可以用 Xid_Start
之类的来解析标识符。
大小写转换
大小写转换会改变长度。
大小写转换是上下文相关的,转换后连接不等于连接后转换。
大小写转换可以是本地化的,不同地区转换结果可以不同。
转过去再转回来,不等于原来的(废话)。转过去再转回来再转过去,不等于直接转过去。
大小写无关形式应当使用专门的 toCasefold
方法,不要转成大写或小写。
如果要规范化,NFC
或 NFD
规范化应当在 toCasefold
前后各进行一遍,进而是幂等的。例如 NFD(toCasefold(NFD(x)))
。