什么是 Java 中的“代理对”?

Posted

技术标签:

【中文标题】什么是 Java 中的“代理对”?【英文标题】:What is a "surrogate pair" in 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 没有这样做,而且这些天你永远不会有这样的行为改变。) 【参考方案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://***.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#objectshttps://www.online-toolz.com/tools/text-unicode-entities-convertor.phphttps://www.ibm.com/developerworks/library/j-unicode/index.htmlhttps://www.oracle.com/technetwork/articles/javaee/supplementary-142654.html

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

【讨论】:

【参考方案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 决定

    将 Unicode 限制为 17 个平面 × 每个平面 65 536 个字符 = 1 114 112 个最大点。 现在的 UTF-32,以前称为 UCS-4,为每个代码点保存 32 位并覆盖所有平面。 继续使用 UTF-8 作为动态编码,将 UTF-8 限制为每个代码点最多 4 个字节,即每个点 1 到 4 个字节。 弃用 UCS-2 基于 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 上的重音。

哎呀!

【讨论】:

以上是关于什么是 Java 中的“代理对”?的主要内容,如果未能解决你的问题,请参考以下文章

聊聊Vue中的数据代理

Java 设计模式之代理 学习与掌握

Spring学习记录

Java设计模式:代理模式

java 反射之静态and动态代理

VUE前进之路使用数据代理,计算属性与监视属性的妙用