Почему байты спецификации UTF-8 efbbbf можно заменить на \ ufeff? - PullRequest
0 голосов
/ 18 января 2019

в Windows кодированный файл UTF-8 имеет спецификацию (маркер порядка байтов): EF BB BF.

И многие решения для удаления это просто однострочный код:

 replace("\uFEFF", "")

Что я не понимаю, почему это работает.

Вот мой тестовый код, и после этого я проверяю двоичный файл и обнаруживаю, что EF BB BF INDEED удален. Так волшебно. Почему?

@Test
public void shit() throws Exception{
    byte[] b = new byte[]{-17,-69,-65, 97,97,97};//EF BB BF 61 61 61
    char[] c = new char[10];
    new InputStreamReader(new ByteArrayInputStream(b),"UTF-8").read(c);
    byte[] bytes = new StringBuilder().append(c).toString().replace("\uFEFF", "").getBytes();//
    for(byte bt: bytes){//61 61 61, we can see EF BB BF is indeed removed
        System.out.println(bt);
    }
}

Ответы [ 2 ]

0 голосов
/ 18 января 2019

Причина в том, что текст в кодировке Unicode должен начинаться с метки порядка байтов (кроме UTF-8, где это не рекомендуется).

из Википедии

Метка порядка байтов (BOM) представляет собой символ Unicode, U + FEFF MARK BYD ORDER MARK (BOM), чей вид в виде магического числа в начале текстового потока ...
...
Спецификация закодирована в той же схеме, что и остальная часть документа ...

Это означает, что этот специальный символ (\uFEFF) также должен быть закодирован в UTF-8.

UTF-8 может кодировать кодовые точки Unicode в один-четыре байта.

  • кодовые точки, которые могут быть представлены 7 битами, кодируются одним байтом, старший бит всегда равен нулю 0xxx xxxx
  • все остальные кодовые точки, закодированные в нескольких байтах, в зависимости от количества битов, левые установленные биты первого байта представляют количество байтов, используемых для кодирования, например, 110x xxxx означает, что кодировка представлена ​​двумя байтами, байты продолжения всегда начинаются с 10xx xxxx (для кодовых точек можно использовать биты x)

Кодовые точки в диапазоне U+0000 - U+007F могут кодироваться одним байтом.
Кодовые точки в диапазоне U+0080 - U+07FF могут кодироваться двумя байтами. Кодовые точки в диапазоне U+0800 - U+FFFF могут кодироваться тремя байтами.

Подробное объяснение на Википедии

Для спецификации нам нужны три байта.

hex    FE       FF
binary 11111110 11111111

кодировать биты в UTF-8

pattern for three byte encoding 1110 xxxx  10xx xxxx  10xx xxxx
the bits of the code point           1111    11 1011    11 1111
result                          1110 1111  1011 1011  1011 1111
in hex                          EF         BB         BF

EF BB BF звучит уже знакомо. ; -)

Последовательность байтов EF BB BF - это не что иное, как спецификация, закодированная в UTF-8.

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

кодирование символа спецификации как UTF-8

jshell> "\uFEFF".getBytes("UTF-8")
$1 ==> byte[3] { -17, -69, -65 }  // EF BB BF

Следовательно, когда файл читается, последовательность байтов декодируется в \uFEFF.

Для кодирования, например UTF-16 добавлена ​​спецификация

jshell> " ".getBytes("UTF-16")
$2 ==> byte[4] { -2, -1, 0, 32 }  // FE FF + the encoded SPACE
0 голосов
/ 18 января 2019

InputStreamReader декодирует кодированную последовательность байтов UTF-8 (b) в UTF-16BE и в процессе преобразует спецификацию UTF-8 в спецификацию UTF-16BE (\ uFEFF). UTF-16BE выбран в качестве целевой кодировки, потому что Charset по умолчанию соответствует этому поведению:

https://docs.oracle.com/javase/7/docs/api/java/nio/charset/Charset.html

Кодировки UTF-16 определены RFC 2781; преобразование форматы, на которых они основаны, указаны в поправке 1 ISO 10646-1, а также описаны в стандарте Unicode.

В кодировках UTF-16 используются 16-битные величины, и поэтому чувствителен к порядку байтов. В этих кодировках порядок байтов потока может указываться начальной меткой порядка байтов, представленной Юникод символ '\ uFEFF'. Метки порядка байтов обрабатываются следующим образом:

При декодировании кодировки UTF-16BE и UTF-16LE интерпретируют начальный порядок следования байтов как неразрывный пробел с нулевой шириной; когда кодирование, они не пишут метки порядка байтов.

При декодировании кодировка UTF-16 интерпретирует метку порядка байтов в начало входного потока для указания порядка байтов stream, но по умолчанию имеет значение big-endian, если нет метки порядка байтов; когда кодирование, он использует порядок байтов с прямым порядком байтов и записывает байты с прямым порядком байтов знак порядка байтов.

См. JLS 3.1, чтобы понять, почему внутренняя кодировка String является UTF-16:

https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.1

Язык программирования Java представляет текст в 16-битных последовательностях кодовые единицы с использованием кодировки UTF-16.

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

Резюме

Последовательность EF BB BF (UTF-8 BOM) преобразуется в FE FF (UTF-16BE BOM) при декодировании последовательности байтов в String с использованием InputStreamReader , поскольку кодировка java.lang.String со значением по умолчанию Charset - это UTF-16 BE при наличии спецификации. После замены спецификации UTF-16BE и вызова String # getBytes () строка декодируется в UTF-8 (кодировку по умолчанию для вашей платформы), и вы видите исходную последовательность байтов без спецификации.

...