Кудрявые цитаты, вызывающие сканер Java hasNextLine(), являются ложными - почему?
У меня возникла проблема с получением java.util.Scanner для чтения текстового файла, который я сохранил в "Блокноте", хотя он отлично работает с другими. В основном, когда он пытается прочитать файл проблемы, он появляется полностью с пустыми руками - hasNextLine() является ложным, буфер пуст и т.д. Я сузил его до того, что он даже не прочитает первую строку, если является фигурной цитатой в любом месте файла. Никакие исключения не выбрасываются. Обратите внимание, что BufferedReader в том же файле не имеет проблемы.
try {
int count = 0;
Scanner scanner = new Scanner(new File("C:/myfile.txt"));
while (scanner.hasNextLine()) {
count++;
scanner.nextLine();
}
scanner.close();
System.out.print(count);
count = 0;
BufferedReader reader = new BufferedReader(new FileReader("C:/myfile.txt"));
while (reader.readLine() != null) {
count++;
}
reader.close();
System.out.print(count);
}
catch(IOException e) {
e.printStackTrace();
}
В приведенном выше коде, читая файл, который содержит только одну фигурную цитату, выводится "01". Поиски Google заставили меня попробовать следующее:
Scanner scanner = new Scanner(new File("C:/myfile.txt"), "ISO-8859-1");
Это заставляет его работать (т.е. печатает "11" ). Я также заметил, что если я пойду в "Блокнот" и сделаю "Сохранить как...", то по умолчанию кодировка "ANSI" . Если я изменил это на "UTF-8" и сохранил файл, то сканер (без кодировки) также будет работать. Если я скажу сканеру "UTF-8" , тогда понятно, что он работает, только если я сохраню как UTF-8, но "ISO-8859-1", похоже, заставляет его работать, даже если я сохраню его как "ANSI" .
Итак, я знаю, что это имеет какое-то отношение к кодировке файлов, но проблема в том, что я ничего не понимаю о кодировке файлов. Мое знание того, что означает "ISO-8859-1", крайне неопределенно; почему это заставляет его работать независимо от того, как я могу сохранить файл? Почему BufferedReader работает независимо?
EDIT:
Ссылки/комментарии ниже действительно помогли мне в правильном направлении! Кажется, я понял.
Прежде всего, в "Блокноте":
- "ANSI" - CP1252
- "Юникод" - это UTF-16LE
- "UTF-8" ... ну, UTF-8
В шестнадцатеричном виде фигурный апостроф представлен как:
- CP1252: 92
- UTF-16LE: 1920
- UTF-8: E2 80 99
Используемая по умолчанию кодировка Java в моей системе, в соответствии с Charset.defaultCharset(), является UTF-8. Поэтому, когда я сохранил файл в UTF-8, сканер знал, чего ожидать. Однако, когда я сохранил файл в CP1252, он задохнулся, как только он ударил "92", потому что это не допустимый способ представления символа в этой кодировке. Он отлично работает, если в файле нет таких градиентов - шестерка для "hello world" оказывается одинаковой как в CP1252, так и в UTF-8 и не вызывает проблем.
UTF-8 не работает с файлом UTF-16, потому что он не знает, что делать с отметкой байтового порядка ( "FFFE" ), независимо от того, какие символы находятся в файле.
С другой стороны, когда я устанавливаю сканер на CP1252 или ISO-8859-1, он гораздо более терпим. Он не обязательно правильно интерпретирует персонажей, заметьте, но нет ничего, что помешало бы ему распознавать строки в файле и зацикливаться.
Насколько у Сканера есть проблема, но FileReader/BufferedReader этого не делает, я собираюсь угадать, что это потому, что сканеру нужно токенизировать файл, т.е. интерпретировать символы, чтобы он мог идентифицировать пробелы и другие шаблоны, поэтому он задыхается, когда есть что-то неузнаваемое. Читателю это не нужно. Все, что нужно идентифицировать, это разрывы строк.
Ответы
Ответ 1
Если вы не укажете кодировку при создании сканера, она попытается очистить кодировку на основе байтового байта (BOM), который является первым количеством байтов файла. Если у него его нет, он будет по умолчанию использовать все настройки по умолчанию, которые использует ОС. Поскольку вы используете Windows, по умолчанию используется cp-1252. Кажется, что блокнот сохраняет ваш текстовый файл, используя ISO-8859-1, который похож, но не такой, как cp-1252. См. Эту ссылку для получения более подробной информации:
http://www.i18nqa.com/debug/table-iso8859-1-vs-windows-1252.html
Когда вы сохраняете его как UTF-8, он, вероятно, помещает спецификацию UTF-8 в начало файла, и сканер может забрать его.
Если вы хотите больше взглянуть на спецификацию, посмотрите в википедии - статья неплохая. Вы также можете загрузить PSPad и открыть текстовый файл в шестнадцатеричном режиме, чтобы просмотреть отдельные байты. Надеюсь, что помогает:)
Ответ 2
Scanner
hasNextLine
метод просто вернет false, если в исходном файле возникла ошибка кодирования. Без каких-либо исключений. Это разочаровывает и не документируется нигде, даже в документации JDK 8.
Если вы просто хотите прочитать файл по очереди, используйте это вместо:
final BufferedReader input = new BufferedReader(new InputStreamReader(new FileInputStream("inputfile.txt"), "inputencoding"));
while (true) {
String line = input.readLine();
if (line == null) break;
// process line
}
input.close();
Убедитесь, что значение inputencoding
, приведенное выше, заменено правильной кодировкой файла. Скорее всего это utf-8
или ascii
. Даже если кодирование несовместимо, оно не будет преждевременно прекращаться, как Scanner
.
Ответ 3
Некоторое время назад у меня была аналогичная проблема с файлом конфигурации, который был отредактирован пользователем. Поскольку я никогда не знаю, какой тип редактора будет использовать, я пробую это:
org.mozilla.universalchardet.UniversalDetector
можно получить здесь:
https://code.google.com/p/juniversalchardet/
Обнаружение кодировки char не просто, поэтому я не могу быть уверенным, что эта библиотека работает в любом состоянии, но для меня было достаточно. Посмотрите, возможно, поможет как-то обнаружить вашу кодировку, а затем установить ее на Scanner
.