Как прочитать файл в кодировке UTF8, используя RandomAccessFile? - PullRequest
13 голосов
/ 01 апреля 2012

У меня есть текстовый файл в кодировке UTF8 (для символов, специфичных для языка). Мне нужно использовать RandomAccessFile для поиска конкретной позиции и чтения из.

Я хочу читать построчно.

String str = myreader.readLine(); //returns wrong text, not decoded 
String str myreader.readUTF(); //An exception occurred: java.io.EOFException

Ответы [ 8 ]

17 голосов
/ 13 декабря 2015

Вы можете преобразовать строку, прочитанную readLine, в UTF8, используя следующий код:

public static void main(String[] args) throws IOException {
    RandomAccessFile raf = new RandomAccessFile(new File("MyFile.txt"), "r");
    String line = raf.readLine();
    String utf8 = new String(line.getBytes("ISO-8859-1"), "UTF-8");
    System.out.println("Line: " + line);
    System.out.println("UTF8: " + utf8);
}

Содержимое MyFile.txt: (Кодировка UTF-8)

Привет из Украины

Вывод на консоль:

Line: ÐÑÐ¸Ð²ÐµÑ Ð¸Ð· УкÑаинÑ
UTF8: Привет из Украины
4 голосов
/ 01 апреля 2012

Вы не сможете пойти по этому пути. Функция seek позиционирует вас на некоторое количество байтов. Нет гарантии, что вы выровнены по границе символов UTF-8.

4 голосов
/ 01 апреля 2012

Документы API говорят следующее для readUTF8

Читает строку из этого файла. Строка была закодирована с использованием модифицированный формат UTF-8.

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

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

Ваша строка отформатирована таким образом?

Похоже, это объясняет ваш EOF, за исключением.

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

Простейший ответ, который я знаю:

try(BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream("jedis.txt"),"UTF-8"))){

    String line = null;
    while( (line = reader.readLine()) != null){
        if(line.equals("Obi-wan")){
            System.out.println("Yay, I found " + line +"!");
        }
    }
}catch(IOException e){
    e.printStackTrace();
}

Или вы можете установить текущую системную кодировку с системным свойством file.encoding в UTF-8.

java -Dfile.encoding=UTF-8 com.jediacademy.Runner arg1 arg2 ...

Вы также можете установить его как системное свойство во время выполнения с System.setProperty(...), если вам это нужно только для этого конкретного файла, но в таком случае, как я думаю, я бы предпочел OutputStreamWriter.

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

Если вы намереваетесь обнаружить ошибки декодирования в вашем файле, вам придется использовать подход InputStreamReader и использовать конструктор, который получает декодер.

В некотором роде

CharsetDecoder decoder = Charset.forName("UTF-8").newDecoder();
decoder.onMalformedInput(CodingErrorAction.REPORT);
decoder.onUnmappableCharacter(CodingErrorAction.REPORT);
BufeferedReader out = new BufferedReader(new InpuStreamReader(new FileInputStream("jedis.txt),decoder));

Вы можете выбирать между действиями IGNORE | REPLACE | REPORT

EDIT

Если вы настаиваете на использовании RandomAccessFile, вам необходимо знать точное смещение линии, которую вы собираетесь читать. И не только это, чтобы читать методом readUTF(), вы должны были записать файл методом writeUTF(). Поскольку этот метод, как указано выше в JavaDocs, ожидает определенного форматирования, в котором первые 2 байта без знака представляют длину в байтах строки UTF-8.

Как таковой, если вы делаете:

try(RandomAccessFile raf = new RandomAccessFile("jedis.bin", "rw")){

    raf.writeUTF("Luke\n"); //2 bytes for length + 5 bytes
    raf.writeUTF("Obiwan\n"); //2 bytes for length + 7 bytes
    raf.writeUTF("Yoda\n"); //2 bytes for lenght + 5 bytes

}catch(IOException e){
    e.printStackTrace();
}

У вас не должно возникнуть проблем при чтении из этого файла с использованием метода readUTF(), если вы можете определить смещение заданной строки, которую вы хотите прочитать.

Если вы откроете файл jedis.bin, вы заметите , это двоичный файл , а не текстовый файл.

Теперь я знаю, что "Luke\n" - это 5 байтов в UTF-8, а "Obiwan\n" - это 7 байтов в UTF-8. И что метод writeUTF() вставит 2 байта перед каждой из этих строк. Следовательно, до "Yoda\n" (5 + 2) + (7 + 2) = 16 байт.

Итак, я мог бы сделать что-то подобное, чтобы добраться до последней строки:

try (RandomAccessFile raf = new RandomAccessFile("jedis.bin", "r")) {

    raf.seek(16);
    String val = raf.readUTF();
    System.out.println(val); //prints Yoda

} catch (IOException e) {
    e.printStackTrace();
}

Но это не будет работать, если вы записали файл с классом Writer, потому что авторы не следуют правилам форматирования метода writeUFT().

В таком случае лучше всего, чтобы ваш двоичный файл был отформатирован таким образом, чтобы все строки занимали одинаковое количество места (число байтов, а не количество символов, потому что количество байтов является переменным в UTF-8 в зависимости от символов в вашей строке), если не все пространство необходимо, вы его дополняете:

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

1 голос
/ 30 января 2019

Как только вы окажетесь в заданной строке (это означает, что вы ответили на первую часть вашей проблемы, см. Ответ @martinjs), вы можете прочитать всю строку и сделать из нее String, используя оператор, приведенный вответ @Matthieu.Но чтобы проверить правильность данного утверждения, мы должны задать себе 4 вопроса.Это не самоочевидно.

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

Оператор, который читает строку и превращает ее в String:

String utf8 = new String(raf.readLine().getBytes("ISO-8859-1"), "UTF-8");
  1. Что такое байт в UTF-8?Это означает, что значения допустимы.Мы увидим, что вопрос фактически бесполезен, как только мы ответим на вопрос 2.
  2. readLine().UTF-8 байтов → UTF-16 байтов ок?Да.Поскольку UTF-16 дает значение всем целым числам от 0 до 255, закодированным в 2 байта, если байт значащего значения (MSB) равен 0. Это гарантировано readLine().
  3. getBytes("ISO-8859-1").Символы, закодированные в UTF-16 (Java String с 1 или 2 char (единица кода) на символ) → ISO-8859-1 байтов, хорошо?Да.Кодовые точки символов в строке Java ≤ 255, а ISO-8859-1 - это «сырая» кодировка, которая означает, что она может кодировать каждый символ в виде одного байта.
  4. new String(..., "UTF-8").ISO-8859-1 байтов → UTF-8 байтов в порядке?Да.Поскольку исходные байты взяты из текста в кодировке UTF-8 и были извлечены как есть, они все еще представляют текст, закодированный в кодировке UTF-8.

Относительно необработанного характера ISO-8859-1, в котором каждыйбайт (значение от 0 до 255) отображается на символ, я копирую / вставляю ниже комментария, который я сделал к ответу @ Matthieu.

См. этот вопрос относительно понятия "raw"кодирование с ISO-8859-1.Обратите внимание на разницу между ISO / IEC 8859-1 (определены 191 байт) и ISO-8859-1 (определены 256 байт).Вы можете найти определение ISO-8859-1 в RFC1345 и увидеть, что управляющие коды C0 и C1 отображаются на 65 неиспользуемых байтов ISO / IEC 8859-1.

1 голос
/ 26 мая 2018

Метод readUTF () метода RandomAccessFile обрабатывает первые два байта из текущего указателя как размер байтов, после двух байтов из текущей позиции, которые будут прочитаны и возвращены как строка.

Чтобы этот метод работал, содержимое должно быть записано с использованием метода writeUTF (), так как он использует первые два байта после текущей позиции для сохранения размера содержимого, а затем записывает содержимое. В противном случае, в большинстве случаев вы получите EOFException.

Подробнее см. http://www.zoftino.com/java-random-access-files.

1 голос
/ 13 августа 2017

Чтение файла через readLine () у меня сработало:

RandomAccessFile raf = new RandomAccessFile( ... );
String line;
while ((line = raf.readLine()) != null) { 
    String utf = new String(line.getBytes("ISO-8859-1"));
    ...
}

// my file content has been created with:
raf.write(myStringContent.getBytes());
1 голос
/ 17 мая 2016

Я понимаю, что это старый вопрос, но, похоже, он все еще имеет некоторый интерес, и не получил принятого ответа.

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

Если вы абсолютно не можете изменить сам файл («строковый файл») - как кажется, - вы всегда можете создать внешний индекс. В первый раз (и только первый раз) доступ к строковому файлу выполняется полностью (последовательно), вы записываете байтовую позицию начала каждой строки и заканчиваете записью конца положение файла (чтобы сделать жизнь проще). Это может быть достигнуто с помощью следующего кода:

myList.add(0); // assuming first string starts at beginning of file
while ((line = myRandomAccessFile.readLine()) != null) {
    myList.add(myRandomAccessFile.getFilePointer());
}

Затем вы записываете эти целые числа в отдельный файл («индексный файл»), который вы будете читать при каждом последующем запуске вашей программы и намерении получить доступ к строковому файлу. Чтобы получить доступ к n-й строке, выберите индекс n th и n+1 th из файла индекса (вызовите эти A и B). Затем вы пытаетесь расположить A в строковом файле и прочитать B-A байтов, которые затем декодируете из UTF8. Например, чтобы получить строку i:

myRandomAccessFile.seek(myList.get(i));
byte[] bytes = new byte[myList.get(i+1) - myList.get(i)];
myRandomAccessFile.readFully(bytes);
String result = new String(bytes, "UTF-8");

Однако во многих случаях было бы лучше использовать базу данных, такую ​​как SQLite, которая создает и поддерживает индекс для вас. Таким образом, вы можете добавлять и изменять дополнительные «строки», не создавая заново весь индекс. См. https://www.sqlite.org/cvstrac/wiki?p=SqliteWrappers для реализации Java.

0 голосов
/ 06 октября 2015

Я считаю API для RandomAccessFile сложным.

Если ваш текст на самом деле ограничен значениями UTF-8 0-127 (7 младших битов UTF-8), тогда безопасно использовать readLine(), но внимательно прочитайте эти Javadocs: это один странный метод. Цитировать:

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

Чтобы безопасно читать UTF-8, я предлагаю вам прочитать (некоторые или все) необработанные байты с комбинацией length() и read(byte[]). Затем преобразуйте ваши байты UTF-8 в Java String с помощью этого конструктора: new String(byte[], "UTF-8").

Чтобы безопасно написать UTF-8, сначала преобразуйте Java String в правильные байты с помощью someText.getBytes("UTF-8"). Наконец, запишите байты, используя write(byte[]).

...