Кудрявые цитаты, вызывающие сканер 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.