Отображение эмодзи UTF-8 в Java

Скажем, у меня есть смайлик ???? (дьявол).

В 4-байтовом UTF-8 это выглядит так: \u00f0\u009f\u0098\u0088

Однако в Java он будет печатать правильно только так: \ud83d\ude08

Как мне перейти от первого ко второму?

ОБНОВЛЕНИЕ 2

Ответ MNEMO намного проще и отвечает на мой вопрос, поэтому, вероятно, лучше пойти с его решением.

ОБНОВЛЕНИЕ

Спасибо Basil Bourque за рецензию. Это было очень интересно.

Я нашел здесь хорошую ссылку: https://github.com/pRizz/Unicode-Converter/blob/master/conversionfunctions.js (в частности, функция convertUTF82Char ()).

Для тех, кто забредет сюда в будущем, вот как это будет выглядеть на Java:

public static String fromCharCode(int n) {
    char c = (char)n;
    return Character.toString(c);
}

public static String decToChar(int n) {
    // converts a single string representing a decimal number to a character
    // note that no checking is performed to ensure that this is just a hex number, eg. no spaces etc
    // dec: string, the dec codepoint to be converted
    String result = "";
    if (n <= 0xFFFF) {
        result += fromCharCode(n);
    } else if (n <= 0x10FFFF) {
        n -= 0x10000;
        result += fromCharCode(0xD800 | (n >> 10)) + fromCharCode(0xDC00 | (n & 0x3FF));
    } else {
        result += "dec2char error: Code point out of range: " + decToHex(n);
    }

    return result;
}

public static String decToHex(int n) {
    return Integer.toHexString(n).toUpperCase();
}

public static String convertUTF8_toChar(String str) {
    // converts to characters a sequence of space-separated hex numbers representing bytes in utf8
    // str: string, the sequence to be converted
    var outputString = "";
    var counter = 0;
    var n = 0;

    // remove leading and trailing spaces
    str = str.replaceAll("/^\\s+/", "");
    str = str.replaceAll("/\\s+$/", "");
    if (str.length() == 0) {
        return "";
    }

    str = str.replaceAll("/\\s+/g", " ");

    var listArray = str.split(" ");
    for (var i = 0; i < listArray.length; i++) {
        int b = parseInt(listArray[i], 16); // alert('b:'+dec2hex(b));
        switch (counter) {
            case 0:
                if (0 <= b && b <= 0x7F) { // 0xxxxxxx
                    outputString += decToChar(b);
                } else if (0xC0 <= b && b <= 0xDF) { // 110xxxxx
                    counter = 1;
                    n = b & 0x1F;
                } else if (0xE0 <= b && b <= 0xEF) { // 1110xxxx
                    counter = 2;
                    n = b & 0xF;
                } else if (0xF0 <= b && b <= 0xF7) { // 11110xxx
                    counter = 3;
                    n = b & 0x7;
                } else {
                    outputString += "convertUTF82Char: error1 " + decToHex(b) + "! ";
                }
                break;
            case 1:
                if (b < 0x80 || b > 0xBF) {
                    outputString += "convertUTF82Char: error2 " + decToHex(b) + "! ";
                }
                counter--;
                outputString += decToChar((n << 6) | (b - 0x80));
                n = 0;
                break;
            case 2:
            case 3:
                if (b < 0x80 || b > 0xBF) {
                    outputString += "convertUTF82Char: error3 " + decToHex(b) + "! ";
                }
                n = (n << 6) | (b - 0x80);
                counter--;
                break;
        }
    }

    return outputString.replaceAll("/ $/", "");
}

В значительной степени копия 1 к 1, но она выполняет мою цель.


person InfexiousBand    schedule 01.06.2020    source источник
comment
Если вы хотите решить эту проблему, рекомендуется узнать больше о кодировке символов и системе Unicode. 4-байтовый UTF-8 - это последовательность байтов, но не сама кодовая точка Unicode.   -  person MNEMO    schedule 01.06.2020


Ответы (2)


ну, в этом нет необходимости добавлять, но после того, как вы поймете всю систему кодировки символов и концепцию Unicode, следующий код может сработать для вас.

byte[] a = { (byte)0xf0, (byte)0x9f, (byte)0x98, (byte)0x88 };
String s = new String(a,"UTF-8");
byte[] b = s.getBytes("UTF-16BE");
for ( byte c : b ) { System.out.printf("%02x ",c); }
person MNEMO    schedule 02.06.2020
comment
Это действительно работает, и это намного проще, чем то, что у меня получилось. Теперь все, что мне нужно сделать, это распечатать его в указанном мной формате. Спасибо. - person InfexiousBand; 02.06.2020

Символ SMILING FACE WITH HORNS (????) назначается кодовая точка 128 520 десятичных знаков (1F608 шестнадцатеричный) в Unicode.

У вас есть выбор, как представить это число серией октетов.

  • UTF-8 is one way to represent that number with a variable length, using 1-4 octets.
    • UTF-8 is becoming the dominant encoding in many spheres.
    • По моему опыту, файлы исходного кода Java обычно пишутся в UTF-8 и как описано здесь.
  • UTF-16 is another way, also variable-length, but using either 2 octets or 4.
    • The Java language uses UTF-16 internally.
    • UTF-8 обычно предпочтительнее UTF-16, как описано здесь .

В большинстве текстовых редакторов вы можете просто вставить один символ ???? в исходный код. При записи в файл UTF-8 редактор создаст необходимую серию октетов.

При записи этого символа в текстовый файл или иной сериализации в поток октетов вы можете выбрать использование UTF-8 или UTF-16. Видеть:

Ниже приведены несколько попыток. Вы можете просмотреть полученные файлы с помощью шестнадцатеричного редактора, чтобы увидеть октеты.

UTF-8

Этот код создает файл в кодировке UTF-8. Находим четыре октета, шестнадцатеричные значения F0 9F 98 88, десятичные значения 240 159 152 136.

Вы можете найти этот код, обсуждаемый в Oracle Java Tutorial.

Обратите внимание, как мы указываем кодировку для нашего файла, _ 3_.

Path file = Paths.get( "/Users/basilbourque/devil_utf-8.txt" );
Charset charset = StandardCharsets.UTF_8;
String s = "????";
try ( BufferedWriter writer = Files.newBufferedWriter( file , charset ) )
{
    writer.write( s , 0 , s.length() );
}
catch ( IOException e )
{
    e.printStackTrace();
}

UTF-16

Этот код создает файл в кодировке UTF-16. Мы находим 6 октетов, 4 октета для нашего одиночного символа, плюс префикс из 2 октетов для Спецификация (ИП FF). Наши четыре октета в десятичной системе счисления - 216061 222008, в шестнадцатеричной - D8 3D DE 08.

Тот же код, что и выше, но мы переключили _ 5_ на _ 6_.

Path file = Paths.get( "/Users/basilbourque/devil_utf-16.txt" );
Charset charset = StandardCharsets.UTF_16;
String s = "????";
try ( BufferedWriter writer = Files.newBufferedWriter( file , charset ) )
{
    writer.write( s , 0 , s.length() );
}
catch ( IOException e )
{
    e.printStackTrace();
}

О Unicode и кодировках

Чтобы изучить основы Unicode и кодировок, прочтите сообщение Абсолютный минимум, который должен знать каждый разработчик программного обеспечения о Unicode и наборах символов (без оправданий!) .

person Basil Bourque    schedule 01.06.2020