Java char задает проблему кодирования (от UTF8 до cp866)

Как преобразовать текст из utf8/cp1251 (кириллицы Windows) в кириллицу DOS (cp866)

Я нахожу этот пример:

Charset fromCharset = Charset.forName("utf8");
Charset toCharset = Charset.forName("cp866");

String text1 = "Николай"; // my name in bulgarian
String text2 = "Nikolay"; // my name in english

System.out.println("TEXT1 :[" + toCharset.decode(fromCharset.encode(text1)).toString() + "]");
System.out.println("TEXT2 :[" + toCharset.decode(fromCharset.encode(text2)).toString() + "]");

И вход:

TEXT1 :[╨Э╨╕╨║╨╛╨╗╨░╨╣] // WRONG
TEXT2 :[Nikolay]  // CORRECT

Где проблема?

Ответ 1

Сначала: если у вас есть объект String, то он больше не имеет кодировки, это чистая строка Unicode (*)!

В Java кодировки используются только при преобразовании из байтов (byte[]) в строку (String) или наоборот. (Теоретически вы можете сделать прямое преобразование от byte[] до byte[], но я еще не видел, что это сделано на Java).

Если у вас есть некоторые кодированные данные cp1251, тогда он должен быть либо byte[] (т.е. массив байтов), либо каким-то потоком (например, предоставлен вам как InputStream).

Если вы хотите предоставить некоторые данные как cp866, вы должны предоставить его либо как byte[], либо как некоторый поток (например, `OutputStream).

Также: нет такой вещи, как "utf8/cp1251". UTF-8 и CP-1251 представляют собой довольно несвязанные кодировки символов. Ваш вход - это UTF-8 или CP-1251 (или что-то еще). На самом деле это не может быть (+).

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

(*) да, строго говоря, он имеет кодировку, и это UTF-16, но для большинства целей вы можете (и должны) думать об этом как о "идеальной Unicode String без кодирования"
(+), строго говоря, это может быть и то и другое, если он использует только символ, который кодируется в одни и те же байты в обоих кодировках, который обычно является подмножеством ASCII

Ответ 2

Проблема в том, что вы пытаетесь декодировать вывод одной кодировки, как если бы она была другой.

Представьте, что у вас была программа, которая могла бы записывать только JPEG, а другая, которая могла бы читать только PNG... вы ожидали бы, что сможете прочитать вывод первой программы со вторым?

В этом случае два кодирования оказываются совместимыми для символов ASCII, но в основном вы делаете не то.

Если у вас есть текст, который уже находится в UTF-8, вы должны прочитать, что из двоичных данных в строку Unicode с использованием кодировки UTF-8, а затем снова запишите его, используя другую кодировку для двоичных данных. Unicode - это промежуточный шаг в основном, как родной текстовый формат Java. Это было бы эквивалентно загрузке выходного файла JPEG в другую программу, которая могла бы выполнить преобразование в PNG, прежде чем читать его со вторым приложением.

Ответ 3

Краткое решение для вашей проблемы:

 System.out.write("ВАСЯ\n".getBytes("cp866")); // its right
 System.out.println("ВАСЯ".getBytes("cp866")); // its wrong

Результат из cmd.exe:

C:\Documents and Settings\afram\Мои документы \NetBeansProjects\Encoding\dist > java -jar Encoding.jar

ВАСЯ

[B @1bab50a

Ответ 4

Short:

Вы декодируете строку utf8 как cp866. Поскольку utf8 и cp866 разделяют только символы ascii, все остальное становится искаженным.

Long

Java представляет строки, использующие UTF-16 внутренне, все объекты String кодируются в UTF-16.

Charset.encode() создает байтовый буфер, содержащий String в выбранной кодировке, в вашем коде это преобразует строку Java UTF-16 в кодированный байтовый массив utf-8.

Charset.decode() принимает байтовый буфер, закодированный как Charset, и преобразует его в строку Java UTF-16. В вашем случае вы декодируете строку utf-8 с декодером cp866, что приводит к искаженной строке.

Так как строки java имеют указанную кодировку, вы должны указать ее при ее чтении или записи. И InputStreamReader, и OutputStreamWriter предоставляют ctors аргумент Charset.

Вот пример того, как вы можете конвертировать файлы/потоки.

//input the source is encoded in fromCharset
BufferedReader in = new BufferedReader(new InputStreamReader(...,fromCharset));
//output the target will be encoded in toCharset
PrintWriter out = new PrintWriter(new OutputStreamWriter(...,toCharset));
//reads a decoded String
String line = in.readLine();
while(line != null)
{
   out.println(line);
   line = in.readLine();
}

Ответ 5

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

Внутренняя строка в java всегда юникод, кодировка важна для операций ввода-вывода. Вы не указали, что хотите делать с "преобразованной" строкой, но вы должны определенно увидеть классы InputStreamReader/OutputStreamWriter. Они обеспечивают настройку набора символов для операций ввода/вывода.