有效的 Unicode 字符串可以包含 FFFF 吗?Java/CharacterIterator 坏了吗?

2022-09-01 18:28:57

以下是java.text.CharacterIterator文档的摘录:

  • 这定义了对文本进行双向迭代的协议。迭代器循环访问有界字符序列。[...]方法和用于迭代。它们返回 if [...] ,表示迭代器已到达序列的末尾。interfaceprevious()next()DONE

  • 静态最终字符 DONE:当迭代器到达文本的末尾或开头时返回的常量。该值是 ,“不是字符”值,不应出现在任何有效的 Unicode 字符串中\uFFFF

斜体部分是我难以理解的,因为从我的测试中,看起来Java肯定可以包含,并且它似乎没有任何问题,除了显然由于误报而中断的规定遍历习语(例如 当它没有真正“完成”时返回)。String\uFFFFCharacterIteratornext()'\uFFFF' == DONE

下面是一个片段来说明“问题”(另请参阅 ideone.com):

import java.text.*;
public class CharacterIteratorTest {

    // this is the prescribed traversal idiom from the documentation
    public static void traverseForward(CharacterIterator iter) {
       for(char c = iter.first(); c != CharacterIterator.DONE; c = iter.next()) {
          System.out.print(c);
       }
    }

    public static void main(String[] args) {
        String s = "abc\uFFFFdef";

        System.out.println(s);
        // abc?def

        System.out.println(s.indexOf('\uFFFF'));
        // 3
        
        traverseForward(new StringCharacterIterator(s));
        // abc
    }
}

这到底是怎么回事呢?

  • 规定的遍历习语是否“破碎”,因为它对?\uFFFF
  • 实现是否“损坏”,因为它没有例如 如果实际上在有效的 Unicode 字符串中是被禁止的?StringCharacterIteratorthrowIllegalArgumentException\uFFFF
  • 有效的 Unicode 字符串实际上不应该包含 吗?\uFFFF
  • 如果这是真的,那么Java是否因为违反Unicode规范而“损坏”了(在大多数情况下)允许无论如何都包含?String\uFFFF

答案 1

编辑(2013-12-17):Peter O.在下面提出了一个很好的观点,这使得这个答案是错误的。下面的旧答案,以确保历史准确性。


回答您的问题:

规定的遍历习语是否“破碎”,因为它对\uFFFF做出了错误的假设?

U+FFFF是所谓的非字符。来自 Unicode 标准的第 16.7 节

非字符是永久保留在 Unicode 标准中供内部使用的代码点。禁止在 Unicode 文本数据的开放交换中使用它们。

...

Unicode 标准预留了 66 个非字符码位。每个平面的最后两个码位是非字符:BMP 上的 U+FFFE 和 U+FFFF,平面 1 上的 U+1FFFE 和 U+1FFFF,依此类推,平面 16 上的 U+10FFFE 和 U+10FFFF,总共有 34 个码位。此外,BMP 中还有另外 32 个非字符码位的连续范围:U+FDD0。U+FDEF.

StringCharacterIterator 实现是否“损坏”,因为它不会引发非法参数异常,如果实际上 \uFFFF 在有效的 Unicode 字符串中是被禁止的?

差一点。允许应用程序以所需的任何方式在内部使用这些代码点。再次引用标准:

应用程序可以在内部自由使用这些非字符代码点中的任何一个,但不应尝试交换它们。如果在开放交换中接收到非字符,则不需要应用程序以任何方式解释它。但是,最好将其识别为非字符并采取适当的操作(例如将其替换为 U+FFFD 替换字符)以在文本中指出问题。不建议简单地从此类文本中删除非字符代码点,因为删除未解释的字符会导致潜在的安全问题。

因此,虽然您永远不应该遇到来自用户,另一个应用程序或文件的此类字符串,但如果您知道自己在做什么,则可以将其放入Java字符串中(这基本上意味着您不能在该字符串上使用CharactIterator。

有效的 Unicode 字符串实际上不应该包含 \uFFFF 吗?

如上所述,用于交换的任何字符串都不得包含它们。在您的应用程序中,您可以自由地以他们想要的任何方式使用它们。

当然,Java ,只是一个16位无符号整数并不真正关心它所包含的值。char

如果这是真的,那么Java是否因为违反Unicode规范而“损坏”了(在大多数情况下)允许字符串包含\uFFFF?

不。事实上,关于非字符的部分甚至建议使用U + FFFF作为哨兵值:

实际上,可以将非字符视为应用程序内部专用代码点。与第 16.5 节 “私人使用字符”中讨论的私有字符不同,私有字符是已分配的字符,旨在用于开放交换,但须通过私人协议进行解释,非字符是永久保留的(未分配的),除了其可能的应用程序内部私人用途之外没有任何解释。

U+FFFF 和 U+10FFFF。这两个非字符码位具有与特定 Unicode 编码形式的最大代码单元值相关联的属性。在 UTF-16 中,U+FFFF 与最大的 16 位代码单位值 FFFF16 相关联。U+10FFFF 与最大的法定 UTF-32 32 码单位值 10FFFF16 相关联。此属性使这两个非字符代码点作为哨兵对内部目的有用。例如,它们可用于指示列表的结尾,表示索引中保证大于任何有效字符值的值,等等。

CharacterIterator 遵循这一点,因为当没有更多字符可用时,它将返回 U+FFFF。当然,这意味着,如果您在应用程序中对该代码点有其他用途,则可以考虑为此目的使用不同的非字符,因为U + FFFF已经被采用 - 至少如果您使用的是CharactIterator。


答案 2

与此同时,其中一些答案发生了变化。

Unicode Consortium 最近发布了更正 9,阐明了非字符(包括 U+FFFF)在 Unicode 字符串中的作用。它指出,虽然非字符旨在供内部使用,但它们可以合法地出现在Unicode字符串中。

这意味着语句“该值为 \uFFFF,不应出现在任何有效的 Unicode 字符串中的'不是字符'值”现在不正确,因为 U+FFFF 可以出现在有效的 Unicode 字符串中。

因此:

  • StringCharacterIterator 实现是否“已损坏”,因为如果在有效的 Unicode 字符串中禁止 \uFFFF,它不会引发异常?由于 U+FFFF 是有效的,因此此处不适用。但是,当实现遇到由于其他原因而非法的文本时,例如未配对的代理代码点,这些代码点仍然是非法的,因此在发出错误信号方面具有广泛的灵活性(请参阅Unicode标准第3章中的一致性条款C10)。

  • 有效的 Unicode 字符串是否不应包含 \uFFFF?U+FFFF 在有效的 Unicode 字符串中并不违法。

    但是,U + FFFF保留为非字符,因此通常不会出现在有意义的文本中。更正删除了非字符“永远不应该互换”的文本,更正说“每当Unicode字符串跨越API边界时”都会发生这种情况,包括此处有争议的StringCharacterIterator API。

  • 如果这是真的,那么Java是否因为违反Unicode规范而“损坏”,因为允许字符串包含\uFFFF无论如何?的规范为“字符串表示 UTF-16 格式的字符串”。U + FFFF在Unicode字符串中是合法的,因此Java在包含它的字符串中允许U + FFFF并不违反Unicode。java.lang.String

通常,更高级别的协议可以在Unicode标准之上强加自己的规则,关于协议接受的文档中允许哪些字符的问题。例如,在 XML 规范中就是这种情况。通常,U+FFFF(和其他 Unicode 标量值)可以有效地出现在文本字符串中,除非更高级别的协议(如 XML)另有指定。事实上,目前(截至 2021 年 11 月 15 日)正在努力限制在某些编程语言(如 Rust)中使用 Unicode 双向覆盖字符,以减少由于视觉混淆而导致的安全攻击。