有哪些方法可以避免 String.substring 返回具有无效 unicode 字符的子字符串?

Che*_*eng 19 java android

最近,只有我注意到,有可能substring返回带有无效 unicode 字符的字符串。

例如

public class Main {

    public static void main(String[] args) {
        String text = "_Salade verte";

        /* We should avoid using endIndex = 1, as it will cause an invalid character in the returned substring. */
        // 1 : ?
        System.out.println("1 : " + text.substring(0, 1));

        // 2 : 
        System.out.println("2 : " + text.substring(0, 2));

        // 3 : _
        System.out.println("3 : " + text.substring(0, 3));

        // 4 : _S
        System.out.println("4 : " + text.substring(0, 4));
    }
}
Run Code Online (Sandbox Code Playgroud)

我想知道,当用 修剪长字符串时String.substring,有哪些好方法可以避免返回的子字符串包含无效的unicode?

Bas*_*que 16

char过时的

\n

该类型自Java 2以来char一直是遗留类型,基本上已被破坏。作为16 位值,物理上无法表示大多数字符char

\n

您的发现表明该String#substring命令是char基于的。因此,您的代码中显示了问题。

\n

代码点

\n

相反,在处理单个字符时应使用代码点整数。

\n
int[] codePoints = "_Salade".codePoints().toArray() ;\n
Run Code Online (Sandbox Code Playgroud)\n
\n

[129382、95、83、97、108、97、100、101]

\n
\n

提取第一个字符\xe2\x80\x99s 代码点。

\n
int codePoint = codePoints[ 0 ] ;\n
Run Code Online (Sandbox Code Playgroud)\n
\n

129382

\n
\n

String为该代码点创建一个单字符对象。

\n
String firstCharacter = Character.toString( codePoint ) ; \n
Run Code Online (Sandbox Code Playgroud)\n
\n

\n

\n

您可以获取该代码点数的子集int

\n
int[] firstFewCodePoints = Arrays.copyOfRange( codePoints , 0 , 3 ) ;\n
Run Code Online (Sandbox Code Playgroud)\n

String从这些代码点创建一个对象。

\n
String s = \n    Arrays\n        .stream( firstFewCodePoints ) \n        .collect( StringBuilder::new , StringBuilder::appendCodePoint , StringBuilder::append )\n        .toString();\n
Run Code Online (Sandbox Code Playgroud)\n
\n

_S

\n
\n

或者我们可以使用 的构造函数String来获取数组的子集。

\n
String result = new String( codePoints , 0 , 3 ) ;\n
Run Code Online (Sandbox Code Playgroud)\n
\n

_S

\n
\n

请参阅在 IdeOne.com 上实时运行的代码

\n


MC *_*ror 5

Basil 的答案很好地表明您应该使用代码点而不是chars。

\n

AString内部不存储 Unicode 代码点,因此在不检查字符串的实际内容的情况下,无法知道哪些字符属于一起形成 Unicode 代码点。

\n

Unicode 感知子字符串

\n

这是一个支持 Unicode 的子字符串方法。由于codePoints()返回 an IntStream,我们可以利用skipandlimit方法来提取字符串的一部分。

\n
public static String unicodeSubstring(String string, int beginIndex, int endIndex) {\n    int length = endIndex - beginIndex;\n    int[] codePoints = string.codePoints()\n        .skip(beginIndex)\n        .limit(length)\n        .toArray();\n    return new String(codePoints, 0, codePoints.length);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

这就是上述代码片段中发生的情况。我们流式传输 Unicode 代码点,跳过第一个beginIndex字节并将流限制为endIndex \xe2\x88\x92 beginIndex,然后将 b 转换为int[]. 结果是 int 数组包含从beginIndexendIndex 的所有 Unicode 代码点所有 Unicode 代码点。

\n

最后,该类String包含一个很好的构造函数,可以Stringint[],因此我们使用它来获取字符串。

\n
\n

当然,您可以通过拒绝越界值来将该方法调整得更严格一些:

\n
if (endIndex < beginIndex) {\n    throw new IllegalArgumentException("endIndex < beginIndex");\n}\nint length = endIndex - beginIndex;\nint[] codePoints = string.codePoints()\n    .skip(beginIndex)\n    .limit(length)\n    .toArray();\nif (codePoints.length < length) {\n    throw new IllegalArgumentException(\n        "begin %s, end %s, length %s".formatted(beginIndex, endIndex, codePoints.length)\n    );\n}\nreturn new String(codePoints, 0, codePoints.length);\n
Run Code Online (Sandbox Code Playgroud)\n

Online demo

\n