Преобразование символа юникода в int дает неверный код - PullRequest
0 голосов
/ 24 мая 2019

Я довольно новичок в Java, поэтому, пожалуйста, будьте осторожны.

Кажется, это общий вопрос, но я все еще не могу найти ответ, который ищу.

Я пишу консольное приложение, которое будет принимать строку символов и выводить их на экран, но больше. Например: «JAVA» будет печататься как:

 JJJJJ   A   V   V   A
   J    A A  V   V  A A
   J   A   A V   V A   A
   J   AAAAA V   V AAAAA
   J   A   A V   V A   A
 J J   A   A  V V  A   A
 JJJ   A   A   V   A   A

Ничего особенного там нет. Строка разбивается на символы, каждый символ затем ищется в большом регистре переключателя, который затем возвращает большую букву. После того, как при необходимости будет выполнена упаковка, большие буквы будут склеены и напечатаны.

Это было слишком просто, и так как я хотел бы сделать свою жизнь более сложной, я хочу разрешить определенные символы Юникода, такие как черное сердце (❤) \ u2674 (что, как утверждает карта символов Windows, так или иначе) ). По сути, передача какого-либо кода в параметр будет заменена внутри сильного и интерпретирована как символ Юникода, например: JAVA {HEART} может выводить (я знаю, что сердце испорчено, но оно отображается нормально с моноширинным шрифтом) ):

 JJJJJ   A   V   V   A     ❤❤  ❤❤
   J    A A  V   V  A A   ❤❤❤❤❤❤
   J   A   A V   V A   A   ❤❤❤❤❤
   J   AAAAA V   V AAAAA    ❤❤❤❤
   J   A   A V   V A   A     ❤❤❤
 J J   A   A  V V  A   A      ❤❤
 JJJ   A   A   V   A   A       ❤

Насколько я знаю, юникод должен вписываться в символ (2 байта) и обязательно должен помещаться в int (4 байта), поэтому я провел эксперимент. На улице говорят, что приведение к int даст вам код персонажа.

String unicodeStr = "\u2674"; // Unicode for black heart.
System.out.println(unicodeStr.getBytes().length); // Only one byte, so should fit into a char, right?

char unicode = '\u2674'; // All good so far.
System.out.println((int)unicode); // Returns 9844. WTAF??

System.exit(-1); // Argh! Oh noez... Panic!

Очевидно, я что-то здесь неправильно понимаю, но я не знаю что. Кто-нибудь может объяснить, почему я получаю неправильный код? Я пытался использовать codePoints, но, очевидно, я тоже не знаю, что я делаю с этим. Если бы кто-нибудь мог указать мне правильное направление, я был бы вечно благодарен. Цель состоит в том, чтобы разбить строку на символы и перевести каждый символ в большую букву с помощью регистра.

Ответы [ 3 ]

2 голосов
/ 24 мая 2019

В соответствии со спецификацией , getBytes() кодирует строку, используя кодировку по умолчанию для платформы , которая отличается от внутренней кодировки Java, UTF-16. Вот почему ваш getBytes() возвращает байтовый массив одной длины.

Но на самом деле представление символа '\u2674' в UTF-16 может уместиться в один символ, поскольку 9844 - это десятичное представление шестнадцатеричного значения 0x2674.

Но я все же рекомендую вам использовать codePoints, потому что есть некоторые символы, которые не могут быть сохранены внутри одного символа, например U+1D161 (???).

Чтобы выполнить итерацию String с использованием codePoints, вы можете использовать следующий код:

public class Main {

    public static void main(String[] args) {
        String str = "JAVA\uD834\uDD61\u2665";
        int len = str.length();
        for(int i = 0; i < len; ) {
            int cp = str.codePointAt(i);
            i += cp > 0xFFFF ? 2 : 1;

            if(cp == "\u2665".codePointAt(0)) {
                System.out.println("Heart!");
            }
            else if(cp == "\uD834\uDD61".codePointAt(0)){
                System.out.println("Music!");
            }
            else{
                System.out.println((char)cp);
            }
        }
    }

}

Выход:

JAVA?♥
size: 6
J
A
V
A
Music!
Heart!

Почему мы должны использовать \uD834\uDD61 для представления U+1D161?

Согласно википедии , чтобы представить символы U + 10000 ~ U + 10FFFF в UTF-16, нам нужно вычесть 0x1D161 с 0x10000, тогда мы получили 0x0D161, что составляет (0000 1101 0001 0110 0001) в двоичном формате.

Затем мы берем старшие десять битов, то есть (0000 1101 00) или 0x034, добавляем 0x034 с 0xD800, мы получаем 0xD834. это старший байт представления UTF-16 для U + 1D161.

Что касается младших десяти битов, мы получаем 0x161 + 0xDC00, что составляет 0xDD61.

Существует еще одна проблема, String.codePointAt принимает в качестве параметра индекс символа. Иногда одна кодовая точка может занимать два символа, поэтому мы должны проверить, является ли текущая кодовая точка больше 0xFFFF, прежде чем мы увеличим i.

Кстати, если вы используете Java 1.8, вы можете использовать новый String.codePoints API, который возвращает IntStream.

1 голос
/ 24 мая 2019

unicodeStr.getBytes (). Длина зависит от Charset

Проверьте это: Байт строки в Java

1 голос
/ 24 мая 2019

Сначала символ, который вы указали в своем вопросе, - это символ Юникода HEAVY BLACK HEART или U + 2764, поэтому его код равен 0x2764.

Затем, когда вы конвертируете символ в int, вы получаете его кодовую точку. Так что да, (int) '\u2674' равно 0x2674 или десятичному 9844. Поэтому неудивительно, что вы получили это.

Если вы хотите напечатать символ, просто напечатайте его без преобразования:

System.out.print(unicode);          // no end of line after the character
System.out.println(unicode);        // character followed with an end of line
...