逐字节检查Unicode字符串中的空格!

3

简单直接的问题:我能否安全地假设UTF-8、UTF-16或UTF-32代码点(字符)的一个字节不会是ASCII空格字符(除非该代码点代表它)?

我来解释一下:

假设我有一个UTF-8编码的字符串。这个字符串包含一些需要多个字节才能存储的字符。我需要找出这个字符串中是否有任何ASCII空格字符(空格、水平制表符、垂直制表符、回车、换行等——Unicode定义了更多的空格字符,但暂且不管)。

所以我要做的就是遍历字符串,并检查任何一个字节是否与定义空格字符的字节相匹配。例如,以0D(十六进制)表示回车。请注意,我们在这里讨论的是字节,而不是字符。

这样做可以吗?会有UTF-8代码点的第一个字节是0D,第二个字节是其他东西,而这个代码点并不表示回车吗?反过来呢?会有第一个字节是奇怪的东西,第二个(或第三个、第四个)字节是0D,而这个代码点并不表示回车吗?

UTF-8与ASCII向后兼容,因此我真的希望它对UTF-8有效。从我所知道的来看,它可能有效,但我不太了解细节,无法确定。

至于UTF-16和UTF-32,我怀疑根本行不通,但我对这些细节知之甚少,因此请随意给我惊喜……


这个奇怪问题的原因是:我的代码检查ASCII空格是否有效,我需要知道它在Unicode上是否会出现问题。由于某些原因,我别无选择,只能逐字节进行检查。我希望与ASCII的向后兼容性可以至少为我提供UTF-8支持。


我不确定这是否相关,但是ASCII范围之外存在空格字符。 - McDowell
@McDowell:是的,我在问题中提到了它们,并选择忽略它们。 - user1481860
4个回答

6
对于UTF-8编码,可以实现该功能。所有的非ASCII字符都由高位设置的字节表示,而所有的ASCII字符都具有未设置高位。
需要明确的是,一个非ASCII字符的编码中每个字节都具有高位设置;这是按照设计要求实现的。
您不应该在字节级别上操作UTF-16或UTF-32。这几乎肯定行不通。事实上,很多东西会损坏,因为每个第二个字节很可能是'\0'(除非你通常使用另一种语言)。

检查空格时,每隔一个字节为空并不重要,但我想我会将其删除。UTF-8 是最广泛使用的编码方式,比没有好多了。谢谢。答案已接受(你是第一个回答的,所以我想这样最公平,尽管其他答案也很好)。好消息。 - user1481860
空字节问题会导致其他问题,因为许多基于ASCII的函数将空字节解释为字符串结束标记。你无法避免你的特定问题,因为有时高位字节恰好是0x20,这与空格字符重合。 - Marcelo Cantos

4
在正确编码的UTF-8中,所有ASCII字符将被编码为一个字节,并且每个字节的数值将等于Unicode和ASCII代码点。此外,任何非ASCII字符都将使用仅具有第八位设置的字节进行编码。因此,字节值为0D将始终表示回车符,而不是多字节UTF-8序列的第二个或第三个字节。
然而,有时会滥用UTF-8解码规则以其他方式存储ASCII字符。例如,如果您取两个字节的序列C0 A0并对其进行UTF-8解码,则会得到一个字节值20,它是一个空格。(每当找到字节C0或C8时,它就是ASCII字符的双字节编码的第一个字节。)我曾看到过这样做来编码最初被认为是单词的字符串,但后来的要求允许该值具有空格。为了不破坏现有代码(使用类似于strtok和sscanf的东西来识别以空格分隔的字段),该值使用这种变形的UTF-8而不是真正的UTF-8进行编码。
但是,您可能不需要担心这一点。如果您程序的输入使用该格式,则您的代码可能不打算在该点检测特殊编码的空格,因此忽略它是安全的。

是的,那将是用户的责任 - 不是我的。 - user1481860

2
是的,但请注意下面有关以这种方式处理非字节定向流的陷阱的警告。
对于UTF-8,任何连续字节总是以位10开头,使它们大于0x7f,因此不可能被误认为是ASCII空格。
您可以在以下表格中看到这一点:
Range              Encoding  Binary value
-----------------  --------  --------------------------
U+000000-U+00007f  0xxxxxxx  0xxxxxxx

U+000080-U+0007ff  110yyyxx  00000yyy xxxxxxxx
                   10xxxxxx

U+000800-U+00ffff  1110yyyy  yyyyyyyy xxxxxxxx
                   10yyyyxx
                   10xxxxxx

U+010000-U+10ffff  11110zzz  000zzzzz yyyyyyyy xxxxxxxx
                   10zzyyyy
                   10yyyyxx
                   10xxxxxx

您还可以看到,ASCII范围外的码点的非连续字节也具有高位设置,因此它们永远不会被误认为是空格。
有关更多详细信息,请参见wikipedia UTF-8
UTF-16和UTF-32首先不应该按字节处理。您应该始终处理单元本身,即16位或32位值。如果您这样做,您也将得到保障。如果您逐字节处理这些内容,则存在一个危险,即您可能会找到一个不是空格的0x20字节(例如,16位UTF-16值的第二个字节)。
对于UTF-16,由于该编码中的扩展字符是由代理对形成的,其各自的值在范围0xd800到0xdfff之间,因此这些代理对组件也不会被误认为是空格。
有关更多详细信息,请参见wikipedia UTF-16
最后,UTF-32(wikipedia链接在此)足够大,可以表示所有Unicode代码点,因此不需要特殊编码。

但对于UTF-16而言,非扩展范围仍由多个字节组成,难道其中一个不能是0D吗?UTF-32也一样吗? - user1481860
@oystein,是的,这就是我说你不应该逐字节处理它们的原因 - 澄清了。 - paxdiablo
@oystein,没问题,底线是你提出的方案对于UTF-8编码是安全的,但对于其他两种编码则不是。但我不确定我理解你的犹豫,大多数C编译器都会有本地的16位和32位数据类型可供使用,速度损失很小。然而,你比我更了解你的要求和限制,所以我不会试图猜测你的想法。 - paxdiablo
1
这更多关乎已经写好的代码,而不是可能会被写出来的代码。如下评论所述,为了获得UTF-16和UTF-32支持而重写和重新测试成千上万行的代码并不是一个好想法...如果我能够在不重写太多代码的情况下让Unicode支持正常工作,那么我就会尝试加入它。 - user1481860
@paxdiablo:毫无疑问,代理对在扫描字节时不会被误认为是空格,但这在逐字节扫描时是无关紧要的;0x20可以作为代理的低位字节被找到。一般来说,所有的代码点U+20xx和U+xx20都将被空格扫描捕获,同样地,U+0Axx和U+xx0A将导致换行符被错误地检测到。逐字节扫描空格对于UTF-16和UTF-32是完全无用的。顺便说一下,GB18030是UTF,逐字节扫描将起作用 :-) - John Machin

0

强烈建议在处理Unicode时不要针对字节进行操作。两个主要的平台(Java和.Net)本地支持Unicode,并提供一种机制来确定这些内容。例如,在Java中,您可以使用Character类的isSpace() / isSpaceChar() / isWhitespace()方法来处理您的用例。


3
哎呀,Java :) 我恐怕被一些相当恶劣的低级 C++ 代码所淹没了,所以我有点孤军奋战。如果我有其他选择,我会很快地采取它们。 - user1481860
1
很抱歉,重写和重新测试成千上万行代码以使用不同的库来获得UTF-16和UTF-32支持并不是一个实际的选择......不过这可能对看到这个问题的其他人有用。 - user1481860
2
如果Java真正本地支持Unicode,你会认为它的“char”可以(始终)容纳一个。但它不能,所以这只是一个可怕的补救措施。 - tchrist

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接