Java печать Unicode глюк - PullRequest
       25

Java печать Unicode глюк

0 голосов
/ 23 ноября 2018

В настоящее время я пишу программу для чтения файлов классов Java.В данный момент я читаю пул констант файла класса (читай здесь ) и печатаю его на консоль.Но когда он печатается, некоторые из юникодов, похоже, портят мой терминал таким образом, что он выглядит так (в случае, если это имеет значение, файл класса, который я читаю, скомпилирован из Kotlin, а терминал Iя использую терминал IntelliJ IDEA, хотя он, похоже, не дает сбоя при использовании обычного терминала Ubuntu.): Messed up terminal on IntelliJ IDEA Я заметил странную последовательность Unicode-Sequence, которая может быть какой-тоиз escape-последовательности, я думаю.

Вот весь вывод без странной последовательности Юникода:

{1=UTF8: (42)'deerangle/decompiler/main/DecompilerMainKt', 2=Class index: 1, 3=UTF8: (16)'java/lang/Object', 4=Class index: 3, 5=UTF8: (4)'main', 6=UTF8: (22)'([Ljava/lang/String;)V', 7=UTF8: (35)'Lorg/jetbrains/annotations/NotNull;', 8=UTF8: (4)'args', 9=String index: 8, 10=UTF8: (30)'kotlin/jvm/internal/Intrinsics', 11=Class index: 10, 12=UTF8: (23)'checkParameterIsNotNull', 13=UTF8: (39)'(Ljava/lang/Object;Ljava/lang/String;)V', 14=Method name index: 12; Type descriptor index: 13, 15=Bootstrap method attribute index: 11; NameType index: 14, 16=UTF8: (12)'java/io/File', 17=Class index: 16, 18=UTF8: (6)'<init>', 19=UTF8: (21)'(Ljava/lang/String;)V', 20=Method name index: 18; Type descriptor index: 19, 21=Bootstrap method attribute index: 17; NameType index: 20, 22=UTF8: (15)'getAbsolutePath', 23=UTF8: (20)'()Ljava/lang/String;', 24=Method name index: 22; Type descriptor index: 23, 25=Bootstrap method attribute index: 17; NameType index: 24, 26=UTF8: (16)'java/lang/System', 27=Class index: 26, 28=UTF8: (3)'out', 29=UTF8: (21)'Ljava/io/PrintStream;', 30=Method name index: 28; Type descriptor index: 29, 31=Bootstrap method attribute index: 27; NameType index: 30, 32=UTF8: (19)'java/io/PrintStream', 33=Class index: 32, 34=UTF8: (5)'print', 35=UTF8: (21)'(Ljava/lang/Object;)V', 36=Method name index: 34; Type descriptor index: 35, 37=Bootstrap method attribute index: 33; NameType index: 36, 38=UTF8: (19)'[Ljava/lang/String;', 39=Class index: 38, 40=UTF8: (17)'Lkotlin/Metadata;', 41=UTF8: (2)'mv', 42=Int: 1, 43=Int: 11, 44=UTF8: (2)'bv', 45=Int: 0, 46=Int: 2, 47=UTF8: (1)'k', 48=UTF8: (2)'d1', 49=UTF8: (58)'WEIRD_UNICODE_SEQUENCE', 50=UTF8: (2)'d2', 51=UTF8: (0)'', 52=UTF8: (10)'Decompiler', 53=UTF8: (17)'DecompilerMain.kt', 54=UTF8: (4)'Code', 55=UTF8: (18)'LocalVariableTable', 56=UTF8: (15)'LineNumberTable', 57=UTF8: (13)'StackMapTable', 58=UTF8: (36)'RuntimeInvisibleParameterAnnotations', 59=UTF8: (10)'SourceFile', 60=UTF8: (20)'SourceDebugExtension', 61=UTF8: (25)'RuntimeVisibleAnnotations'}
AccessFlags: {ACC_PUBLIC, ACC_FINAL, ACC_SUPER}

А вот последовательность Юникода, открытая в Sublime Text: Strange unicode in sublime text

Мои вопросы по поводу всего этого: почему этот Unicode ломает консоль в IntelliJ IDEA, это часто встречается в Kotlin-Class-Files, и что можно сделать, чтобы удалить все такие "escape-последовательности "из строки перед печатью?

Ответы [ 3 ]

0 голосов
/ 24 ноября 2018

Ответ Майка уже охватывал тот факт, что файлы классов Java не совсем используют кодировку UTF8, но я решил, что предоставлю больше информации об этом.

Кодировка, используемая в файлах классов Java, называется Modified UTF-8 (или MUTF-8).Он отличается от обычного UTF-8 в двух отношениях:

  • Нулевой байт кодируется с использованием двухбайтовой последовательности
  • Кодовые точки вне BMP представлены суррогатной парой, как в UTF16,Каждая кодовая точка в паре, в свою очередь, кодируется тремя байтами, используя обычную кодировку UTF8.

Первое изменение состоит в том, что закодированные данные не содержат необработанных нулевых байтов, что упрощает обработку, когданаписание C-кода.Второе изменение является следствием того факта, что еще в 90-х годах UTF-16 был в моде, и не было ясно, что UTF-8 в конечном итоге победит.Фактически, Java использует 16-битные символы по аналогичной причине.Кодирование астральных символов с помощью суррогатных пар значительно облегчает работу в 16-битном мире.Обратите внимание, что Javascript, разработанный примерно в то же время, имеет аналогичные проблемы со строками UTF-16.

В любом случае, кодирование и декодирование MUTF-8 довольно просты.Это просто раздражает, поскольку нигде не встроено.При декодировании вы декодируете так же, как UTF-8, вам просто нужно быть более терпимым, за исключением последовательностей, которые технически недопустимы для UTF-8 (несмотря на использование того же кодирования), а затем заменять суррогатные пары, если это применимо.При кодировании вы делаете обратное.

Обратите внимание, что это относится только к байт-коду Java.Программисты на Java, как правило, не должны иметь дело с MUTF-8, поскольку Java везде использует смесь UTF-16 и истинного UTF-8.

0 голосов
/ 26 ноября 2018

Консоль IntelliJ, скорее всего, интерпретирует определенные символы строки как управляющие символы (сравните с Вывод на консоль Colorize в продуктах Intellij ).

Скорее всего, это будет эмуляция терминала ANSI,что вы можете легко проверить, выполнив

System.out.println("Hello "
    + "\33[31mc\33[32mo\33[33ml\33[34mo\33[35mr\33[36me\33[37md"
    + " \33[30mtext");

Если вы видите, что этот текст напечатан разными цветами, это интерпретация, совместимая с терминалом ANSI.

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

Простой способ сделать это -

System.out.println(string.replaceAll("\\p{IsControl}", "."));

, который перед печатью заменяет все управляющие символы точкой..

Если вы хотите получить некоторую диагностику относительно фактического значения символа, вы можете использовать, например,

System.out.println(Pattern.compile("\\p{IsControl}").matcher(string)
    .replaceAll(mr -> String.format("{%02X}", (int)string.charAt(mr.start()))));

Это требует Java 9, но, конечно, та же логика может быть реализована дляболее ранняя версия Java также.Для этого потребуется только немного более подробный код.

Экземпляр Pattern, возвращаемый Pattern.compile("\\p{IsControl}"), может быть сохранен и использован повторно.

0 голосов
/ 23 ноября 2018

По какой-то непостижимой причине, когда Sun Microsystems разрабатывали Java, они решили кодировать строки в постоянном пуле, используя кодировку, отличную от UTF8.Это пользовательская кодировка, используемая только компилятором java и загрузчиками классов.

Добавляя оскорбление к травме, в документации JVM они решили назвать это UTF8.Но это , а не UTF8, и их выбор имени вызывает много ненужной путаницы.Итак, я предполагаю, что вы видели, что они называют это UTF8, поэтому вы рассматриваете это как real UTF8, и в результате вы получаете мусор.

Вам понадобитсяпоискать описание CONSTANT_Utf8_info в спецификации JVM и написать алгоритм, который декодирует строки в соответствии с их спецификацией.

Для вашего удобства, вот код, который я написал для этого:

public static char[] charsFromBytes( byte[] bytes )
{
    int t = 0;
    int end = bytes.length;
    for( int s = 0;  s < end;  )
    {
        int b1 = bytes[s] & 0xff;
        if( b1 >> 4 >= 0 && b1 >> 4 <= 7 ) /* 0x0xxx_xxxx */
            s++;
        else if( b1 >> 4 >= 12 && b1 >> 4 <= 13 ) /* 0x110x_xxxx 0x10xx_xxxx */
            s += 2;
        else if( b1 >> 4 == 14 ) /* 0x1110_xxxx 0x10xx_xxxx 0x10xx_xxxx */
            s += 3;
        t++;
    }
    char[] chars = new char[t];
    t = 0;
    for( int s = 0;  s < end;  )
    {
        int b1 = bytes[s++] & 0xff;
        if( b1 >> 4 >= 0 && b1 >> 4 <= 7 ) /* 0x0xxx_xxxx */
            chars[t++] = (char)b1;
        else if( b1 >> 4 >= 12 && b1 >> 4 <= 13 ) /* 0x110x_xxxx 0x10xx_xxxx */
        {
            assert s < end : new IncompleteUtf8Exception( s );
            int b2 = bytes[s++] & 0xff;
            assert (b2 & 0xc0) == 0x80 : new MalformedUtf8Exception( s - 1 );
            chars[t++] = (char)(((b1 & 0x1f) << 6) | (b2 & 0x3f));
        }
        else if( b1 >> 4 == 14 ) /* 0x1110_xxxx 0x10xx_xxxx 0x10xx_xxxx */
        {
            assert s < end : new IncompleteUtf8Exception( s );
            int b2 = bytes[s++] & 0xff;
            assert (b2 & 0xc0) == 0x80 : new MalformedUtf8Exception( s - 1 );
            assert s < end : new IncompleteUtf8Exception( s );
            int b3 = bytes[s++] & 0xff;
            assert (b3 & 0xc0) == 0x80 : new MalformedUtf8Exception( s - 1 );
            chars[t++] = (char)(((b1 & 0x0f) << 12) | ((b2 & 0x3f) << 6) | (b3 & 0x3f));
        }
        else
            assert false;
    }
    return chars;
}
...