将字符串与 Java/Kotlin 中等效但不同的 Unicode 代码点进行比较
Comparing Strings with equivalent but different Unicode code points in Java/Kotlin
我 运行 在用不同编码器比较两个字符串时遇到了问题。我的代码实际上是在 Kotlin 中,但它在 JVM 上是 运行,并且有效地使用了 Java 的 String 实现。另外,我的问题更笼统,我的实际代码不会受到关注。
问题是我有两个字符串,比方说 a
和 b
,其中
a = "something something äöü something"
b = "äöü"
你会期望 a.contains(b)
returns true
,如果你像上面显示的那样检索你的字符串就是这种情况。但就我而言,字符串来自不同的来源,并且碰巧有不同的编码器。字符串 a
具有编码器 1
,即 UTF16
,而字符串 b
具有编码器 0
,即 LATIN1
。在这种情况下,a.contains(b)
return 为假。现在您可能已经注意到我包含了特殊字符(ä
、ö
和 ü
),因为根据我的调试,这是比较失败的地方。
当我在发生 a.contains(b)
调用的堆栈帧时,两个字符串都正确显示在我的调试器 (IntelliJ IDEA Ultimate 2020.2) 中。但是,如果我随后进入比较函数,我注意到在 java.lang.StringLatin1.regionMatchesCI_UTF16()
中,字节数组逐个字符转换回字符,b
的特殊字符现在不正确(ä
-> a
、ö
-> o
、ü
-> u
)。当然,比较失败了。
现在正如我所说,两个字符串最初都在调试器中正确显示,所以信息必须在某个地方。我的问题是:我需要做什么才能让 a.contains(b)
像预期的那样调用 return true
?
编辑:
我确定问题出在具有两个不同编码器的字符串上。然而,尽管不同的编码人员 暗示 不同的编码在起作用,但它们并不是问题的 来源 。一般来说,不同的编码器不会影响.equals()
、.contains()
或类似调用的结果。 @O运行geDog 指出了这一点,同时也暗示我实际上最终得到了同一角色的两种不同表示形式,事实确实如此。而且,我的问题仍然是一样的:如何比较这两个“语义上”相同但某些字符的表示不同的字符串?
- Java11(11.0.2,openJDK 11)
- Kotlin/JVM 1.4.0
- IntelliJ IDEA 旗舰版 2020.2
忽略 String
的内部细节。就您而言,它没有编码,它只存储字符序列(或 Kotlin 文档描述的“代码点单元”)。
我猜你的一个字符串(即 Latin-1)使用字符 U+00E4
(ä),另一个使用序列 U+0061
U+0308
(ä) .您可以使用 toCharArray()
.
进行验证
为了能够明智地比较这些字符串,有 class java.text.Normalizer
:
Normalizer.normalize(a, Form.NFKD).contains(Normalizer.normalize(b, Form.NFKD))
或者,确保您收到的任何字符串都已采用推荐的 NFC
格式。
我 运行 在用不同编码器比较两个字符串时遇到了问题。我的代码实际上是在 Kotlin 中,但它在 JVM 上是 运行,并且有效地使用了 Java 的 String 实现。另外,我的问题更笼统,我的实际代码不会受到关注。
问题是我有两个字符串,比方说 a
和 b
,其中
a = "something something äöü something"
b = "äöü"
你会期望 a.contains(b)
returns true
,如果你像上面显示的那样检索你的字符串就是这种情况。但就我而言,字符串来自不同的来源,并且碰巧有不同的编码器。字符串 a
具有编码器 1
,即 UTF16
,而字符串 b
具有编码器 0
,即 LATIN1
。在这种情况下,a.contains(b)
return 为假。现在您可能已经注意到我包含了特殊字符(ä
、ö
和 ü
),因为根据我的调试,这是比较失败的地方。
当我在发生 a.contains(b)
调用的堆栈帧时,两个字符串都正确显示在我的调试器 (IntelliJ IDEA Ultimate 2020.2) 中。但是,如果我随后进入比较函数,我注意到在 java.lang.StringLatin1.regionMatchesCI_UTF16()
中,字节数组逐个字符转换回字符,b
的特殊字符现在不正确(ä
-> a
、ö
-> o
、ü
-> u
)。当然,比较失败了。
现在正如我所说,两个字符串最初都在调试器中正确显示,所以信息必须在某个地方。我的问题是:我需要做什么才能让 a.contains(b)
像预期的那样调用 return true
?
编辑:
我确定问题出在具有两个不同编码器的字符串上。然而,尽管不同的编码人员 暗示 不同的编码在起作用,但它们并不是问题的 来源 。一般来说,不同的编码器不会影响.equals()
、.contains()
或类似调用的结果。 @O运行geDog 指出了这一点,同时也暗示我实际上最终得到了同一角色的两种不同表示形式,事实确实如此。而且,我的问题仍然是一样的:如何比较这两个“语义上”相同但某些字符的表示不同的字符串?
- Java11(11.0.2,openJDK 11)
- Kotlin/JVM 1.4.0
- IntelliJ IDEA 旗舰版 2020.2
忽略 String
的内部细节。就您而言,它没有编码,它只存储字符序列(或 Kotlin 文档描述的“代码点单元”)。
我猜你的一个字符串(即 Latin-1)使用字符 U+00E4
(ä),另一个使用序列 U+0061
U+0308
(ä) .您可以使用 toCharArray()
.
为了能够明智地比较这些字符串,有 class java.text.Normalizer
:
Normalizer.normalize(a, Form.NFKD).contains(Normalizer.normalize(b, Form.NFKD))
或者,确保您收到的任何字符串都已采用推荐的 NFC
格式。