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

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

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

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

斜体部分是我无法理解的部分,因为从我的测试来看,它看起来像Java String肯定包含\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实现是否“已损坏”,因为它实际上不会throw IllegalArgumentException如果事实上在有效的Unicode字符串中禁止使用\uFFFF
  • 实际上有效的Unicode字符串不应该包含\uFFFF吗?
  • 如果这是真的,那么Java是否因为违反Unicode规范而被“破解”(对于大多数部分而言)允许String包含\uFFFF

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

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

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

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

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

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

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

如果这是真的,那么Java是否因为违反Unicode规范而被“破解”(对于大多数部分而言)允许String包含\ uFFFF?

实际上,非字符部分甚至建议使用U + FFFF作为哨兵值:

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

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

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

其中一些答案在此期间发生了变化。

Unicode联盟最近发布了勘误表9 ,阐明了非字符(包括U + FFFF)在Unicode字符串中的作用。 它声明虽然非字符用于内部使用,但它们可以合法地出现在Unicode字符串中。

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

因此:

  • “遍历成语”是否被打破? 是的,因为它对Unicode字符串中U + FFFF的有效性做出了错误的假设。
  • StringCharacterIterator实现是否“已损坏”,因为如果在有效的Unicode字符串中禁止\ uFFFF,它不会抛出exception? 由于U + FFFF有效,因此不适用于此处。 但是,当遇到由于其他原因而非法的文本(例如未配对的代理代码点,仍然是非法的)时,实现在发出错误信号时具有很大的灵活性(参见Unicode标准第3章中的符合性条款C10)。
  • 是否有效的Unicode字符串不应包含\ uFFFF? U + FFFF在有效的Unicode字符串中不是非法的。 但是U + FFFF保留为非字符,因此通常不会出现在有意义的文本中。 更正删除了非字符“永远不应该互换”的文本,更正说“任何时候Unicode字符串跨越API边界”,包括此处讨论的StringCharacterIterator API。
  • 如果这是真的,那么Java是否因为允许String包含\ uFFFF而违反Unicode规范而“被破坏”? java.lang.String的规范说“String表示UTF-16格式的字符串。” U + FFFF在Unicode字符串中是合法的,因此Java不允许U + FFFF在包含它的字符串中使用Unicode。

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

不严格按照Unicode,但它与Java的其他字符串处理接口不一致,并且不一致可能会产生非常不愉快的影响。 想想我们从字符串处理中获得的所有安全漏洞,而不是将\0视为终结符。

我强烈要避免使用CharacterIterator接口。

是的,CharacterIterator使用0xFFFF作为DONE值有点exception。 但从有效的文本处理的角度来看,这一切都是有道理的。

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

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

  • 抛出exception(太贵了),或者
  • 使用int作为返回类型,对于调用者来说生活更复杂。

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

底线是:

  • 如果你想要严格的Unicode字符串,那么不要使用String ,和
  • 如果要迭代包含0xFFFF值的Java字符串,则不要使用CharacterIterator。