【问题标题】:What is a "surrogate pair" in Java?什么是 Java 中的“代理对”?
【发布时间】:2011-08-19 16:02:29
【问题描述】:

我正在阅读StringBuffer 的文档,尤其是reverse() 方法。该文档提到了一些关于代理对的内容。在这种情况下,什么是代理对?什么是 lowhigh 代理?

【问题讨论】:

  • 这是 UTF-16 术语,在此解释:download.oracle.com/javase/6/docs/api/java/lang/…
  • 这种方法有问题:它应该反转完整的字符 ᴀᴋᴀ 代码点 - 而不是 将它们分开,ᴀᴋᴀ 代码单元。错误在于,该特定遗留方法仅适用于单个字符单元而不是代码点,这是您 想要 Strings 组成的,而不仅仅是字符单元。太糟糕了,Java 不允许您使用 OO 来解决这个问题,但 String 类和 StringBuffer 类都已被 final 化。话说,这不是被杀的委婉说法吗? :)
  • @tchrist 文档(和源代码)说它确实反转为一串代码点。 (大概 1.0.2 没有这样做,而且这些天你永远不会有这样的行为改变。)

标签: java unicode utf-16 surrogate-pairs


【解决方案1】:

术语“代理对”是指在 UTF-16 编码方案中对具有高代码点的 Unicode 字符进行编码的方法。

在 Unicode 字符编码中,字符被映射到 0x0 到 0x10FFFF 之间的值。

在内部,Java 使用 UTF-16 编码方案来存储 Unicode 文本字符串。在 UTF-16 中,使用 16 位(两字节)代码单元。由于 16 位只能包含从 0x0 到 0xFFFF 的字符范围,因此会使用一些额外的复杂性来存储高于此范围(0x10000 到 0x10FFFF)的值。这是使用称为代理的代码单元对完成的。

代理代码单元位于称为“高代理”和“低代理”的两个范围内,具体取决于它们是否允许在两个代码单元序列的开头或结尾。

【讨论】:

    【解决方案2】:

    早期的 Java 版本使用 16 位 char 数据类型表示 Unicode 字符。这种设计在当时是有意义的,因为所有 Unicode 字符的值都小于 65,535 (0xFFFF),并且可以用 16 位表示。然而,后来,Unicode 将最大值增加到 1,114,111 (0x10FFFF)。由于 16 位值太小,无法表示 Unicode 版本 3.1 中的所有 Unicode 字符,因此 32 位值(称为代码点)被用于 UTF-32 编码方案。 但是为了有效地使用内存,16 位值优于 32 位值,因此 Unicode 引入了一种新设计以允许继续使用 16 位值。该设计采用 UTF-16 编码方案,将 1,024 个值分配给 16 位高代理项(在 U+D800 到 U+DBFF 范围内),将另外 1,024 个值分配给 16 位低代理项(在 U+DC00 范围内)到 U+DFFF)。它使用高代理后跟低代理(代理对)来表示(1,024 和 1,024 的乘积)1,048,576 (0x100000) 值介于 65,536 (0x10000) 和 1,114,111 (0x10FFFF) 之间。

    【讨论】:

    • 我比接受的答案更喜欢这个,因为它解释了 Unicode 3.1 如何从原始 65535 中保留 1024 + 1024(高 + 低)值以获得 1024 * 1024 新值,没有附加要求解析器从字符串的开头开始。
    • 我不喜欢这个暗示 UTF-16 是内存效率最高的 Unicode 编码的答案。 UTF-8 存在,并且 将大多数文本呈现为两个字节。今天主要使用 UTF-16,因为微软在 UTF-32 出现之前就选择了它,而不是为了提高内存效率。您真正想要 UTF-16 的唯一一次是当您在Windows 上进行大量文件处理,因此同时读取 写入它很多.否则,UTF-32 用于高速(b/c 常量偏移)或 UTF-8 用于低内存(b/c 最小 1 字节)
    【解决方案3】:

    this 帖子的上述答案中添加更多信息。

    在 Java-12 中测试,应该适用于 5 以上的所有 Java 版本。

    这里提到:https://stackoverflow.com/a/47505451/2987755,
    无论哪个字符(其 Unicode 高于 U+FFFF)都表示为代理对,Java 将其存储为一对 char 值,即单个 Unicode 字符表示为两个相邻的 Java 字符。
    正如我们在以下示例中看到的那样。
    1.长度:

    "?".length()  //2, Expectations was it should return 1
    
    "?".codePointCount(0,"?".length())  //1, To get the number of Unicode characters in a Java String  
    

    2。平等:
    使用 Unicode \ud83c\udf09 将“?”表示为字符串,如下所示并检查相等性。

    "?".equals("\ud83c\udf09") // true
    

    Java 不支持 UTF-32

    "?".equals("\u1F309") // false  
    

    3。您可以将 Unicode 字符转换为 Java 字符串

    "?".equals(new String(Character.toChars(0x0001F309))) //true
    

    4。 String.substring() 不考虑补充字符

    "??".substring(0,1) //"?"
    "??".substring(0,2) //"?"
    "??".substring(0,4) //"??"
    

    为了解决这个问题,我们可以使用String.offsetByCodePoints(int index, int codePointOffset)

    "??".substring(0,"??".offsetByCodePoints(0,1) // "?"
    "??".substring(2,"??".offsetByCodePoints(1,2)) // "?"
    

    5。使用 BreakIterator 迭代 Unicode 字符串
    6. 使用 Unicode 对字符串进行排序java.text.Collator
    7.不要使用字符的toUpperCase(),toLowerCase(),方法,而是使用特定语言环境的大写和小写字符串。
    8.Character.isLetter(char ch)不支持,最好使用Character.isLetter(int codePoint),对于Character类中的每个methodName(char ch)方法,都会有methodName(int codePoint)的类型,可以处理补充字符。
    9.在String.getBytes()中指定charset,将Bytes转换为String,InputStreamReaderOutputStreamWriter

    参考:
    https://coolsymbol.com/emojis/emoji-for-copy-and-paste.html#objects
    https://www.online-toolz.com/tools/text-unicode-entities-convertor.php
    https://www.ibm.com/developerworks/library/j-unicode/index.html
    https://www.oracle.com/technetwork/articles/javaee/supplementary-142654.html

    有关示例的更多信息 image1 image2
    其他值得探索的术语:NormalizationBiDi

    【讨论】:

      【解决方案4】:

      该文档的意思是,在调用 reverse 方法后,无效的 UTF-16 字符串可能会变得有效,因为它们可能是有效字符串的反面。代理对(已讨论 here)是一对 UTF-16 中的 16 位值,它们编码单个 Unicode 代码点;低代理和高代理是该编码的两半。

      【讨论】:

      • 澄清。字符串必须在“真”字符(也称为“字形”或“文本元素”)上反转。单个“字符”代码点可以是一个或两个“字符”块(代理对),而字素可以是这些代码点中的一个或多个(即基本字符代码加上一个或多个组合字符代码,每个可以是一个或两个 16 位块或“字符”长)。因此,单个字素可以是三个组合字符,每两个“字符”长,总共 6 个“字符”。反转整个字符串时,所有 6 个“字符”必须按顺序(即不反转)保持在一起。
      • 因此“char”数据类型具有误导性。 “字符”是一个松散的术语。 “char”类型实际上只是 UTF16 块大小,我们称它为字符,因为代理对的出现相对较少(即它通常代表一个完整的字符代码点),所以“字符”实际上是指单个 unicode 代码点,但随后使用组合字符,您可以拥有显示为单个“字符/字素/文本元素”的字符序列。这不是航天科技;概念很简单,但语言很混乱。
      • 在开发 Java 时,Unicode 还处于起步阶段。 Java 在 Unicode 出现代理对之前已经存在了大约 5 年,所以当时 16 位 char 非常适合。现在,使用 UTF-8 和 UTF-32 比使用 UTF-16 要好得多。
      【解决方案5】:

      小序言

      • Unicode 表示代码点。根据 Unicode 标准,每个代码点都可以编码为 8 位、16 位或 32 位块。

      • 在 3.1 版之前,主要使用的是 8 位编码(称为 UTF-8)和 16 位编码(称为 UCS-2 或“用 2 个八位字节编码的通用字符集”)。 UTF-8 将 Unicode 点编码为 1 字节块的序列,而 UCS-2 总是占用 2 个字节:

        A = 41 - 使用 UTF-8 的 8 位块
        A = 0041 - 使用 UCS-2 的 16 位块
        Ω = CE A9 - 两个使用 UTF-8 的 8 位块
        Ω = 03A9 - 一个使用 UCS-2 的 16 位块

      问题

      联盟认为 16 位足以涵盖任何人类可读的语言,这给出了 2^16 = 65536 个可能的代码值。对于今天的 65536 个代码点中的 55,445 个平面 0,也称为 BMP 或基本多语言平面,情况就是如此。 BMP 几乎涵盖了世界上所有人类语言,包括中日韩符号 (CJK)。

      随着时间的推移,新的亚洲字符集被添加,仅中文符号就占据了 70,000 多点。现在,甚至还有 Emoji points 作为标准的一部分?。添加了新的 16 个“附加”Planes。 UCS-2 房间不足以覆盖比 Plane-0 更大的任何东西。

      Unicode 决定

      1. 将 Unicode 限制为 17 个平面 × 每个平面 65 536 个字符 = 1 114 112 个最大点。
      2. 现在的 UTF-32,以前称为 UCS-4,为每个代码点保存 32 位并覆盖所有平面。
      3. 继续使用 UTF-8 作为动态编码,将 UTF-8 限制为每个代码点最多 4 个字节,即每个点 1 到 4 个字节。
      4. 弃用 UCS-2
      5. 基于 UCS-2 创建 UTF-16。使 UTF-16 动态化,因此每点占用 2 个字节或 4 个字节。将 1024 个点 U+D800–U+DBFF,称为 High Surrogates,分配给 UTF-16;将 1024 个符号 U+DC00–U+DFFF,称为 Low Surrogate,分配给 UTF-16。

      通过这些更改,BMP 被 UTF-16 中的 1 个 16 位块覆盖,而所有“补充字符”都被 代理对 覆盖,每个 16 位呈现 2 个块,总共 1024x1024 = 1 048 576 点。

      高代理在低代理之前。任何与此规则的偏差都被视为错误编码。例如,没有对的代理不正确,低代理站在高代理之前是不正确的。

      ?,'MUSICAL SYMBOL G CLEF',以 UTF-16 编码为一对代理项 0xD834 0xDD1E(2 x 2 字节),
      在 UTF-8 中为 0xF0 0x9D 0x84 0x9E(4 x 1 字节),
      在 UTF-32 中为 0x0001D11E(1 x 4 字节)。

      现状

      • 虽然根据标准,代理项仅专门分配给 UTF-16,但历史上一些 Windows 和 Java 应用程序使用 UTF-8 和 UCS-2 点,现在保留给代理项范围。
        为了支持具有不正确 UTF-8/UTF-16 编码的旧版应用程序,创建了一个新标准 WTF-8,即摆动转换格式。它支持任意代理点,例如非配对代理或不正确的序列。如今,有些产品不符合标准,将 UTF-8 视为 WTF-8。
      • 代理解决方案打开了一些security problems,以及尝试使用“非法代理对”。

      许多历史细节被隐藏以跟随主题⚖。
      最新的 Unicode 标准可以在http://www.unicode.org/versions/latest找到。

      【讨论】:

      • 您的“安全问题”链接已损坏。
      • 谢谢@Indolering,我没有找到旧链接,它是基于UCS2到UTF16的博客系列:archives.miloush.net/michkap/archive/2009/06/10/9723321.html。用链接更新了文本。
      • 非常好的答案,我认为它应该得到更多的选票。一个错字是 BPM,应该是 BMP(基本多语言平面)?
      【解决方案6】:

      代理对是指 UTF-16 对某些字符进行编码的方式,请参阅http://en.wikipedia.org/wiki/UTF-16/UCS-2#Code_points_U.2B10000..U.2B10FFFF

      【讨论】:

      • “字符”就是这样一个加载项。
      • Unicode 中没有字符,但有代码点。每个代码点可以呈现为零到多个字符。
      【解决方案7】:

      代理对是 UTF-16 中的两个“代码单元”,它们构成一个“代码点”。 Java 文档声明这些“代码点”仍然有效,它们的“代码单元”顺序正确,反之亦然。它进一步指出,两个未配对的代理代码单元可以反转并形成有效的代理对。也就是说,如果有不成对的代码单元,那么有可能反向的可能不一样!

      但请注意,文档中没有提到 Graphemes——它是多个代码点的组合。这意味着 e 和伴随它的重音仍然可以切换,因此将重音放在 e 之前。这意味着如果在 e 之前有另一个元音,它可能会得到 e 上的重音。

      哎呀!

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-11-06
        • 2016-11-17
        • 1970-01-01
        • 1970-01-01
        • 2010-11-02
        • 2011-06-22
        • 2018-02-03
        相关资源
        最近更新 更多