Java не может открыть файл с суррогатными значениями Unicode в имени файла?

Я имею дело с кодом, который выполняет различные операции ввода-вывода с файлами, и я хочу, чтобы он мог работать с международными именами файлов. Я работаю на Mac с Java 1.5, и если имя файла содержит символы Unicode, для которых требуются суррогаты, JVM не может найти файл. Например, мой тестовый файл:

"草鷗外.gif", который разбивается на символы Java \u8349\uD85B\uDFF6\u9DD7\u5916.gif

Если я создам файл с этим именем файла, я не смогу его открыть, потому что я получаю исключение FileNotFound. Даже использование этого в папке, содержащей файл, не удастся:

File[] files = folder.listFiles(); 
for (File file : files) {
    if (!file.exists()) {
        System.out.println("Failed to find File"); //Fails on the surrogate filename
    }
}

Большая часть кода, с которым я на самом деле имею дело, имеет вид:

FileInputStream instream = new FileInputStream(new File("草鷗外.gif"));
// operations follow

Есть ли способ решить эту проблему, либо экранируя имена файлов, либо открывая файлы по-другому?


person Bear    schedule 09.10.2009    source источник
comment
Каково значение Charset.defaultCharset() в вашей среде?   -  person matt b    schedule 09.10.2009
comment
(К сожалению, у StackOverflow также есть проблема с суррогатами, и он удалил идеограмму U + 26FF6 из вопроса)   -  person bobince    schedule 09.10.2009
comment
Можете ли вы указать, что возвращает System.getProperty(file.encoding)? Попробуйте изменить кодировку java -dfile.encoding=ENCODING_GOES_HERE, если это не поможет изменить язык вашей системы. Если это тоже не работает, мы будем ждать эксперта, чтобы решить это.   -  person JCasso    schedule 10.10.2009
comment
Кодировка и кодировка файла - UTF-8.   -  person Bear    schedule 27.10.2009


Ответы (4)


Я подозреваю, что один из Java или Mac использует CESU-8 вместо правильного УТФ-8. Java использует «модифицированную UTF-8» (которая представляет собой небольшую вариацию CESU-8) для различных внутренних целей, но я не знал, что она может использоваться как файловая система/кодировка по умолчанию. К сожалению, у меня нет ни Mac, ни Java для тестирования.

«Изменено» — это модифицированный способ сказать «сильно прослушивается». Вместо вывода четырехбайтовой последовательности UTF-8 для дополнительных (не BMP) символов, таких как ????:

\xF0\xA6\xBF\xB6

он выводит последовательность в кодировке UTF-8 для каждого из суррогатов:

\xED\xA1\x9B\xED\xBF\xB6

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

Итак, сначала давайте просто проверим, как имена файлов на самом деле хранятся в вашей текущей файловой системе, используя платформу, которая использует байты для имен файлов, например Python 2.x:

$ python
Python 2.x.something (blah blah)
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> os.listdir('.')

В моей файловой системе (Linux, ext4, UTF-8) имя файла «草????鷗外.gif» выглядит так:

['\xe8\x8d\x89\xf0\xa6\xbf\xb6\xe9\xb7\x97\xe5\xa4\x96.gif']

это то, что вы хотите. Если это то, что вы получаете, вероятно, Java делает это неправильно. Если вы получите более длинную шестибайтовую версию:

['\xe8\x8d\x89\xed\xa1\x9b\xed\xbf\xb6\xe9\xb7\x97\xe5\xa4\x96.gif']

вероятно, OS X делает это неправильно ... всегда ли он хранит такие имена файлов? (Или файлы изначально пришли откуда-то еще?) Что, если вы переименуете файл в «правильную» версию?:

os.rename('\xe8\x8d\x89\xed\xa1\x9b\xed\xbf\xb6\xe9\xb7\x97\xe5\xa4\x96.gif', '\xe8\x8d\x89\xf0\xa6\xbf\xb6\xe9\xb7\x97\xe5\xa4\x96.gif')
person bobince    schedule 09.10.2009
comment
На самом деле это не ошибка, поскольку это часть спецификации (даже если это часто сбивает с толку). - person finnw; 10.10.2009
comment
Результатом команд python было правильное имя файла, которое вы указали первым, поэтому, должно быть, Java не работает хорошо. - person Bear; 26.10.2009
comment
О, это прискорбно. Даже если вы обнаружите ситуацию с неработающим CESU-8, я не могу придумать никакого способа обойти это и получить байт-ориентированный интерфейс имени файла. :-( Возможно, вам придется явно запретить суррогаты до тех пор, пока Sun не исправит это. Как плохо. - person bobince; 26.10.2009

Если локаль вашей среды по умолчанию не включает эти символы, вы не сможете открыть файл.

См.: File.exists() не работает с символами Юникода в имени

Изменить: Хорошо. Вам нужно изменить языковой стандарт системы. Независимо от того, какую ОС вы используете.

Изменить:

См.: Как открыть файлы, содержащие диакритические знаки в Java?

См.: JFileChooser на Mac не может видеть файлы, названные китайскими символами. ?

person JCasso    schedule 09.10.2009
comment
Нельзя ли это сделать без изменения локали системы? Программу, которую я создаю, нужно будет запускать в любой локали, и я должен иметь возможность вводить эти символы и работать с этими файлами даже в локали США/Англии. - person Bear; 26.10.2009
comment
Плохое решение - потому что приложение работает на пользователях, которые не сидят на моем компьютере. И у них разные локали, и у них нет прав администратора для этого. - person Dmitry Nelepov; 08.06.2013
comment
AFAIK другого решения нет. Это ограничение приходит с Sun/Oracle Java. Вы можете попробовать JFileChooser, если для вас нормально отображать диалоговое окно сохранения для ваших пользователей. - person JCasso; 10.06.2013

Это оказалось проблемой с Mac JVM (проверено на 1.5 и 1.6). К именам файлов, содержащим дополнительные символы/суррогатные пары, нельзя получить доступ с помощью класса Java File. В итоге я написал библиотеку JNI с вызовами Carbon для версии проекта для Mac (ick). Я подозреваю, что проблема CESU-8 упоминалась Бобинсом, поскольку вызов JNI для получения символов UTF-8 возвращал строку CESU-8. Не похоже, что это то, что вы действительно можете обойти.

person Bear    schedule 25.11.2009

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

... и обязательно читайте и записывайте содержимое файла, используя соответствующую кодировку, например: Files.readAllLines(myPath, StandardCharsets.UTF_8)

person pomo    schedule 24.02.2014