首页 > 解决方案 > #byteslice 无法正确计算包含重音符号的字符串中的字节

问题描述

我注意到,当给定的字符串包含重音符号时,该byteslice方法无法返回正确的字符,因为 Ruby 显然以某种奇怪的方式计算字符串中的字节数。那,或者我错过了有关如何计算字符串中的字节数的信息。

这是一个MWE:

text = "Càdiz"
puts text.byteslice(0)
puts text.byteslice(1)
puts text.byteslice(2)
puts text.byteslice(3)
puts text.byteslice(4)

我在终端得到的结果是这样的:

C
�
�
d
i

所以,当然,那个重音字母给我带来了麻烦。这是正常的吗?有没有办法总是得到一个字符串的第五个字符作为回报,使用text.byteslice(4)或其他类似的方法?

标签: rubystringbyte

解决方案


我注意到,当给定的字符串包含重音符号时,该byteslice方法无法返回正确的字符,

String#byteslice返回字节而不是字符,因此它无法返回字符这一事实应该不足为奇。

因为 Ruby 显然以某种奇怪的方式计算字符串中的字节数。

我看不出有什么奇怪的。您要求它返回第二个字节,它返回了第二个字节。UTF-8 是专门设计的,多字节序列的每个单独字节都是非法编码,所以如果你像这样分割一个字符,它不可能产生一个合法字符。

也就是说它允许 UTF-8 流解码器自同步。

有没有办法总是得到一个字符串的第五个字符作为回报,使用text.byteslice(4)或其他类似的方法?

不,没有办法使用 获得第五个字符byteslice因为byteslice它是字节,而不是字符

但是,您可以String#[]使用以下方法获取第五个字符:

text[4]

但是,您可能想要的不是第五个字符,而是第五个字素簇,您可以使用以下String#grapheme_clusters方法做到这一点:

text.grapheme_clusters[4]

例如,让我们看看我的名字。用 Unicode 写我的名字有两种不同的方法:

  1. 约尔格
  2. J o <组合字符分音符> rg

请注意,第一个版本是四个字符长,第二个版本是五个字符长。不过,两者都有四个字素簇。但是,请注意,虽然字形相同,但字素簇却不同。

第一个版本以 ISO8859-15 编码为 4 个字节,但在 UTF-8 中需要 5 个字节,在 UTF-16 中需要 8 个字节,在 UTF-32 中需要 16 个字节。

第二个版本不能用 ISO8859-15 编码,因为 ISO8859-15 没有组合字符分词。事实上,它根本没有组合字符。用 UTF-8 编码第二个版本需要 6 个字节,UTF-16 10 个字节,UTF-32 20 个字节。

让我们假设第二个例子,假设它是用 UTF-8 编码的。该字符串由以下字符组成:

  1. U+004A 拉丁文大写字母 J
  2. U+006F 拉丁文小写字母 o
  3. U+0308 组合字符分音
  4. U+0072 拉丁文小写字母 r
  5. U+0067 拉丁文小写字母 g

这些被编码成 UTF-8 到这个字节序列中:

  1. 0x4A
  2. 0x6F
  3. 0xCC
  4. 0x88
  5. 0x72
  6. 0x67

因此,例如,如果您要请求第三个字节,您会得到0xCC. 如果您尝试将其显示为字符串,它将失败,因为0xCC它本身不是合法的 UTF-8 编码。它是多字节序列的第一个字节。

如果你要第三个字符,你会得到 U+0308 Combining Character Diaeresis。如果您尝试将其显示为字符串,它将失败,因为没有基本字符的组合字符没有意义。

如果你要第三个字素簇,你会得到'r',这可能是你想要的。

所以,简而言之:你没有得到你想要的字符的原因是你没有要求一个字符,你要求一个字节。如果你想要一个角色,你需要一个角色。但是,您可能想要的是字素簇,而不是字符。

事实上,字素簇几乎总是你想要的。


推荐阅读