Один китайский символ определяется как длина 2 в Java/Scala String

Я пытаюсь разделить все китайские символы из String, но я столкнулся с странной ситуацией для персонажа 𥑮

scala> "𥑮"
res1: String = 𥑮

scala> res1.length
res2: Int = 2

scala> res1.getBytes
res3: Array[Byte] = Array(-16, -91, -111, -82)

scala> res1(0)
res4: Char = ?

scala> res1(1)
res5: Char = ?

Это один символ, но Java/ Scala определяет его как два неизвестных символа. И обычно я вижу, что китайский символ занимает три байта в UTF-8, но этот символ занимает четыре.

Следовательно, я не могу разбить String и найти этот единственный символ. Хуже того, при использовании myString.replaceAll("[^\\p{script=Han}]", "") для вывода всех некитайских символов вторая часть 𥑮 заменяется и становится недопустимой строкой.

Есть ли какое-либо решение? Я использую openjdk-8-jdk на Ubuntu.

Ответ 1

Для длины используйте

string.codePointCount(0, string.length());

Для замены лучше избегать регулярных выражений char. Вы можете написать цикл, полагающийся на String#offsetByCodePoints(), и вручную удалить символы на основе String.codePointAt() и Character.isIdeographic().

Ответ 2

Вы столкнулись с суррогатной парой. Этот символ U + 2546E, который, как видите, намного больше, чем 2 ^ 16. Он представлен в Java или Scala String как последовательность 0xD855 0xDC6E.

Если вам нужна библиотека регулярных выражений, которая прозрачно обрабатывает эту вещь, я знаю, где ее найти: TCL regex портировано в Java, Если вы не хотите туда идти, вам нужно использовать методы Point Point для String и Character в java для навигации.

Ответ 3

Поддержка unicode стандартной библиотеки Java предшествует существующему стандарту, и поэтому поддержка астральных (не BMP) символов... ограничена; несколько API будут рассматривать их как отдельные суррогатные пары, как вы видели. Если вы выполняете обширные манипуляции с строками, лучше всего использовать ICU4J, который, как я понимаю, предлагает регулярные выражения с полной поддержкой юникода.

Ответ 4

Основываясь на ответе @Marko, вот пример разбиения строки:

scala> val x = "硓𥑮abc"
x: String = 硓𥑮abc

scala> (0 to x.codePointCount(0, x.length)).map(c => x.offsetByCodePoints(0, c)).sliding(2).map(w => x.substring(w.head, w.last)).toList
res1: List[String] = List(硓, 𥑮, a, b, c)

И чтобы определить, является ли каждый символ CJKV:

scala> (0 until x.codePointCount(0, x.length)).map(c => x.offsetByCodePoints(0, c)).map(i => Character.isIdeographic(x.codePointAt(i))).toList
res2: List[Boolean] = List(true, true, false, false, false)

Ответ 5

Я думаю, что вы хотите заменить/разделить строку. Это можно сделать, не зная длины строки. Поскольку java принимает последовательность строк также для замены определенной char или последовательности char в строке. Например: -vpublic class Test {

public static void main(String[] args) {


    String s="𥑮";
    System.out.println(s.replace("𥑮", "k"));

}
}

` И если вы хотите разбить String, то перейдите к stringtokenizer. Например: -

StringTokenizer st= new StringTokenizer("your sentence or String","the problematic char/string");

Ответ 6

Вероятно, что этот символ недопустим или не поддерживается в UTF-8, но поддерживается в UTF-16, что приводит к некоторой несовместимости между JVM и оболочкой Scala. Является ли ваша система большой или малодушной? Также вы можете попробовать получить код кода Unicode и проверить, является ли это UTF-8 или UTF-16. Кроме того, китайцы усугубили письма, такие как японские кандзи и фуриганы, так что это также может быть частью вашей проблемы.