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


149

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


3
它的UTF-16术语,这里解释:download.oracle.com/javase/6/docs/api/java/lang/...
WKL

1
该方法有很多问题:它应该反转完整的字符ᴀᴋᴀ代码点- 而不是单独的部分,代码单元。问题在于,特定的传统方法仅适用于单个char单元,而不适用于代码点,而这正是您要组成 String的代码点,而不仅仅是char单元。太糟糕了,Java不允许您使用OO来解决此问题,但是String该类和这些StringBuffer类均已实现final。说,这不是对被杀害的委婉说法吗?:)
tchrist 2011年

2
@tchrist文档(和源代码)说它确实是作为一串代码点反转的。(大概1.0.2并没有做到这一点,而且这些天你永远都不会做出这样的改变。)
Tom Hawtin-11年

Answers:


128

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

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

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

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


4
它获得最多的票数,但没有提供单个代码示例。关于如何实际使用它,也没有任何答案。这就是为什么人们对此表示不满。
乔治·泽维尔

57

早期的Java版本使用16位char数据类型表示Unicode字符。当时的设计很有意义,因为所有Unicode字符的值都小于65,535(0xFFFF),可以用16位表示。但是,后来Unicode将最大值增加到1,114,111(0x10FFFF)。由于16位值太小而无法表示Unicode 3.1版中的所有Unicode字符,因此UTF-32编码方案采用了32位值(称为代码点)。但是为了有效使用内存,16位值比32位值更可取,因此Unicode引入了一种新设计,可以继续使用16位值。在UTF-16编码方案中采用的此设计将1,024个值分配给16位低代理(在U + D800至U + DBFF范围内),并将另外1,024个值分配给16位低代理(在U + DC00范围内)到U + DFFF)。


7
我喜欢这个比接受的答案更好,因为它解释了Unicode 3.1如何从原始65535中保留1024 + 1024(高+低)值,以获取1024 * 1024新值,并且没有附加要求解析器从a开头开始串。
埃里克·赫斯特

1
我不喜欢这个答案,因为它暗示UTF-16是内存使用效率最高的Unicode编码。UTF-8存在,并且不会将大多数文本呈现为两个字节。UTF-16今天被广泛使用,因为Microsoft在UTF-32出现之前就选择了它,而不是为了提高内存效率。大约只有您一次真正想要 UTF-16是当你在Windows上做了很多的文件处理,并因此同时在读取写入了很多。否则,UTF-32用于高速(b / c恒定偏移量)或UTF-8用于低内存(b / c至少1个字节)
Fund Monica's Lawsuit

23

该文档所说的是,无效的UTF-16字符串在调用该reverse方法后可能变得有效,因为它们可能是有效字符串的反向。代理对(在此处讨论)是UTF-16中的一对16位值,它们对单个Unicode代码点进行编码。低和高替代是该编码的两半。


6
澄清。字符串必须在“ true”字符(也称为“ graphemes”或“ text elements”)上颠倒。单个“字符”代码点可以是一个或两个“字符”块(代理对),而字素可以是这些代码点中的一个或多个(即基本字符代码加上一个或多个组合字符代码,每个字符代码可以是一个或两个16位块或“字符”长)。因此,单个字素可以是三个组合字符,每个字符两个长,总共6个“字符”。反转整个字符串时,必须将所有6个“字符”按顺序排列在一起(即不可颠倒)。
Triynko

4
因此,“ char”数据类型颇具误导性。“字符”是一个宽松的术语。“字符”类型实际上只是UTF16块的大小,我们将其称为字符是因为出现的代理对相对稀少(即,它通常代表整个字符代码点),因此“字符”实际上是指单个Unicode代码点,但是有了组合字符,您可以使一系列字符显示为单个“字符/字素/文本元素”。这不是航天科技; 概念很简单,但是语言令人困惑。
Triynko

在开发Java时,Unicode还处于起步阶段。在Unicode获得代理对之前,Java出现了大约5年,因此当时的16位char非常合适。现在,使用UTF-8和UTF-32比使用UTF-16更好。
乔纳森·鲍德温

23

向该帖子的以上答案中添加更多信息。

经过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将“🌉”表示为String 并检查相等性。

"🌉".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.迭代Unicode字符串与的BreakIterator
6.排序字符串使用Unicode java.text.Collat​​or中
7.字符的toUpperCase()toLowerCase(),方法不应该被使用,相反,使用字符串大写和特定地点的小写。
8. Character.isLetter(char ch)不支持,更好地使用Character.isLetter(int codePoint),对于methodName(char ch)Character类中的每个方法,都会有methodName(int codePoint)可以处理补充字符的类型。
9.在中指定字符集String.getBytes(),将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


2
专门登录以投票支持该答案(我的意思是将窗口从隐身窗口更改为正常窗口:P)。对于一个noob最好的诠释
的N- JOY

1
谢谢!我很高兴为您提供了帮助,但原始帖子的作者值得所有赞赏。
dkb

很好的例子!我也登录以对其进行投票:)再一次,这让我(再次)认为我真的不明白为什么Java会在其代码中保留已知的错误。我完全尊重他们不想破坏现有的代码,但是,拜托……解决这些错误已经浪费了几个小时?如果损坏,请修复,该死!
Franz D.


6

小序言

  • 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个可能的代码值。对于“平面0”(也称为BPM或“基本多语言平面”)而言,这是正确的,它今天包含55,445个65536个代码点。BPM涵盖了世界上几乎所有人类语言,包括中日韩符号(CJK)。

时间的流逝和新的亚洲字符集的添加,仅中国符号就花费了超过70,000点。现在,甚至在标准😺中都有表情符号点。添加了新的16个“其他” 飞机。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个字节。为UTF-16分配1024点U + D800–U + DBFF(称为“高替代”);将1024个符号U + DC00–U + DFFF(称为低代理)分配给UTF-16。

    通过这些更改,BPM在UTF-16中覆盖了1个16位块,而所有“补充字符”都覆盖了代理对,每个代理对显示2个块,每个16位,总共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。
  • 代理解决方案在不同编码之间的转换中打开了许多安全性问题,其中大多数都得到了很好的处理。

许多历史细节被压制以遵循主题⚖。
最新的Unicode标准可在http://www.unicode.org/versions/latest中找到


3

代理对是UTF-16中的两个“代码单元”,它们组成一个“代码点”。Java文档指出,相反之后,这些“代码点”将仍然有效,并且其“代码单元”的顺序正确。它进一步指出,两个不成对的代理代码单元可以颠倒并形成有效的代理对。这意味着,如果存在未配对的代码单元,则反向的反向可能不一样!

但是请注意,文档没有对Graphemes进行任何说明-Graphemes是组合在一起的多个代码点。这意味着e和随之而来的重音可能仍会切换,因此将重音放在e之前。这意味着,如果在e之前有另一个元音,则可能得到e上的重音。

kes!

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.