Один китайский символ определяется как длина 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. Кроме того, китайцы усугубили письма, такие как японские кандзи и фуриганы, так что это также может быть частью вашей проблемы.