Как найти кодировку / кодировку по умолчанию в Java? - PullRequest
88 голосов
/ 17 ноября 2009

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

Ответы [ 6 ]

62 голосов
/ 17 ноября 2009

Это действительно странно ... После установки 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);
}

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

24 голосов
/ 17 ноября 2009

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

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

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

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

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

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

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

4 голосов
/ 17 ноября 2009

Поведение не так уж странно. Если посмотреть на реализацию классов, это вызвано:

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

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

4 голосов
/ 17 ноября 2009

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

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

3 голосов
/ 17 декабря 2013

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

0 голосов
/ 14 декабря 2012

чек

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...