Причина в том, что текст в кодировке 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