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. Они обеспечивают настройку набора символов для операций ввода/вывода.