25

这是文档的摘录java.text.CharacterIterator

  • interface定义了文本双向迭代的协议。迭代器迭代有界的字符序列。[...] 方法previous()next()用于迭代。它们返回DONEif [...],表示迭代器已到达序列的末尾。

  • static final char DONE:当迭代器到达文本的结尾或开头时返回的常量。该值是不应出现在任何有效 Unicode 字符串中\uFFFF的“非字符”值。

斜体部分是我难以理解的部分,因为从我的测试来看,它看起来像 JavaString肯定可以包含\uFFFF,并且似乎没有任何问题,除非显然使用规定的CharacterIterator遍历习语会中断,因为误报(例如,当它没有真正“完成”时next()返回)。'\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
  • 实现是否StringCharacterIterator“损坏”,因为它没有例如if 实际上在throw有效的 Unicode 字符串中是被禁止的?IllegalArgumentException\uFFFF
  • 有效的 Unicode 字符串不应该包含真的是真的\uFFFF吗?
  • 如果这是真的,那么 Java 是否因(在大多数情况下)允许String包含而违反 Unicode 规范而被“破坏” \uFFFF
4

4 回答 4

29

编辑(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 实现是否“损坏”,因为如果实际上在有效的 Unicode 字符串中禁止 \uFFFF,它不会抛出 IllegalArgumentException?

不完全的。允许应用程序以任何他们想要的方式在内部使用这些代码点。再次引用标准:

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

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

有效的 Unicode 字符串不应该包含 \uFFFF 是真的吗?

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

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

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

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

实际上,非字符可以被认为是应用程序内部的私有代码点。与第 16.5 节中讨论的私有字符不同,私有字符是分配的字符,旨在用于开放交换,受私人协议的解释,非字符被永久保留(未分配)并且在外部没有任何解释他们可能的应用程序内部私人用途。

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

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

于 2010-08-14T09:09:04.937 回答
20

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

Unicode 联盟最近发布了更正 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 通过允许 String 包含 \uFFFF 是否违反了 Unicode 规范而被“破坏”了?规范java.lang.String说“字符串表示 UTF-16 格式的字符串”。U+FFFF 在 Unicode 字符串中是合法的,因此 Java 不会违反 Unicode,因为它允许 U+FFFF 在包含它的字符串中。

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

于 2013-05-18T01:41:59.887 回答
3

StringCharacterIterator 实现是否“损坏”,因为如果实际上在有效的 Unicode 字符串中禁止 \uFFFF,它不会抛出 IllegalArgumentException?

不严格按照 Unicode,但它与 Java 的其他字符串处理接口不一致,这种不一致可能会产生非常不愉快的影响。想想我们从字符串处理中遇到的所有安全漏洞,这些安全漏洞确实与不\0视为终止符。

我会强烈避免使用CharacterIterator界面。

于 2010-08-19T14:17:04.343 回答
2

是的,CharacterIterator 使用 0xFFFF 作为 DONE 值有点反常。但从高效文本处理的角度来看,这一切都说得通。

String 类不禁止 0xFFFF “非字符”和其他保留或未映射的 Unicode 代码点。这样做需要 String 构造函数检查每个提供的char值。处理包含在未来(相对于 JVM)版本的 Unicode 中定义的 Unicode 代码点的文本也会出现问题。

另一方面,CharacterIterator 接口被设计为通过调用一个 just 方法来允许迭代;即next()。他们决定使用一个可区分的char值来表示“不再”,因为其他选择是:

  • 抛出异常(这太昂贵了),或者
  • 使用int作为调用者生活更复杂的返回类型。

如果 CharacterIterator 用于“真正的”Unicode 文本,那么不能包含 0xFFFF 的事实就不是问题。有效的 Unicode 文本不包含此代码点。(事实上​​,将 0xFFFF 保留为非字符的原因是为了支持将 Unicode 文本表示为由非字符值终止的字符串的应用程序。使用 0xFFFF 作为字符会完全破坏这一点。)

底线是:

  • 如果您想要严格的 Unicode 字符串,请不要使用String, 和
  • 如果要迭代包含 0xFFFF 值的 Java 字符串,则不要使用 CharacterIterator。
于 2010-08-14T09:45:34.420 回答