Очевидный ответ - использовать, Charset.defaultCharset()
но недавно мы обнаружили, что это может быть неправильный ответ. Мне сказали, что результат несколько раз отличается от реальной кодировки по умолчанию, используемой классами java.io. Похоже, в Java есть 2 набора кодировки по умолчанию. У кого-нибудь есть идеи по этому поводу?
Нам удалось воспроизвести один случай отказа. Это своего рода ошибка пользователя, но она может выявить основную причину всех других проблем. Вот код,
public class CharSetTest {
public static void main(String[] args) {
System.out.println("Default Charset=" + Charset.defaultCharset());
System.setProperty("file.encoding", "Latin-1");
System.out.println("file.encoding=" + System.getProperty("file.encoding"));
System.out.println("Default Charset=" + Charset.defaultCharset());
System.out.println("Default Charset in Use=" + getDefaultCharSet());
}
private static String getDefaultCharSet() {
OutputStreamWriter writer = new OutputStreamWriter(new ByteArrayOutputStream());
String enc = writer.getEncoding();
return enc;
}
}
Нашему серверу требуется кодировка по умолчанию в Latin-1 для работы с некоторой смешанной кодировкой (ANSI / Latin-1 / UTF-8) в устаревшем протоколе. Итак, все наши серверы работают с этим параметром JVM,
-Dfile.encoding=ISO-8859-1
Вот результат на Java 5,
Default Charset=ISO-8859-1
file.encoding=Latin-1
Default Charset=UTF-8
Default Charset in Use=ISO8859_1
Кто-то пытается изменить время выполнения кодировки, установив в коде file.encoding. Все мы знаем, что это не работает. Однако это явно отбрасывает defaultCharset (), но не влияет на реальную кодировку по умолчанию, используемую OutputStreamWriter.
Это ошибка или особенность?
РЕДАКТИРОВАТЬ: принятый ответ показывает основную причину проблемы. По сути, вы не можете доверять defaultCharset () в Java 5, которая не является кодировкой по умолчанию, используемой классами ввода-вывода. Похоже, Java 6 исправляет эту проблему.
источник
Ответы:
Это действительно странно ... После установки Charset по умолчанию кэшируется и не изменяется, пока класс находится в памяти. Установка
"file.encoding"
свойства с помощьюSystem.setProperty("file.encoding", "Latin-1");
ничего не делает. Каждый раз приCharset.defaultCharset()
вызове он возвращает кешированную кодировку.Вот мои результаты:
Default Charset=ISO-8859-1 file.encoding=Latin-1 Default Charset=ISO-8859-1 Default Charset in Use=ISO8859_1
Однако я использую JVM 1.6.
(Обновить)
ОК. Я воспроизвел вашу ошибку с JVM 1.5.
Если посмотреть на исходный код версии 1.5, кешированная кодировка по умолчанию не установлена. Я не знаю, ошибка это или нет, но 1.6 меняет эту реализацию и использует кешированную кодировку:
JVM 1.5:
public static Charset defaultCharset() { synchronized (Charset.class) { if (defaultCharset == null) { java.security.PrivilegedAction pa = new GetPropertyAction("file.encoding"); String csn = (String) AccessController.doPrivileged(pa); Charset cs = lookup(csn); if (cs != null) return cs; return forName("UTF-8"); } return defaultCharset; } }
JVM 1.6:
public static Charset defaultCharset() { if (defaultCharset == null) { synchronized (Charset.class) { java.security.PrivilegedAction pa = new GetPropertyAction("file.encoding"); String csn = (String) AccessController.doPrivileged(pa); Charset cs = lookup(csn); if (cs != null) defaultCharset = cs; else defaultCharset = forName("UTF-8"); } } return defaultCharset; }
Когда вы устанавливаете кодировку файла
file.encoding=Latin-1
в следующий раз, когда вы вызываетеCharset.defaultCharset()
, происходит то, что, поскольку кешированная кодировка по умолчанию не установлена, он попытается найти подходящую кодировку для имениLatin-1
. Это имя не найдено, потому что оно неверно, и возвращает значение по умолчаниюUTF-8
.Что касается того, почему классы ввода-вывода, например,
OutputStreamWriter
возвращают неожиданный результат,реализация
sun.nio.cs.StreamEncoder
(которая используется этими классами ввода-вывода) отличается также для JVM 1.5 и JVM 1.6. Реализация JVM 1.6 основана наCharset.defaultCharset()
методе получения кодировки по умолчанию, если она не предоставляется классам ввода-вывода. Реализация JVM 1.5 использует другой методConverters.getDefaultEncodingName();
для получения кодировки по умолчанию. Этот метод использует собственный кеш кодировки по умолчанию, которая устанавливается при инициализации JVM:JVM 1.6:
public static StreamEncoder forOutputStreamWriter(OutputStream out, Object lock, String charsetName) throws UnsupportedEncodingException { String csn = charsetName; if (csn == null) csn = Charset.defaultCharset().name(); try { if (Charset.isSupported(csn)) return new StreamEncoder(out, lock, Charset.forName(csn)); } catch (IllegalCharsetNameException x) { } throw new UnsupportedEncodingException (csn); }
JVM 1.5:
public static StreamEncoder forOutputStreamWriter(OutputStream out, Object lock, String charsetName) throws UnsupportedEncodingException { String csn = charsetName; if (csn == null) csn = Converters.getDefaultEncodingName(); if (!Converters.isCached(Converters.CHAR_TO_BYTE, csn)) { try { if (Charset.isSupported(csn)) return new CharsetSE(out, lock, Charset.forName(csn)); } catch (IllegalCharsetNameException x) { } } return new ConverterSE(out, lock, csn); }
Но я согласен с комментариями. Вы не должны полагаться на эту недвижимость . Это деталь реализации.
источник
Похоже на неопределенное поведение. Я знаю, что на практике вы можете изменить кодировку по умолчанию, используя свойство командной строки, но я не думаю, что то, что происходит, когда вы это делаете, определено.
Идентификатор ошибки: 4153515 при проблемах с настройкой этого свойства:
Я съеживаюсь, когда вижу, как люди устанавливают кодировку в командной строке - вы не знаете, какой код это повлияет.
Если вы не хотите использовать кодировку по умолчанию, установите желаемую кодировку явно с помощью соответствующего метода / конструктора .
источник
Во-первых, Latin-1 такой же, как ISO-8859-1, поэтому значение по умолчанию уже было для вас приемлемым. Правильно?
Вы успешно установили кодировку ISO-8859-1 с помощью параметра командной строки. Вы также программно устанавливаете его на «Latin-1», но это не признанное значение кодировки файла для Java. См. Http://java.sun.com/javase/6/docs/technotes/guides/intl/encoding.doc.html
Когда вы это сделаете, похоже, что Charset сбрасывается до UTF-8, если посмотреть на источник. Это, по крайней мере, объясняет большую часть поведения.
Я не знаю, почему OutputStreamWriter показывает ISO8859_1. Он делегирует классы sun.misc. * С закрытым исходным кодом. Я предполагаю, что это не совсем касается кодирования с помощью того же механизма, что странно.
Но, конечно, вы всегда должны указывать, какую кодировку вы имеете в виду в этом коде. Я бы никогда не стал полагаться на платформу по умолчанию.
источник
Поведение на самом деле не такое уж странное. Если посмотреть на реализацию классов, это вызвано:
Charset.defaultCharset()
не кэширует определенный набор символов в Java 5.Charset.defaultCharset()
вызывает вторую оценку системного свойства, набор символов с именем «Latin-1» не найден, поэтому поCharset.defaultCharset()
умолчанию используется «UTF-8».OutputStreamWriter
не менее, он кэширует набор символов по умолчанию и, вероятно, используется уже во время инициализации виртуальной машины, так что его набор символов по умолчанию отличается от него,Charset.defaultCharset()
если системное свойство «file.encoding» было изменено во время выполнения.Как уже указывалось, не задокументировано, как виртуальная машина должна вести себя в такой ситуации. Документация по
Charset.defaultCharset()
API не очень точна в отношении того, как определяется набор символов по умолчанию, только упоминается, что это обычно выполняется при запуске виртуальной машины на основе таких факторов, как набор символов по умолчанию для ОС или языковой стандарт по умолчанию.источник
Я установил аргумент vm на сервере WAS как -Dfile.encoding = UTF-8, чтобы изменить набор символов по умолчанию для серверов.
источник
проверять
System.getProperty("sun.jnu.encoding")
похоже, это та же кодировка, что и в командной строке вашей системы.
источник