Як знайти кодування / кодування за замовчуванням у Java?


92

Очевидна відповідь - використовувати, 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

Хтось намагається змінити час виконання кодування, встановивши в коді файл .encoding. Ми всі знаємо, що це не працює. Однак це, очевидно, відкидає defaultCharset (), але це не впливає на реальну кодировку за замовчуванням, що використовується OutputStreamWriter.

Це помилка чи функція?

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


Це дивно, оскільки defaultCharset використовує статичну змінну, яка встановлюється лише один раз (відповідно до документів - під час запуску ВМ). Яким постачальником VM ви користуєтесь?
Божо

Я зміг відтворити це на Java 5, як на Sun / Linux, так і на Apple / OS X.
ZZ Coder

Це пояснює, чому defaultCharset () не кешує результат. Мені ще потрібно з’ясувати, яка справжня кодировка за замовчуванням використовується класами IO. Повинна бути інша кодова символіка за замовчуванням, кешована десь ще.
ZZ Coder

@ZZ Coder, я все ще досліджую це. Мені відомо лише те, що Charset.defaulyCharset () не викликається із sun.nio.cs.StreamEncoder в JVM 1.5. У JVM 1.6 викликається метод Charset.defaulyCharset (), що дає очікувані результати. Реалізація StreamEncoder JVM 1.5 якось кешує попереднє кодування.
bruno conde

Відповіді:


62

Це насправді дивно ... Після встановлення 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.

Що стосується того, чому класи IO, такі як OutputStreamWriterповернення несподіваного результату,
реалізація sun.nio.cs.StreamEncoder(witch використовується цими класами IO) також відрізняється для 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);
}

Але я згоден з коментарями. Не варто покладатися на це майно . Це деталь реалізації.


Щоб відтворити цю помилку, ви повинні бути на Java 5, а кодування JRE за замовчуванням має бути UTF-8.
ZZ Coder

2
Це написання до реалізації, а не до абстракції. Якщо ви покладаєтесь на документи, не оформлені документами, не дивуйтесь, якщо ваш код зламається при переході на нову версію платформи.
Макдауелл

24

Це помилка чи функція?

Схоже на невизначену поведінку. Я знаю, що на практиці ви можете змінити кодування за замовчуванням за допомогою властивості командного рядка, але я не думаю, що відбувається, коли ви це робите, визначено.

Ідентифікатор помилки: 4153515 щодо проблем із встановленням цієї властивості:

Це не помилка. Властивість "file.encoding" не вимагається специфікацією платформи J2SE; це внутрішня деталь реалізацій Sun, і її не слід перевіряти чи модифікувати за допомогою коду користувача. Він також призначений для читання; технічно неможливо підтримати налаштування цієї властивості на довільні значення в командному рядку або в будь-який інший час під час виконання програми.

Найкращим способом змінити кодування за замовчуванням, яке використовується віртуальною машиною та системою виконання, є зміна мови базової платформи перед запуском програми Java.

Я стискаюся, коли бачу, як люди встановлюють кодування в командному рядку - ви не знаєте, на який код це вплине.

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


4

По-перше, 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. * Із закритим кодом. Я здогадуюсь, що це не зовсім стосується кодування за тим же механізмом, що дивно.

Але, звичайно, ви завжди повинні вказувати, яке кодування ви маєте на увазі в цьому коді. Я б ніколи не покладався на стандартну платформу.


4

Поведінка насправді не така дивна. Розглядаючи реалізацію класів, це спричинено:

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

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


3

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


1

перевірити

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

здається, це таке саме кодування, як і те, що використовується в командному рядку вашої системи.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.