本文介绍了为满足《中华人民共和国通用语言文字法》、GB 18030-2022、《通用规范汉字表》的要求,使用编程语言及数据库软件需要做些什么
什么是字符?
比如一个拉丁字母“A”、一个汉字“我”、一个标点符号“。”、一个表情符号“😀”等都是字符。
注意,根据不同的场景ZWJ序列可视为一个字符,也可视为多个字符。一般来说ZWJ序列应视为多个字符。 以🧑🏽为例,由
U+1F9D1
+U+1F3FD
组成,两个码点(Code Point)应为两个字符,但是理解成一个字符也没问题。
在信息化系统中有很多场景都需要计算字符数量,我们通常使用编程语言内置的形如String
类型的Length
属性获取字符数。如果我们处理的都是基本多文种平面(Basic Multilingual Plane,以下简称 BMP)范围内字符的话,并没有什么问题,但是如果处理其他平面内字符的话获取到的Length
并不是我们期望的结果。
'A'.length // 输出 1
'惠'.length // 输出 1
'𠅤'.length // 输出 2
'😀'.length // 输出 2
以上代码执行结果中字符A
、惠
返回了数量1,字符𠅤
、😀
返回了数量2。数量2
明显不是我们能接受的。
JavaScript使用UTF-16编码(即UCS-2),UTF-16是一种定长编码,每个代码单元(Code Unit)使用2个字节共16位表示一个字符,1个编码单元可以表示BMP范围\u0000-\uFFFF
内所有字符。表示不在BMP范围内的字符则使用代理项对(Surrogate Pair)项寻址,代理项对使用2个代码单元,前导(高代理项)\uD800–\uDBFF
紧跟着尾随(低代理项)\uDC00–\uDFFF
,它们必须成对出现共使用4个字节32位表示一个字符。
表1 各字符不同编码方案的二进制形式。括号内为UTF16的代理项对码点
字符 | Unicode | UTF-8 | UTF-16BE | UTF-32BE |
---|---|---|---|---|
A | U+0041 |
01000001 |
00000000 01000001 |
00000000 00000000 00000000 01000001 |
惠 | U+60E0 |
11100110 10000011 10100000 |
01100000 11100000|00000000 00000000 01100000 11100000 |
|
𠅤 | U+20164 (U+D840 U+DD64 ) |
11110000 10100000 10000101 10100100 |
11011000 01000000 11011101 01100100 |
00000000 00000010 00000001 01100100 |
😀 | U+1F600 (U+D83D U+DE00 ) |
11110000 10011111 10011000 10000000 |
11011000 00111101 11011110 00000000 |
00000000 00000001 11110110 00000000 |
在JavaScript中,String length
属性获取到的正是字符串使用的代码单元数量1。如果想获取BMP文字例如拉丁文字、西里尔文字、CJK(中日韩统一表意文字)等常见文字的字符数时使用String length
并不会有什么问题,但是如果想获取某些特殊文字比如扩充B-G区内的汉字2、表情符号、数学符号等文字的字符数时,就要考虑换种方式了。
上文的例子中A
、惠
位于BMP使用1个代码单元,𠅤
、😀
在BMP以外,UTF-16编码需使用2个代码单元,因此获取length
属性时分别返回了1和2。
[..."A"].length // 输出 1
[..."惠"].length // 输出 1
[..."𠅤"].length // 输出 1
[..."😀"].length // 输出 1
等同于
Array.from("𠅤").length // 输出 1
Array.from("😀").length // 输出 1
JavaScript中如果想把ZWJ序列计算为一个字符可使用Segmenter
分段器
[...new Intl.Segmenter().segment("🧑🏽")].length // 输出 1
以如下文本框为例测试
<input maxlength=1 type=text>
在.NET中,String
对象是Char Struct
的顺序集合,Char
使用UTF-16编码表示Unicode码点,每个Char
表示一个代码单元3,BMP以外的字符和ZWJ序列由多个Char
对象表示。因此String
的Length
属性返回该实例中Char
对象的数量4而不是Unicode字符数。因此须使用System.Globalization.StringInfo
类来处理Unicode字符。
"惠".Length; // 输出 1
"𠅤".Length; // 输出 2
StringInfo.ParseCombiningCharacters("惠").Length; // 输出 1
StringInfo.ParseCombiningCharacters("𠅤").Length; // 输出 1
在Java中,String
对象是Char
的集合,Char
使用UTF-16表示Unicode码点,每个Char
表示一个代码单元,BMP以外的增补字符由代理项对表示,使用2个Char
5。获取字符数因使用如下方式。Character
相关的方法查看Character#Unicode。有关Java平台增补字符的其他资料可查阅Supplementary Characters in the Java Platform。
"惠".length; // 输出 1
"𠅤".length; // 输出 2
"惠".codePointCount(0, "惠".length); // 输出 1
"𠅤".codePointCount(0, "𠅤".length); // 输出 1
MySQL 中,LENGTH
函数返回字符的字节数,CHAR_LENGTH
函数返回Unicode字符数(Number of Code Point)6。
SELECT LENGTH('惠'); -- 输出 3
SELECT LENGTH('𠅤'); -- 输出 4
SELECT CHAR_LENGTH('惠'); -- 输出 1
SELECT CHAR_LENGTH('𠅤'); -- 输出 1
应使用utf8mb4
编码方案。
术语 | 中文 | 描述 |
---|---|---|
BMP(Basic Multilingual Plane) | 基本多文种平面、基本平面 | 指0000...FFFF 范围内的字符 |
code point | 码点、代码点 | 一般对应一个字符。在UTF-16 中,使用代理项对来表示扩充字符(表现为两个代码单元,也对应了两个代理项范围的码点,事实上代理项也仅针对UTF-16 定义) |
code unit | 代码单元 | 特定编码方案使用的位大小。UTF-8 的代码单元是1字节8位,UTF-16 的代码单元是2字节16位,UTF-32 的代码单元是4字节32位 |
surrogate | 代理项 | 由D800...DBFF 和DC00...DFFF 范围的码点组成,在UTF-16 中成对出现用来表示BMP以外的扩充字符 |
character | 字符 | 每个字符都是一个自然语言中的字母、符号、汉字等。每个码点对应一个字符。UTF-16 中一个字符使用1~2个代码单元表示一个字符。 |
byte | 字节 | 8位(比特)组成1字节 |
bit | 位、比特 | 值包含0和1,计算机中的最小运算单位 |
String length - JavaScript https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/length ↩
国务院2013年颁布的《通用规范汉字表》中包含196个汉字位于BMP之外。2022年发布的GB 18030-2022 也将增补区的196个汉字规定为实现级别2的要求。 ↩
Char Struct - .NET https://learn.microsoft.com/en-us/dotnet/api/system.char ↩
String - .NET https://learn.microsoft.com/en-us/dotnet/api/system.string.length ↩
String (Java Platform SE 8) https://docs.oracle.com/javase/8/docs/api/java/lang/String.html ↩
12.8 String Functions and Operators https://dev.mysql.com/doc/refman/8.0/en/string-functions.html ↩