Java: чтение строк из файла произвольного доступа с буферизованным входом

StackOverflow https://stackoverflow.com/questions/4305094

Вопрос

У меня никогда не было близких опыта с 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
}

Это быстро поступает в ад с apis java io;) на самом деле я вижу два способа читать строки (заканчивая \n) из обычных локальных файлов:

  • RandomAccessFile. имеет getFilePointer() и seek(long pos), но это readline () Читает строки не-utf8 (и даже не байтовые массивы), но очень странные строки с разбитым кодировкой, и она не имеет буферизации (что, вероятно, означает, что каждый read*() Звонок будет переведен в одну несущественную ОС read() => Довольно медленно).
  • Буферреджер имеет большое количество 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) Чтобы установить один, но, похоже, в моем случае не работает: Похоже, он возвращает положение буфера предварительного заполнения, выполненного буферреджером, или что-то в этом роде - эти счетчики, кажется, округляются в 16К.

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

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

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

Это было полезно?

Решение

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.

Другие советы

Я написал этот код, чтобы прочитать 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");

    }
}
}

Для @kek 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();
    }
}

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

Затем создайте свой BufferedReader с использованием

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

Тогда вы можете позвонить readLine на BufferedReader.

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

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

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

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

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

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

FileReader fr = new FileReader( new File("x.txt") );
char[] buffer = new char[ pos2 - pos ];
fr.read( buffer, pos, buffer.length );
...

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

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

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top