去除UTF-8字符串中占用的闲置空间
在研究 Koha 中的一个非常奇怪的 bug 时,我不得不找出一种方法,将字符串切成特定的最大长度。以字节为单位,而不是以字符为单位,因为在这种情况下使用的是可怕的 USMARC 格式,其规格以两面红旗开头:它是 2000 年 1 月发布的,而且是 “美国国家标准的实现”,所以你可以肯定它只适用于 ASCII,在处理 Unicode 时会……很有趣。但对于较长的字符串来说,它一般都是坏的。
字节与字符
我想你应该知道字节和字符(或代码点)的区别。如果不知道,请阅读此文!
您可能还没读过链接,这里有一个非常简短的摘要:
- UTF8 使用可变长度将字母存储为 0 和 1。旧格式(如 ASCII)使用固定长度(例如一个字节 = 8 位),因此只能表示有限数量的字母。
- “普通 “字母(如果你说的是英文)需要 1 个字节。
- 但我们可以处理英语以外的字母。我们有像🚲这样的 “字母”。🚲 需要 4 个字节。在这里可以看到很多细节。
- 通常,如果普通人谈论字符串的长度,他们会计算字母:”Hello “有 5 个字母。”Hello🚲”有 6 个字母。
- 不幸的是,计算机有时需要知道字节数(例如要将数据存储到某个地方)。”Hello “需要 5 个字节。”Hello🚲”需要 9 个字节。
我的字符串有多长?
让我们比较一下 “I love to ride my bicycle “和 “I ♥ to ride my 🚲”。
~$ perl -C -Mutf8 -MEncode -E 'say q{"I love to ride my bicycle" vs. "I ♥ to ride my 🚲"}'
"I love to ride my bicycle" vs. "I ♥ to ride my 🚲"
本脚注2 解释了可能存在的奇怪命令行标志。
默认情况下,Perl 使用 length()
计算字符数:
~$ perl -C -Mutf8 -MEncode -E 'say length("I love to ride my bicycle")'
25
所以是 25 个字符。
~$ perl -C -Mutf8 -MEncode -E 'say length("I ♥ to ride my 🚲")'
16
现在只有 16 。
如果我们要计算字节数(通常不应该这样做,因为计算机会帮你处理),我们需要使用 bytes::length()
:
~$ perl -C -Mutf8 -MEncode -E 'say bytes::length("I love to ride my bicycle")'
25
也是 25 个字节,因为每个基本英文字母需要一个字节。
~$ perl -C -Mutf8 -MEncode -E 'say bytes::length("I ♥ to ride my 🚲")'
21
但现在花哨的 Unicode 版本需要 21 个字节,因为 ♥ 需要 3 个字节,🚲 需要 4 个字节,所以比字符多了 3 + 4 – 2 = 5 个字节。
塞进有限的空间
现在假设我们只有 20 个字节来存储这个字符串。因此,我们需要使用 substr()
删除部分文本:
~$ perl -C -Mutf8 -MEncode -E 'say substr("I love to ride my bicycle", 0, 20)'
I love to ride my bi
有效!
如果我们在花哨的 Unicode 版本上这样做:
~$ perl -C -Mutf8 -MEncode -E 'say substr("I ♥ to ride my 🚲", 0, 20)'
I ♥ to ride my 🚲
我们会得到相同的字符串!因为默认情况下,Perl 会计算字符数。而字符串只有 16 个字符。但却有 21 个字节。多了 1 个字节。因此,我们需要使用 bytes::substr()
:
~$ perl -C -Mutf8 -MEncode -E 'say bytes::substr("I ♥ to ride my 🚲", 0, 20)'
I ⥠to ride my ð
那垃圾是什么?
由于我们离开了常规 Perl 字符串处理和调用字节的安全填充空间,我们不得不更加小心谨慎,并了解一些 Perl 的内部机制(我将略作说明):Perl 会跟踪字符串是否包含 UTF-8。如果调用 bytes,它就会忘记字符串包含 UTF-8,而输出原始字节。但我们希望 Perl 将 bytes::substr
返回的字符串解释为 UTF-8,因此我们必须通过 decode_utf8
(从 Encode 导入)明确地告诉它。我觉得 decode_utf8
这个名字有点令人困惑,但(我)这样想会有帮助:将我们知道包含 UTF-8 的字节字符串(或文档中所说的八位字节)解码为 Perl 字符串。那么
~$ perl -C -Mutf8 -MEncode -E 'say decode_utf8(bytes::substr("I ♥ to ride my 🚲", 0, 20))'
I ♥ to ride my �
耶,我们的 ♥ 又回来了!
另一种方法(因此我们不必使用字节)是将字符串的字节表示传递给 substr
,我们可以使用 encode_utf8
生成字符串的字节表示。这又有点令人困惑,除非你这样想:把这个 Perl 字符串用 UTF-8 编码成一堆字节:
perl -C -Mutf8 -MEncode -E 'say decode_utf8(substr(encode_utf8("I ♥ to ride my 🚲"), 0, 20))'
I ♥ to ride my �
一样,但稍微好一点!
总之,我们又可以用”♥”了。但得到的字符串有点难看,因为它以”�”结尾。由于我们砍掉了 🚲 的几个字节,我们最终得到的确实是一个无效符号,它被渲染为 �。
但这个被混淆的字符串有多长呢?
~$ perl -C -Mutf8 -MEncode -E 'say bytes::length(bytes::substr("I ♥ to ride my 🚲", 0, 20))'
20
耶,20 个字节,现在合适了!
把它塞进有限的空间里,但不要有拖尾垃圾
现在,也许我们不喜欢结尾的 � 了。有一个很好的标志可以传递给 decode_utf8()
,这是我在处理/对抗最初的 bug 时了解到的:Encode::FB_QUIET
.
perl -C -Mutf8 -MEncode -E 'say decode_utf8(substr(encode_utf8("I ♥ to ride my 🚲"),0,20),Encode::FB_QUIET)'
I ♥ to ride my
不再有 �!因为 FB_QUIET 会告诉 decode_utf8
忽略无效字节。
那么它有多长呢?
perl -C -Mutf8 -MEncode -E 'say bytes::length(decode_utf8(substr(encode_utf8("I ♥ to ride my 🚲"),0,20),Encode::FB_QUIET))'
17
甚至更短,因为现在字符串不包含任何自行车部件 🙂
所有字符串
为了好玩,下面是从 1 到原始字符串的所有子串,不含任何 “半 “字符或无效字符。您可以清楚地看到,🚲 在完全呈现之前需要经过几个步骤:
perl -C -Mutf8 -MEncode -E 'my $s= "I ♥ to ride my 🚲"; for my $l (1 .. bytes::length($s)) { say decode_utf8(substr(encode_utf8($s),0,$l),Encode::FB_QUIET)}'
I
I
I
I
I ♥
I ♥
I ♥ t
I ♥ to
I ♥ to
I ♥ to r
I ♥ to ri
I ♥ to rid
I ♥ to ride
I ♥ to ride
I ♥ to ride m
I ♥ to ride my
I ♥ to ride my
I ♥ to ride my
I ♥ to ride my
I ♥ to ride my
I ♥ to ride my 🚲
Footnotes
命令行标志说明:
- -C 是 -CESL 的缩写,用于打开各种输入和输出流(STDOUT 等)的 UTF-8。参见 perldoc perlrun。
- -Mutf8 加载 utf8 模块,与在源代码中调用 use utf8; 相同。这会告诉 Perl 源代码本身包含 UTF-8 字符。就像🚲…
- -MEncode 加载编码。
你也许感兴趣的:
- 【外评】电脑从哪里获取时间?
- 【外评】为什么 Stack Overflow 正在消失?
- Android 全力押注 Rust,Linux 却在原地踏步?谷歌:用 Rust 重写固件太简单了!
- 【外评】哪些开源项目被广泛使用,但仅由少数人维护?
- 【外评】好的重构与不好的重构
- C 语言老将从中作梗,Rust for Linux 项目内讧升级!核心维护者愤然离职:不受尊重、热情被消耗光
- 【外评】代码审查反模式
- 我受够了维护 AI 生成的代码
- 【外评】Linux 桌面市场份额升至 4.45
- 【外评】作为全栈开发人员如何跟上 AI/ML 的发展?
你对本文的反应是: