正则表达式 \p{Cs} 与 Java 16 中的符号不​​匹配

San*_*tos 4 java regex java-11 java-16

有谁知道为什么正则表达式\p{Cs}Java 16中的符号不​​匹配?它曾经在 Java 11 中匹配它。

爪哇 11

jshell 
|  Welcome to JShell -- Version 11.0.7
|  For an introduction type: /help intro

jshell> import java.util.regex.*

jshell> var text = new StringBuilder().appendCodePoint(55622).appendCodePoint(56380)
text ==> 

jshell> Pattern.compile("\\p{Cs}").matcher(text).find()
$3 ==> true
Run Code Online (Sandbox Code Playgroud)

爪哇 16

INFO: Created user preferences directory.
|  Welcome to JShell -- Version 16.0.1
|  For an introduction type: /help intro

jshell> import java.util.regex.*

jshell> var text = new StringBuilder().appendCodePoint(55622).appendCodePoint(56380)
text ==> 

jshell> Pattern.compile("\\p{Cs}").matcher(text).find()
$3 ==> false
Run Code Online (Sandbox Code Playgroud)

Hol*_*ger 7

首先,您的“符号”具有代码点 399420,Unicode 标准尚未分配该代码点(尚未分配),因此,如果您在这里看到有用的东西,则这是您系统的非标准行为。

您构造字符串的方式在语义上是不正确的,但恰好创建了预期的字符串。由于历史原因,Java 的 API 以 UTF-16 表示为中心。

当您使用两个代理字符定义符号时,即

var text = "\uD946\uDC3C";
System.out.println(text.codePointAt(0));
Run Code Online (Sandbox Code Playgroud)

你会得到

var text = "\uD946\uDC3C";
System.out.println(text.codePointAt(0));
Run Code Online (Sandbox Code Playgroud)

另一方面,当您使用

399420
Run Code Online (Sandbox Code Playgroud)

你会得到

var text = new StringBuilder().appendCodePoint(399420);
text.chars().forEach(c -> System.out.printf("\\u%04X", c));
System.out.println();
Run Code Online (Sandbox Code Playgroud)

换句话说,两个代理 UTF-16char单元的序列\uD946\uDC3C相当于单个代码点399420。从概念上讲,字符串由单个代码点组成,换句话说,

\uD946\uDC3C
Run Code Online (Sandbox Code Playgroud)

将打印

System.out.println(text.codePointCount(0, text.length()) + " codepoint(s)");
System.out.println(text.codePointAt(0));
System.out.println("type " + Character.getType(text.codePointAt(0)));
Run Code Online (Sandbox Code Playgroud)

在任一情况下。该类型0指示此代码点未分配。

appendCodePoint用于将两个 UTF-16 单元附加到StringBuilder,但由于此方法以与 UTF-16 单元相同的方式处理BMP的代码点,因此它也恰好构造了相同的字符串。

由于代码点的类别是“未分配”,它不应该是“代理”,因此\p{Cs}永远不应该在这里找到匹配项。在处理有效的 Unicode 字符串时,您永远不应遇到此类别,因为它只能匹配不能被解释为BMP之外的代码点的悬空代理字符。

但是存在错误JDK-8247546,模式匹配不会正确跳过补充字符。在 Java 16 之前,正则表达式引擎确实正确处理了位置 0 处的代码点,但只前进了一个char位置,因此在单独查看char位置时发现了一个悬空的代理字符1

我们可以使用

1 codepoint(s)
399420
type 0
Run Code Online (Sandbox Code Playgroud)

在 JDK 16 之前打印“在 1 处找到匹配项”,这是错误的,因为1char位置01.

此错误已在 JDK 16 中修复。因此,现在该字符串被视为“未分配”类别的单个代码点。当然,这个类别在未来可能会再次发生变化。但它永远不应该是“代理”。