Как найти кодировку по умолчанию или кодировку в Java?

Очевидным ответом является использование 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;
    }
}

Наш сервер требует кодировки по умолчанию на латинском языке 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

Кто-то пытается изменить время выполнения кодировки, установив файл. encoding в коде. Мы все знаем, что это не сработает. Однако это, по-видимому, сбрасывает defaultCharset(), но это не влияет на реальную кодировку по умолчанию, используемую OutputStreamWriter.

Это ошибка или функция?

EDIT: принятый ответ показывает основную причину проблемы. В принципе, вы не можете доверять defaultCharset() в Java 5, который не является кодировкой по умолчанию, используемой классами ввода-вывода. Похоже, что Java 6 исправляет эту проблему.

Ответ 1

Это действительно странно... После установки по умолчанию 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, хотя.

(обновление)

Ok. Я воспроизвел вашу ошибку с помощью 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);
}

Но я согласен с комментариями. Вы не должны полагаться на это свойство. Это деталь реализации.

Ответ 2

Это ошибка или функция?

Похож на поведение undefined. Я знаю, что на практике вы можете изменить кодировку по умолчанию, используя свойство командной строки, но я не думаю, что происходит, когда вы это делаете.

Идентификатор ошибки: 4153515 о проблемах с настройкой этого свойства:

Это не ошибка. Свойство "file.encoding" не требуется J2SE спецификация платформы; это внутренняя деталь реализации Sun и не должны быть проверены или изменены кодом пользователя. Он также предназначался для только для чтения; технически невозможно поддерживать настройку этого имущества к произвольным значениям в командной строке или в любое другое время во время программы выполнение.

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

Я сжимаю, когда вижу, как люди устанавливают кодировку в командной строке - вы не знаете, какой код повлияет.

Если вы не хотите использовать кодировку по умолчанию, установите кодировку, которую вы явно хотите использовать с помощью соответствующего метода /конструктор.

Ответ 3

Во-первых, 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. * Classes. Я предполагаю, что он не совсем разбирается в кодировании с помощью того же механизма, что и странно.

Но, конечно, вы всегда должны указывать, какую кодировку вы имеете в виду в этом коде. Я никогда не полагался на платформу по умолчанию.

Ответ 4

Поведение на самом деле не так странно. Изучая реализацию классов, это вызвано:

  • Charset.defaultCharset() не кэширует определенный набор символов в Java 5.
  • Установка системного свойства "file.encoding" и вызов Charset.defaultCharset() снова вызывает вторую оценку системного свойства, не установлен набор символов с именем "Latin-1", поэтому Charset.defaultCharset по умолчанию имеет значение "UTF-8".
  • Однако OutputStreamWriter кэширует набор символов по умолчанию и, вероятно, используется уже во время инициализации VM, поэтому его набор символов по умолчанию переводит из Charset.defaultCharset(), если системное свойство "file.encoding" было изменено во время выполнения.

Как уже указывалось, документация о том, как виртуальная машина должна вести себя в такой ситуации, не документирована. Документация API Charset.defaultCharset() не очень точно определяет, как определяется набор символов по умолчанию, и только упоминание о том, что это обычно делается при запуске VM, на основе таких факторов, как набор символов по умолчанию ОС или стандартная локаль.

Ответ 5

Я установил аргумент vm в WAS-сервере как -Dfile.encoding = UTF-8, чтобы изменить набор символов по умолчанию сервера.

Ответ 6

проверить

System.getProperty("sun.jnu.encoding")

похоже, такая же кодировка, как и в командной строке вашей системы.