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

Хочу поделиться своими мыслями о том, почему это происходит.

Предположим, у нас есть файл, в котором хранится нужный нам текст. Чтобы поработать с этим текстом в Java нам нужно загнать данные в String. Как это сделать?

readFile
String readFile(String fileName, String encoding) {
    StringBuilder out = new StringBuilder();
    char buf[] = new char[1024];
    InputStream inputStream = null;
    Reader reader = null;
    try {
        inputStream = new FileInputStream(fileName);
        reader = new InputStreamReader(inputStream, encoding);
        for (int i = reader.read(buf); i >= 0; i = reader.read(buf)) {
            out.append(buf, 0, i);
        }
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (reader != null) {
            try {
                reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    String result = out.toString();
    return result;
}


Обратите внимание, что для чтения файла недостаточно просто знать его имя. Нужно еще знать, в какой кодировке в нем находятся данные. Двоичное представление символов в памяти Java-машины и в файле на жестком диске практически никогда не совпадает, поэтому нельзя просто взять и скопировать данные из файла в строку. Сначала нужно получить последовательность байт, а уже потом произвести преобразование в последовательность символов. В приведенном примере это делает класс InputStreamReader.

Код получается достаточно громоздким при том, что необходимость в преобразовании из байтов в символы и обратно возникает очень часто. В связи с этим логичным было бы предоставить разработчику вспомомогательные функции и классы, облегчающие работу по перекодировке. Что для этого сделали разработчики Java? Они завели функции, которые не требуют указания кодировки. Например, класс InputStreamReader имеет конструктор с одним параметром типа InputStream.

readFile
String readFile(String fileName) {
    StringBuilder out = new StringBuilder();
    char buf[] = new char[1024];
    try (
        InputStream inputStream = new FileInputStream(fileName);
        Reader reader = new InputStreamReader(inputStream);
    ) {
        for (int i = reader.read(buf); i >= 0; i = reader.read(buf)) {
            out.append(buf, 0, i);
        }
    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
    String result = out.toString();
    return result;
}


Стало чуть попроще. Но здесь разработчики Java закопали серьезные грабли. В качестве кодировки для преобразования данных они использовали так называемый «default character encoding».

Default charset устанавливается Java-машиной один раз при старте на основании данных взятых из операционной системы и сохраняется для информационных целей в системном свойстве file.encoding. В связи с этим возникают следующие проблемы.

  1. Кодировка по умолчанию — это глобальный параметр. Нельзя установить для одних классов или функций одну кодировку, а для других — другую.
  2. Кодировку по умолчанию нельзя изменить во время выполнения программы.
  3. Кодировка по умолчанию зависит от окружения, поэтому нельзя заранее знать, какая она будет.
  4. Поведение методов, зависящих от кодировки по умолчанию, нельзя надежно покрыть тестами, потому что кодировок достаточно много, и множество их значений может расширяться. Может выйти какая-нибудь новая ОС с кодировкой типа UTF-48, и все тесты на ней окажутся бесполезными.
  5. При возникновении ошибок приходится анализировать больше кода, чтобы узнать, какую именно кодировку использовала та или иная функция.
  6. Поведение JVM в случае изменения окружения после старта становится непредсказуемо.

Но главное — это то, что от разработчика скрывается важный аспект работы программы, и он может просто не заметить, что использовал функцию, которая в разном окружении будет работать по-разному. Класс FileReader вообще не содержит функций, которые позволяют указать кодировку, хотя сам класс логичен и удобен, поэтому он стимулирует пользователя на создание платформозависимого кода.

Из-за этого происходят удивительные вещи. Например, программа может неправильно открыть файл, который ранее сама же создала.

Или, скажем, есть у нас XML-файл, у которого в заголовке написано encoding=«UTF-8», но в Java-программе этот файл открывается при помощи класса FileReader, и привет. Где-то откроется нормально, а где-то нет.

Особенно ярко проблема file.encoding проявляется в Windows. В ней Java в качестве кодировки по умолчанию использует ANSI-кодировку, которая для России равна Cp1251. В самой Windows говорится, что «этот параметр задает язык для отображения текста в программах, не поддерживающих Юникод». При чем здесь Java, которая изначально задумывалась для полной поддержки Юникода, непонятно, ведь для Windows родная кодировка — UTF-16LE, начиная где-то с Windows 95, за 3 года до выхода 1-й Java.

Так что если вы сохранили при помощи Java-программы файл у себя на компьютере и отправили его вашему коллеге в Европу, то получатель при помощи той же программы может и не суметь открыть его, даже если версия операционной системы у него такая же как и у вас. А когда вы переедете с Windows на Mac или Linux, то вы уже и сами свои файлы можете не прочитать.

А ведь еще есть Windows консоль, которая работает в OEM-кодировке. Все мы наблюдали, как вплоть до Java 1.7 любой вывод русского текста в черном окне при помощи System.out выдавал крокозябры. Это тоже результат использования функций, основанных на default character encoding.

Я у себя проблему кодировок в Java решаю следующим образом:

  1. Всегда запускаю Java с параметром -Dfile.encoding=UTF-8. Это позволяет убрать зависимость от окружения, делает поведение программ детерминированным и совместимым с большинством операционных систем.
  2. При тестировании своих программ обязательно делаю тесты с нестандартной (несовместимой с ASCII) кодировкой по умолчанию. Это позволяет отловить библиотеки, которые пользуются классами типа FileReader. При обнаружении таких библиотек стараюсь их не использовать, потому что, во-первых, с кодировками обязательно будут проблемы, а во-вторых, качество кода в таких библиотеках вызывает серьезные сомнения. Обычно я запускаю java с параметром -Dfile.encoding=UTF-32BE, чтобы уж наверняка.

Это не дает стопроцентной гарантии от проблем, потому что есть же еще и лаунчеры, которые запускают Java в отдельном процессе с теми параметрами, которые считают нужными. Например, так делали многие плагины к анту. Сам ант работал с file.encoding=UTF-8, но какой-нибудь генератор кода, вызываемый плагином, работал с кодировкой по умолчанию, и получалась обычная каша из разных кодировок.

По идее, со временем код должен становиться более качественным, программы более надежными, форматы более стандартизованными. Однако этого не происходит. Вместо этого наблюдается всплеск ошибок с кодировками в Java-программах. Видимо, это связано с тем, что в мир Java иммигрировали люди, не привыкшие решать проблему кодировок. Скажем, в C# по умолчанию применяется кодировка UTF-8, поэтому разработчик, переехавший с C#, вполне разумно считает, что InputStreamReader по умолчанию использует эту же кодировку, и не вдается в детали его реализации.

Недавно наткнулся на подобную ошибку в maven-scr-plugin.

Но настоящее удивление пришлось испытать при переезде на восьмерку. Тесты показали, что проблема с кодировкой затесалась в JDK.

Баг в JDK 8
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import javax.crypto.Cipher;

public class PemEncodingBugDemo {

    public static void main(String[] args) {
        try {
            String str = "ABCDEFGHIJKLMNOPQRSTUVWXYZ012345467890\r\n /=+-";
            byte ascii[] = str.getBytes(StandardCharsets.US_ASCII);
            byte current[] = str.getBytes(Charset.defaultCharset());
            if (Arrays.equals(ascii, current)) {
                System.err.printf("Run this test with non-ascii native encoding,%n");
                System.err.printf("for example java -Dfile.encoding=UTF-16%n");
            }
            Cipher.getInstance("RC4");
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
}


На девятке не воспроизводится, видимо, там уже починили.

Поискав по базе ошибок, я нашел еще одну недавно закрытую ошибку, связанную с теми же самыми функциями. И что характерно, их даже исправляют не совсем правильно. Коллеги забывают, что для стандартных кодировок, начиная с Java 7, следует использовать константы из класса StandardCharsets. Так что впереди, к сожалению, нас ждет еще масса сюрпризов.

Запустив grep по исходникам JDK, я нашел десятки мест, где используются платформозависимые функции. Все они будут работать некорректно в окружении, где родная кодировка, несовместима с ASCII. Например, класс Currency, хотя казалось бы, уж этот-то класс должен учитывать все аспекты локализации.

Когда некоторые функции начинают создавать проблемы, и для них существует адекватная альтернатива, давно известно, что нужно делать. Нужно отметить эти функции как устаревшие и указать, на что их следует заменить. Это хорошо зарекомендовавший себя механизм deprecation, который даже планируют развивать.

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

Функция На что заменить
Charset.defaultCharset() удалить
FileReader.FileReader(String) FileReader.FileReader(String, Charset)
FileReader.FileReader(File) FileReader.FileReader(File, Charset)
FileReader.FileReader(FileDescriptor) FileReader.FileReader(FileDescriptor, Charset)
InputStreamReader.InputStreamReader (InputStream) InputStreamReader.InputStreamReader (InputStream, Charset)
FileWriter.FileWriter(String) FileWriter.FileWriter(String, Charset)
FileWriter.FileWriter(String, boolean) FileWriter.FileWriter(String, boolean, Charset)
FileWriter.FileWriter(File) FileWriter.FileWriter(File, Charset)
FileWriter.FileWriter(File, boolean) FileWriter.FileWriter(File, boolean, Charset)
FileWriter.FileWriter(FileDescriptor) FileWriter.FileWriter(FileDescriptor, Charset)
OutputStreamWriter.OutputStreamWriter (OutputStream) OutputStreamWriter.OutputStreamWriter (OutputStream, Charset)
String.String(byte[]) String.String(byte[], Charset)
String.String(byte[], int, int) String.String(byte[], int, int, Charset)
String.getBytes() String.getBytes(Charset)

Чуть не забыл
Да, а что там с космическим аппаратом на Марсе?

Часть программного обеспечения для марсианского зонда Скиапарелли написали на Java, на актуальной в то время версии 1.7. Запустили изделие весной, и путь к месту назначения составил полгода. Пока он летел, в Европейском космическом агентстве обновили JDK.

Ну а что? Разработка софта для нынешней миссии завершена, надо делать ПО уже для следующей, а мы все еще на семерке сидим. НАСА и Роскосмос уже давно на восьмерку перешли, а там лямбды, стримы, интерфейсные методы по умолчанию, новый сборщик мусора, и вообще.

Обновились и перед посадкой отправили на космический аппарат управляющую команду не в той кодировке, в которой он ожидал.

Поделиться с друзьями
-->

Комментарии (52)


  1. dyakhnov
    21.11.2016 09:41
    +22

    А можно ссылку на оригинал: кем найдена причина падения и в чем она заключается? Вроде бы расследование еще не завершено пока…


    1. ainoneko
      21.11.2016 19:01
      +2

      Что сомнительно: вряд ли команды должны зависеть от кодировки (разве что туда проник автор статьи со своим «параметром -Dfile.encoding=UTF-32BE, чтобы уж наверняка».)
      А то можно было бы сделать заголовок ещё лучше: «Java снова не тормозит» (если кто помнит про события 11-летной давности: «Робот под управлением Linux с софтом на Java убился об стену»).


  1. mayorovp
    21.11.2016 10:42

    А тем временем в Microsoft портировали проблему с кодировками из Java в powershell :)


    Рекомендуемый всюду способ считывания xml-файлов:


     [xml]$xml = Get-Content Путь:\к\файлу.xml

    При этом сначала считывается файл в системной кодировке, а потом превращается в DOM. А на то, что в самом файле может быть указана другая кодировка — не важно...


    1. rbobot
      21.11.2016 10:48
      +1

      Get-Content — не специализированный коммандлет для работы с xml и он не ожидает указания кодировки от файла. Это просто получение содержимого, вся xml магия в приведении.
      Вместе с тем, для Get-Content можно указать кодировку параметром -Encoding и не иметь проблем.


      1. mayorovp
        21.11.2016 11:14
        +1

        Это если кодировка файла — известна. А если нет?


        Тот же XmlDocument, лежащий за синонимом [xml], умеет читать любой поток байт, определяя кодировку из декларации. Так же как XmlReader и XDocument.


        Но в powershell эту возможность так просто использовать не получится — вот и гуляет ошибочное решение по интернету…


        1. rbobot
          21.11.2016 15:37
          -1

          Ну, если это реально насущная проблема, можно прочитать первую строчку и сматчить из нее кодировку. PS же скриптовый язык и такие вещи тут на каждом углу, иначе получится очередной C# — за простоту и универсальность надо чем-то платить.


          1. mayorovp
            21.11.2016 15:46
            +1

            Это ж уже велосипед будет… Правильный способ — такой:


            $xml = New-Object ([xml])
            $xml.Load((Get-Item путь\к\файлу.xml))

            Если путь абсолютный — то Get-Item не нужен


            Проблема исключительно в том, что в инете советуют способ через Get-Content


            1. rbobot
              23.11.2016 13:55

              Да, и правда лучше, спасибо.
              Жуткий формат, я его избегаю постоянно, а тут упала задача как-раз в xml в 1251.


    1. tandzan
      21.11.2016 23:07
      +1

      Они еще и в VC Runtime видимо портировали:

      wcout << L"test" << endl; //будет выведено
      wcout << L"тест" << endl; //не будет выведено
      wcout << L"test" << endl; //НЕ будет выведено
      

      Приходится делать заглушки с WriteConsoleW


      1. mayorovp
        22.11.2016 06:27

        Это там всегда было!


  1. fogone
    21.11.2016 10:58
    +1

    Чтобы поработать с этим текстом в Java нам нужно загнать данные в String. Как это сделать?

    Или еще вот так можно:
    String str = new String(Files.readAllBytes(Paths.get("file")), StandardCharsets.UTF_8);
    


  1. Beholder
    21.11.2016 11:06
    +11

    NASA широко использует Java, но только на наземных рабочих станциях, в частности для задач анализа и визуализации, но никто не будет её устанавливать непосредственно в аппараты. Так что громкий заголовок очень похож на случай так называемого вранья.


    1. mayorovp
      21.11.2016 11:20
      +4

      Там же все написано русским по белому! Java стоит не на аппарате, а в центре управления. Пруф конечно же все еще нужен — но ваше возражение совсем не по делу.


      1. TimsTims
        21.11.2016 15:07
        +1

        > Java стоит не на аппарате
        Как же не на аппарате, если вы сами написали:
        > Часть программного обеспечения для марсианского зонда Скиапарелли написали на Java

        Или вы хотите улизнуть и сейчас скажете: «ну ПО то написали, но это не значит, что оно там используется!»


        1. mayorovp
          21.11.2016 15:48
          -2

          Во-первых, очевидно что имелась в виду та часть ПО, которая осталась на Земле. Во-вторых, пост писал не я.


          1. dimm_ddr
            21.11.2016 16:39
            +3

            Ну все-таки не очевидно.


  1. MzMz
    21.11.2016 11:25

    При чем здесь Java, которая изначально задумывалась для полной поддержки Юникода, непонятно, ведь для Windows родная кодировка — UTF-16LE, начиная где-то с Windows 95, за 3 года до выхода 1-й Java.


    У меня например консоль cmd.exe выдает:
    H:\>chcp
    Active code page: 437


    Выводить кириллический текст в нее даже в UTF-8 бесполезно. При этом поставить правильную кодировку еще полдела, надо еще и шрифт поменять на TrueType.

    Java оперирует внутри себя 16-битными Unicode сodepoint, но внешний мир жесток и несовершенен (особенно в Windows), поэтому статья правильно акцентирует внимание на том, что про кодировку помнить нужно и проставлять лучше явно. С другой стороны, на правильных операционных системах дефолтная кодировка в 99% работает корректно.


    1. mayorovp
      21.11.2016 12:30

      Консоль в винде всегда можно переключить в нужный режим. Кстати, начиная с win10 она даже последовательности VT100 поддерживает, если их включить...


      1. sergey-b
        21.11.2016 23:35
        -1

        Это очень хорошо. Осталось только определить, когда какой режим нужен, и кто его должен переключать.


        1. mayorovp
          22.11.2016 06:30

          В идеале, переключать должен был рантайм Java. При запуске или при первом обращении к консоли. И еще перед выходом — обратно.


  1. fogone
    21.11.2016 12:13
    +1

    Надо заметить, что, хотя проблема такая действительно существует, перечисленные в таблице методы — это легаси, существующее для совместимости (которая в мире java очень важна), а все новые api учитывают проблемы дефолтной кодировки и используют UTF8 в качестве кодировки по-умолчанию. Это хороший повод, например, использовать новый api для работы с файловой системой — там эти проблемы учтены, а для чтения файла в строку есть удобные методы, делающие это в одну строку кода.


    1. mayorovp
      21.11.2016 12:32
      +1

      Так о том и речь, что надо бы им @Deprecated поставить.


      1. Lure_of_Chaos
        21.11.2016 13:42

        Но нет, они @Deprecated поставили именно на Date, который до сих пор никуда не делся и активно используется.


    1. sergey-b
      21.11.2016 23:10

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


      1. fogone
        23.11.2016 12:07

        Да, вероятно, было бы лучше, если бы такие потенциально опасные методы были помечены как @Deprecated. Но где их используют в JDK?


        1. sergey-b
          23.11.2016 21:43

          Например, вот здесь http://hg.openjdk.java.net/jdk9/jdk9/jdk/rev/4235be4c9432


          1. fogone
            24.11.2016 13:39

            ну да, но судя по тому, что этот код исправили и указали кодировку явно, это скорее баг, чем намеренно использование этого конструктора. И да, я согласен, что если бы они были @Deprecated, то такие ошибки отлавливать было бы намного проще.


  1. TimsTims
    21.11.2016 12:38
    +4

    > Найдена причина аварии
    Хотя бы сменили бы желтушный заголовок, раз это лишь предположение. Мы ведь не «горячие новости» из smi2 тут читаем, а вполне адекватные посты.

    Укажите примерно так:
    > Откуда в Java всплывают проблемы с кодировками и возможная причина падения марсианского зонда


    1. sergey-b
      21.11.2016 22:44

      Вы правы. Спасибо.


  1. Lure_of_Chaos
    21.11.2016 13:41
    +2

    Статью можно резюмировать так: указывайте кодировку, передавая в соотв. методах параметр Charset — обычно StandardCharsets.UTF_8 хватает, на что указывают современные IDE (отдельное спасибо JetBrains за IDEA и анализатор «из коробки») и анализаторы типа FindBugs — благодаря им столько потенциальных багов было выявлено еще до тестирования!


    1. guai
      21.11.2016 15:12

      В идее в самой трабла с кодировками недавно попадалась :)


    1. sergey-b
      21.11.2016 22:56

      Надо будет попробовать IDEA. В Eclipse пока не видел такого валидатора.


    1. grossws
      22.11.2016 01:57

      Ещё крайне полезно использовать forbidden-apis checker, умеет ловить следующее:


      • jdk-unsafe-* — использование методов jdk, которые неявно используют charset, locale или timezone по умолчанию;
      • jdk-deprecated-* — использование deprecated методов и jdk;
      • jdk-internal-* — использование внутренних методов jdk из пакетов, выдаваемых Security.getProperty("package.access");
      • jdk-non-portable — множество потенциально несовместимых api, включающее jdk-internal;
      • jdk-system-out — думаю и так ясно;
      • jdk-reflection — куски использования reflection, которые должны поломаться в java 9;
      • commons-io-unsafe-* — использование методов с default charset.

      jdk-internal @openjdk 8u122
      • com.oracle.webservices.internal.
      • com.oracle.xmlns.internal.
      • com.sun.activation.registries.
      • com.sun.corba.se.
      • com.sun.imageio.
      • com.sun.istack.internal.
      • com.sun.jmx.
      • com.sun.media.sound.
      • com.sun.naming.internal.
      • com.sun.org.apache.bcel.internal.
      • com.sun.org.apache.regexp.internal.
      • com.sun.org.apache.xalan.internal.extensions.
      • com.sun.org.apache.xalan.internal.lib.
      • com.sun.org.apache.xalan.internal.res.
      • com.sun.org.apache.xalan.internal.templates.
      • com.sun.org.apache.xalan.internal.utils.
      • com.sun.org.apache.xalan.internal.xslt.
      • com.sun.org.apache.xalan.internal.xsltc.cmdline.
      • com.sun.org.apache.xalan.internal.xsltc.compiler.
      • com.sun.org.apache.xalan.internal.xsltc.trax.
      • com.sun.org.apache.xalan.internal.xsltc.util.
      • com.sun.org.apache.xerces.internal.
      • com.sun.org.apache.xml.internal.res.
      • com.sun.org.apache.xml.internal.security.
      • com.sun.org.apache.xml.internal.serializer.utils.
      • com.sun.org.apache.xml.internal.utils.
      • com.sun.org.apache.xpath.internal.
      • com.sun.org.glassfish.
      • com.sun.proxy.
      • com.sun.xml.internal.
      • jdk.internal.
      • jdk.nashorn.internal.
      • jdk.nashorn.tools.
      • oracle.jrockit.jfr.
      • org.jcp.xml.dsig.internal.
      • sun.


  1. Lure_of_Chaos
    21.11.2016 13:45
    -1

    И, кстати, поддержу: почему стандартная кодировка в винде не UTF-8, как в тех же линухах?


    1. MacIn
      21.11.2016 21:19
      +2

      Когда «в тех же линухах» появилась UTF-8 как стандарт?
      Когда в винде появился юникод?


    1. sergey-b
      21.11.2016 22:55
      +2

      Дело в том, что само понятие стандартной кодировки размыто. Одни программы работают в чистом ASCII, другие UTF-8, функции Win32 работают в UTF-16 или в Ansi. Консоль работает в OEM и легкоп переключается командой chcp. У cygwin своя система локализации. Вполне возможно, что в будущих версиях Windows кодировки могут быть у разных пользователей разные. Какую кодировку считать стандартной, непонятно.


  1. Lure_of_Chaos
    21.11.2016 13:47

    А еще, мы тут на работе словили exception при JAXB анмаршаллинге xml, который приехал с BOM, это было очень весело править :)


  1. kolemik
    21.11.2016 14:56
    +5

    Последний абзац — это шутка?


    1. sergey-b
      21.11.2016 23:19
      -1

      Да. Это шутка. Я старался, чтобы было понятно. Загнал текст о марсианском зонде под спойлер. Но, видимо, перемудрил с заголовком.


  1. dom1n1k
    21.11.2016 17:04
    +1

    Перед самой посадкой никакие команды на зонд не отправляются (пинг не позволяет), она проходит в автономном режиме.


    1. sergey-b
      21.11.2016 23:25

      Согласен. Перед посадкой что-либо отправлять на аппарат уже бесполезно. В процессе полета обычно производят коррекцию траектории командами с Земли. А посадка производится в полностью автоматическом режиме.


  1. zirix
    21.11.2016 21:07
    -2

    byte ascii[] = str.getBytes(StandardCharsets.US_ASCII);
    byte current[] = str.getBytes(Charset.defaultCharset()); // Charset.defaultCharset() == UTF-16 при -Dfile.encoding=UTF-16
    

    Вам не кажется что число байтов(и их значение) в UTF-8 и в UTF-16 будет разное? При этом UTF-8 будет такой же как ASCII если не использовать символы >=127 (в этом моменте могу ошибаться).

    Возможно я вас не понял, но тут никакой ошибки в JDK я не вижу.


    1. zirix
      21.11.2016 21:30
      -1

      Читал невнимательно, искал где тут про зонд написано.
      Cipher.getInstance(«RC4»); вылетает…


    1. sergey-b
      21.11.2016 23:29

      Это я добавил, чтобы было понятно, в каких условиях работает test case. Ведь установить кодировку по умолчанию изнутри программы невозможно, поэтому тест основывается на том, что его запускают в определенном окружении. Тестировщики, они ведь они такие: прогонят тест и напишут, что «не воспроизводится».


  1. sand14
    21.11.2016 22:12

    Видимо, это связано с тем, что в мир Java иммигрировали люди, не привыкшие решать проблему кодировок. Скажем, в C# по умолчанию применяется кодировка UTF-8, поэтому разработчик, переехавший с C#, вполне разумно считает, что InputStreamReader по умолчанию использует эту же кодировку, и не вдается в детали его реализации.

    Java и C#, нужно смотреть контракт класса, который используешь.

    В C# действительно при чтении файлов по умолчанию используется UTF8, или определяется по заголовку (для XML) или BOM, если заголовок не определен или отсутствует BOM.
    Тем не менее, и тут есть вероятность ошибки автоматического определения кодировки.
    А что, если BOM это BOM, а полезные данные? — при создании объекта-кодировки можно указать, использовать ли BOM, когда эта кодировка передается ридеру (и тут тоже есть свои умолчания — использовать BOM).
    Поэтому программист тут тоже должен понимать, что именно делает, и принимать решение, что делать.

    И в C# хватает своих неоднозначностей — например, использование глобальных настроек CurrentCulture при работе со строками.


  1. vlanko
    21.11.2016 22:46

    Да, важная проблема. Я использовал тупо FileWriter, и при некоторых параметрах он писал китайские иероглифы.


  1. semio
    21.11.2016 22:47

    Что характерно, в классе java.nio.Files, появившемся в java 7, по умолчанию используется UTF-8.
    А в библиотеке Guava, в аналогичном классе Files, вообще нет методов, имеющих кодировку по умолчанию (например, Files.toString()).


  1. vadim_mishchenko
    21.11.2016 22:47

    «откуда в Java всплывают проблемы с кодировками» — почему то, что инженер не прочитал что АПИ, стало проблемой Java?


  1. kbac9
    21.11.2016 22:51

    А еще в далеком 2005-ом году, когда робот Томми разбился об стену на гонках роботов, тоже говорили, что Java виновата:)


  1. sadr0b0t
    24.11.2016 02:22

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

    Если все начнут хардкодить кодировки, получится еще больший ацкий зоопарк, на который даже параметрами командной строки не повлиять. Просто кодировка по умолчанию должна быть не системная, а на всех платформах всегда и везде — UTF-8. Кому хочется легаси и экзотики — пускай передают параметры или хардкодят через предложенные методы, но в этом случае при каждом запуске приложения и каждом чтении файла должен выскакивать назойливый диалог: «вы используете устаревшую кодировку, настоятельно рекомендуем конвертировать ваши файлы в UTF-8».


  1. selenite
    24.11.2016 02:52

    > Например, так делали многие плагины к анту. Сам ант работал с file.encoding=UTF-8, но какой-нибудь генератор кода, вызываемый плагином, работал с кодировкой по умолчанию, и получалась обычная каша из разных кодировок.

    1. окей гугл, исходники openjdk без регистрации.
    2. окей, написать обертку для бинарники java, подставляющую нужные аргументы — не вариант?
    3. если «не вариант» — тем же easyhook/LD_PRELOAD/%решениенейм% быстро запилить фильтр для запускающихся процессов с целью передачи нужных аргументов. Тем более что для Java на современных машинах под виндой приходится примерно таким образом «портить» обращение к функциям, возвращающим информацию о DPI дисплея ради некоторых древних приложений типа Packet Tracer.


  1. dyakhnov
    24.11.2016 06:35

    А вот и официальный отчет из ESA (расследование еще не завершено):

    When merged into the navigation system, the erroneous information generated an estimated altitude that was negative – that is, below ground level. This in turn successively triggered a premature release of the parachute and the backshell, a brief firing of the braking thrusters and finally activation of the on-ground systems as if Schiaparelli had already landed. In reality, the vehicle was still at an altitude of around 3.7 km.


    Короче какой-то сенсор послал максимальное значение в компьютер и тот решил включить тормоза и отстрелить парашют.