Java: чтение строк из файла с произвольным доступом с буферизованным вводом - PullRequest
7 голосов
/ 29 ноября 2010

Раньше я никогда не сталкивался с Java IO API, и сейчас я очень расстроен. Мне трудно поверить, насколько это странно и сложно, и как трудно было бы выполнить простую задачу.

Моя задача: у меня 2 позиции (начальный байт, конечный байт), pos1 и pos2. Мне нужно прочитать строки между этими двумя байтами (включая начальный, не включая конечный) и использовать их в качестве строковых объектов UTF8.

Например, в большинстве языков сценариев это будет очень простой 1-2-3-строчный аналог (в Ruby, но он будет по сути одинаковым для Python, Perl и т. Д.):

f = File.open("file.txt").seek(pos1)
while f.pos < pos2 {
  s = f.readline
  # do something with "s" here
}

Это быстро приходит в ад с API Java IO;) На самом деле, я вижу два способа чтения строк (заканчивающихся \n) из обычных локальных файлов:

  • RandomAccessFile имеет getFilePointer() и seek(long pos), но это readLine () читает строки не-UTF8 (и даже не байтовые массивы), но очень странные строки со сломанной кодировкой и у него нет буферизации (что, вероятно, означает, что каждый вызов read*() будет транслироваться в одну недопустимую ОС read() => довольно медленно).
  • BufferedReader имеет отличный метод readLine(), и он может даже выполнять поиск с помощью skip(long n), но он не может определить четное число уже прочитанных байтов, не говоря уже о текущем позиция в файле.

Я пытался использовать что-то вроде:

    FileInputStream fis = new FileInputStream(fileName);
    FileChannel fc = fis.getChannel();
    BufferedReader br = new BufferedReader(
            new InputStreamReader(
                    fis,
                    CHARSET_UTF8
            )
    );

... и затем с помощью fc.position(), чтобы получить текущую позицию чтения файла, и fc.position(newPosition), чтобы установить ее, но в моем случае это не работает: похоже, она возвращает позицию выполненного предварительного заполнения буфера BufferedReader, или что-то в этом роде - кажется, что эти счетчики округлены с шагом 16K.

Должен ли я реализовать все это самостоятельно, то есть интерфейс чтения файлов, который бы:

  • позвольте мне получить / установить позицию в файле
  • операции чтения из буферного файла
  • разрешить чтение строк UTF8 (или, по крайней мере, разрешить такие операции, как «читать все до следующего \n»)

Есть ли более быстрый способ, чем реализовать все это самому? Я что-то наблюдаю?

Ответы [ 7 ]

6 голосов
/ 14 апреля 2011

Я написал этот код для чтения utf-8, используя randomaccessfiles

//File: CyclicBuffer.java
public class CyclicBuffer {
private static final int size = 3;
private FileChannel channel;
private ByteBuffer buffer = ByteBuffer.allocate(size);

public CyclicBuffer(FileChannel channel) {
    this.channel = channel;
}

private int read() throws IOException {
    return channel.read(buffer);
}

/**
 * Returns the byte read
 *
 * @return byte read -1 - end of file reached
 * @throws IOException
 */
public byte get() throws IOException {
    if (buffer.hasRemaining()) {
        return buffer.get();
    } else {
        buffer.clear();
        int eof = read();
        if (eof == -1) {
            return (byte) eof;
        }
        buffer.flip();
        return buffer.get();
    }
}
}
//File: UTFRandomFileLineReader.java


public class UTFRandomFileLineReader {
private final Charset charset = Charset.forName("utf-8");
private CyclicBuffer buffer;
private ByteBuffer temp = ByteBuffer.allocate(4096);
private boolean eof = false;

public UTFRandomFileLineReader(FileChannel channel) {
    this.buffer = new CyclicBuffer(channel);
}

public String readLine() throws IOException {
    if (eof) {
        return null;
    }
    byte x = 0;
    temp.clear();

    while ((byte) -1 != (x = (buffer.get())) &amp;&amp; x != '\n') {
        if (temp.position() == temp.capacity()) {
            temp = addCapacity(temp);
        }
        temp.put(x);
    }
    if (x == -1) {
        eof = true;
    }
    temp.flip();
    if (temp.hasRemaining()) {
        return charset.decode(temp).toString();
    } else {
        return null;
    }
}

private ByteBuffer addCapacity(ByteBuffer temp) {
    ByteBuffer t = ByteBuffer.allocate(temp.capacity() + 1024);
    temp.flip();
    t.put(temp);
    return t;
}

public static void main(String[] args) throws IOException {
    RandomAccessFile file = new RandomAccessFile("/Users/sachins/utf8.txt",
            "r");
    UTFRandomFileLineReader reader = new UTFRandomFileLineReader(file
            .getChannel());
    int i = 1;
    while (true) {
        String s = reader.readLine();
        if (s == null)
            break;
        System.out.println("\n line  " + i++);
        s = s + "\n";
        for (byte b : s.getBytes(Charset.forName("utf-8"))) {
            System.out.printf("%x", b);
        }
        System.out.printf("\n");

    }
}
}
6 голосов
/ 29 ноября 2010
import org.apache.commons.io.input.BoundedInputStream

FileInputStream file = new FileInputStream(filename);
file.skip(pos1);
BufferedReader br = new BufferedReader(
   new InputStreamReader(new BoundedInputStream(file,pos2-pos1))
);

Если вас не заботит pos2, значит, вам не нужен Apache Commons IO.

1 голос
/ 29 ноября 2010

Для @Ken Bloom Очень быстрый переход на версию Java 7.Примечание: я не думаю, что это самый эффективный способ, я все еще разбираюсь в NIO.2, Oracle начал свое руководство здесь

Также обратите внимание, что это не 'Используя новый синтаксис ARM в Java 7 (который заботится об обработке исключений для файловых ресурсов), он не работал в последней сборке openJDK, которая у меня есть.Но если люди хотят увидеть синтаксис, дайте мне знать.

/* 
 * Paths uses the default file system, note no exception thrown at this stage if 
 * file is missing
 */
Path file = Paths.get("C:/Projects/timesheet.txt");
ByteBuffer readBuffer = ByteBuffer.allocate(readBufferSize);
FileChannel fc = null;
try
{
    /*
     * newByteChannel is a SeekableByteChannel - this is the fun new construct that 
     * supports asynch file based I/O, e.g. If you declared an AsynchronousFileChannel 
     * you could read and write to that channel simultaneously with multiple threads.
     */
    fc = (FileChannel)file.newByteChannel(StandardOpenOption.READ);
    fc.position(startPosition);
    while (fc.read(readBuffer) != -1)
    {
        readBuffer.rewind();
        System.out.println(Charset.forName(encoding).decode(readBuffer));
        readBuffer.flip();
    }
}
0 голосов
/ 13 июля 2014

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

После долгих обходов Javadocs и Stack Overflow, я думаю, я нашел простое решение.

После поиска подходящего места в вашем RandomAccessFile, который я здесь называю raFile, сделайте следующее:

FileDescriptor fd = raFile.getFD();
FileReader     fr = new FileReader(fd);
BufferedReader br = new BufferedReader(fr);

Тогда вы сможете позвонить br.readLine() на ваше усмотрение, чтобудет намного быстрее, чем вызов raFile.readLine().

Единственное, в чем я не уверен, так это в том, правильно ли обрабатываются строки UTF8.

0 голосов
/ 29 ноября 2010

Я думаю, что путаница вызвана кодировкой UTF-8 и возможностью использования двухбайтовых символов.

UTF8 не указывает, сколько байтов в одном символе. Я предполагаю из вашего поста, что вы используете однобайтовые символы. Например, 412 байтов означали бы 411 символов. Но если в строке используются двухбайтовые символы, вы получите 206 символов.

Оригинальный пакет java.io не справился с этой многобайтовой путаницей. Таким образом, они добавили больше классов для работы со строками. Пакет смешивает два разных типа файловых обработчиков (и они могут сбивать с толку, пока не определится номенклатура). Классы stream обеспечивают прямой ввод / вывод данных без какого-либо преобразования. Классы reader преобразуют файлы в строки с полной поддержкой многобайтовых символов. Это может помочь прояснить часть проблемы.

Поскольку вы заявляете, что используете символы UTF-8, вам нужны классы считывателей. В этом случае я предлагаю FileReader. Метод skip () в FileReader позволяет пропустить символы X, а затем начать читать текст. В качестве альтернативы я предпочитаю перегруженный метод read (), поскольку он позволяет вам захватить весь текст за один раз.

Если вы предполагаете, что ваши "байты" являются отдельными символами, попробуйте что-то вроде этого:

FileReader fr = new FileReader( new File("x.txt") );
char[] buffer = new char[ pos2 - pos ];
fr.read( buffer, pos, buffer.length );
...
0 голосов
/ 29 ноября 2010

Начните с RandomAccessFile и используйте read или readFully, чтобы получить байтовый массив от pos1 до pos2.Допустим, мы сохранили данные, прочитанные в переменной с именем rawBytes.

. Затем создайте свой BufferedReader, используя

new BufferedReader(new InputStreamReader(new ByteArrayInputStream(rawBytes)))

Затем вы можете вызвать readLine на BufferedReader.

Предостережение: это, вероятно, использует больше памяти, чем если бы вы могли BufferedReader искать в нужном месте, потому что он предварительно загружает все в память.

0 голосов
/ 29 ноября 2010

API Java IO очень гибок. К сожалению, иногда гибкость делает его многословным. Основная идея здесь заключается в том, что есть много потоков, писателей и читателей, которые реализуют скороговорку. Например, BufferedInputStream переносит любой другой InputStream. То же самое касается выходных потоков.

Разница между потоками и программами чтения / записи заключается в том, что потоки работают с байтами, а программы чтения / записи - с символами.

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

    InputStream in = new FileInputStream("/usr/home/me/myfile.txt");
    if (in.markSupported()) {
        in.skip(1024);
        in.read();
    }

Это не так сложно, как ты боишься.

Каналы это что-то другое. Это часть так называемого "нового ввода-вывода" или nio. Новый IO не заблокирован - это его главное преимущество. Вы можете найти в интернете любой «учебник по nio java» и прочитать о нем. Но это сложнее, чем обычный ввод-вывод и не требуется для большинства приложений.

...