Подтвердить что ты не робот

Как открыть файлы с акцентами в Java?

(редактирование для уточнения и добавления кода)

Здравствуйте, У нас есть требование проанализировать данные, отправленные пользователями по всему миру. Наши системы Linux имеют стандартную локаль en_US.UTF-8. Однако мы часто получаем файлы с диакритическими знаками в их именах, таких как "special_á_ã_è_characters.doc". Хотя ОС прекрасно справляется с этими файлами, а strace показывает, что ОС передает правильное имя файла в программу Java, Java перенаправляет имена и бросает исключение "file not found", которое пытается открыть их.

Эта простая программа может проиллюстрировать проблему:

import java.io.*;
import java.text.*;

public class load_i18n
{
  public static void main( String [] args ) {
    File actual = new File(".");
    for( File f : actual.listFiles()){
      System.out.println( f.getName() );
    }
  }
}

Запуск этой программы в каталоге, содержащем файл special_á_ã_è_characters.doc, и по умолчанию для английского языка по умолчанию:

special_�_�_�_characters.doc

Установка языка через экспорт LANG = es_ES @UTF-8 правильно выдает имя файла (но является неприемлемым решением, так как вся система теперь запущена на испанском языке.) Явное определение локали внутри программы, как показано ниже, не имеет эффекта или. Ниже я изменил программу: a) попытался открыть файл и b) распечатать имя как в ASCII, так и в виде байтового массива, когда он не может открыть файл:

import java.io.*;
import java.util.Locale;
import java.text.*;

public class load_i18n
{
  public static void main( String [] args ) {
    // Stream to read file
    FileInputStream fin;

    Locale locale = new Locale("es", "ES");
    Locale.setDefault(locale);
    File actual = new File(".");
    System.out.println(Locale.getDefault());
    for( File f : actual.listFiles()){
      try {
        fin = new FileInputStream (f.getName());
      }
      catch (IOException e){
        System.err.println ("Can't open the file " + f.getName() + ".  Printing as byte array.");
        byte[] textArray = f.getName().getBytes();
        for(byte b: textArray){
          System.err.print(b + " ");
        }
        System.err.println();
        System.exit(-1);
      }

      System.out.println( f.getName() );
    }
  }
}

Это приводит к выходу

es_ES
load_i18n.class
Can't open the file special_�_�_�_characters.doc.  Printing as byte array.
115 112 101 99 105 97 108 95 -17 -65 -67 95 -17 -65 -67 95 -17 -65 -67 95 99 104 97 114 97 99 116 101 114 115 46 100 111 99

Это показывает, что проблема не просто проблема с отображением консоли, так как одни и те же символы и их представления выводятся в байтах или в формате ASCII. Фактически дисплей консоли работает даже при использовании LANG = en_US.UTF-8 для некоторых утилит, таких как bash echo:

[[email protected] tmp]$ echo $LANG
en_US.UTF-8
[[email protected] tmp]$ echo *
load_i18n.class special_á_ã_è_characters.doc
[[email protected] tmp]$ ls
load_i18n.class  special_?_?_?_characters.doc
[[email protected] tmp]$

Можно ли изменить этот код таким образом, чтобы при запуске в Linux с LANG = en_US.UTF-8 он читал имя файла таким образом, чтобы его можно было успешно открыть?

4b9b3361

Ответ 1

Во-первых, используемая кодировка символов не имеет прямого отношения к языку. Поэтому изменение языкового стандарта не поможет.

Во-вторых, � типичен для символ замены Unicode U + FFFD , который печатается в ISO-8859-1 вместо UTF-8. Вот доказательство:

System.out.println(new String("�".getBytes("UTF-8"), "ISO-8859-1")); // �

Итак, есть две проблемы:

  • Ваша JVM читает эти специальные символы как .
  • Ваша консоль использует ISO-8859-1 для отображения символов.

Для Sun JVM аргумент VM -Dfile.encoding=UTF-8 должен исправить первую проблему. Вторая проблема должна быть исправлена ​​в настройках консоли. Если вы используете, например, Eclipse, вы можете изменить его в Window > Preferences > General > Workspace > Text File Encoding. Установите его также в UTF-8.


Обновить: согласно вашему обновлению:

byte[] textArray = f.getName().getBytes();

Это должно было быть следующим, чтобы исключить влияние кодировки по умолчанию платформы:

byte[] textArray = f.getName().getBytes("UTF-8");

Если это все еще отображается одинаково, проблема кроется глубже. Какой JVM вы используете? Сделайте a java -version. Как говорилось ранее, аргумент -Dfile.encoding является спецификацией Sun JVM. Некоторые Linux-машины поставляются с JVM GNU или OpenJDK JVM, и этот аргумент может не работать.

Ответ 2

Это ошибка в JRE/JDK, которая существует в течение многих лет.

Как исправить java, если при отказе открыть файл со специальным charater в имени файла?

Ошибка файла .exists() с символами Unicode в имени

Теперь я перехожу к новому отчету об ошибках, поскольку LC_ALL = en_us исправит некоторые случаи, между тем он не сможет выполнить некоторые другие случаи.

Ответ 3

Это ошибка в java файле старого java файла api, возможно, только на Mac? Во всяком случае, новый java.nio api работает намного лучше. У меня есть несколько файлов, содержащих символы Unicode, которые не загружаются с использованием классов java.io.... После преобразования всего моего кода для использования java.nio.Path ВСЕ начало работать. И я заменил apache FileUtils (который имеет ту же проблему) с java.nio.Files...

Ответ 4

Системное свойство Java file.encoding должно соответствовать кодировке символов консоли. Свойство должно быть установлено при запуске java в командной строке:

java -Dfile.encoding=UTF-8 …

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

Ответ 5

Ну, я весь день был задушен этой проблемой! Мой предыдущий (неправильный) код был таким же, как вы:

for(File f : dir.listFiles()) {
 String filename = f.getName(); // The filename here is wrong !
 FileInputStream fis = new FileInputStream (filename);
}

и он не работает (я использую Java 1.7 Oracle для CentOS 6, LANG и LC_CTYPE = fr_FR.UTF-8 для всех пользователей, кроме zimbra = > LANG и LC_CTYPE = C, что, кстати, является причиной этого но я не могу изменить это без риска того, что Zimbra перестанет работать...)

Итак, я решил использовать новые классы пакета java.nio.file(файлы и пути):

DirectoryStream<Path> paths = Files.newDirectoryStream(Paths.get(outputName));
for (Iterator<Path> iterator = paths.iterator(); iterator.hasNext();) {
  Path path = iterator.next();
  String filename = path.getFileName().toString(); // The filename here is correct
  ...
}

Итак, если вы используете Java 1.7, вам следует попробовать новые классы в пакет java.nio.file: он сохранил мой день!

Надеюсь, что это поможет

Ответ 6

В использовании DirectoryStream не забудьте закрыть поток (здесь могут помочь try-with-resources)