2009-07-07 1 views
33

Мне нужно написать функцию, которая принимает какую-то информацию о потоке ввода (например, InputStream или FileChannel), чтобы прочитать большой файл за два прохода: один раз чтобы прекомпомировать некоторые возможности, а во-вторых, выполнить «настоящую» работу. Я не хочу, чтобы весь файл загружался в память сразу (если только он не мал).java file input с возможностью перемотки назад()/reset()

Есть ли соответствующий Java-класс, который предоставляет эту возможность? FileInputStream сам по себе не поддерживает mark()/reset(). BufferedInputStream, я думаю, но я не знаю, нужно ли хранить весь файл для этого.

C так просто, вы просто используете fseek(), ftell() и перематываете(). :-(

+3

Джейсон, пожалуйста, снимите принять мой ответ и принять [это.] (Http://stackoverflow.com/a/18665678/3474) Это хорошо, потому что это обеспечивает эффективную реализацию стандартного стандартного API-интерфейса InputStream; любой потребитель 'InputStream' может использовать его без загрузки всего файла. – erickson

ответ

22

Я думаю, что ответы, ссылающиеся на FileChannel, находятся на отметке.

Вот пример реализации входного потока, который инкапсулирует эту функциональность. Он использует делегирование, поэтому он не является истинным FileInputStream, но это InputStream, которого обычно достаточно. Аналогичным образом можно расширить FileInputStream, если это необходимо.

Не тестировался, используйте на свой страх и риск :)

public class MarkableFileInputStream extends FilterInputStream { 
    private FileChannel myFileChannel; 
    private long mark = -1; 

    public MarkableFileInputStream(FileInputStream fis) { 
     super(fis); 
     myFileChannel = fis.getChannel(); 
    } 

    @Override 
    public boolean markSupported() { 
     return true; 
    } 

    @Override 
    public synchronized void mark(int readlimit) { 
     try { 
      mark = myFileChannel.position(); 
     } catch (IOException ex) { 
      mark = -1; 
     } 
    } 

    @Override 
    public synchronized void reset() throws IOException { 
     if (mark == -1) { 
      throw new IOException("not marked"); 
     } 
     myFileChannel.position(mark); 
    } 
} 
+1

Это, безусловно, лучшее решение. BufferedInput заставляет большие части или, возможно, ВСЕ файл быть двойным буфером. Это огромная трата. И RandomAccessFile не наследует от I nputStream, поэтому не может быть заменой для тех, где вы уже используете потоки. Однако этот маленький класс должен быть чрезвычайно быстрым и эффективным для памяти. – Adam

+0

Это отлично подойдет для меня. Я добавил «mark (0);» к конструктору, потому что при первом вызове 'reset()' я получал «не отмеченную» ошибку и, по крайней мере, в моем случае, имеет смысл для позиции сброса по умолчанию для 0. 0. –

+2

Это решение отлично работает с одним небольшим изменением. Я бы удалил «mark = -1» внутри метода сброса. В javadocs для сброса не указывается, что он должен сбросить отметку, а только позицию. Это позволяет вызывать один раз один раз, а затем перезапускаться для вызова несколько раз, например, при выполнении нескольких попыток. –

2

Заканчивать java.io.RandomAccessFile

+0

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

+0

Слишком плохо RandomAccessFile не реализует InputStream с его методами меток()/reset(). > :( –

+0

Вы можете легко свернуть (если не так элегантно), см. Http://www.coderanch.com/t/277378/Streams/java/InputStream-RandomAccessFile-best-way для примера –

7

java.nio.channels.FileChannel имеет метод position(long) для сброса позиции до нуля, как FSEEK() в С.

22

BufferedInputStream поддерживает mark буферизации содержимого в памяти. Он лучше всего подходит для относительно небольших перспектив предсказуемого размера.

Вместо этого RandomAccessFile может использоваться непосредственно или может служить основой для бетона InputStream, дополненного методом rewind().

В качестве альтернативы для каждого прохода может быть открыт новый FileInputStream.

+1

Я перехожу к этому ответу, потому что мне нужно использовать интерфейс, который я могу использовать между обычными файлами и в -memory buffers. Grrrrr. Я пишу свой собственный интерфейс RewindableStream + классов реализации, один из которых обертывает RandomAccessFile. –

2

PushbackInputStream также будет работать, до тех пор, как вы знаете, сколько символов вы хотите, чтобы иметь возможность перемотать

+0

Неверно. 'PushbackInputStream # непрочитанный (int b)' не перематывает байты 'b', но я нажимаю' b 'на вершине потока. –

2

BufferedInputStream имеет mark(readlimit) и reset(). readlimit должно быть больше filesize, чтобы сделать отметку действительной. file.length()+1 в порядке. Это означает, что знак действителен до тех пор, пока не будут прочитаны readlimit байтов, таким образом вы можете вернуться на reset().

2

То, что вы хотите, это RandomAccessFileInputStream - осуществляет интерфейс InputStream с отметкой/сбросом, иногда ищет на основе RandomAccessFiles. Существуют некоторые реализации, которые могут делать то, что вам нужно.

Один пример в комплекте с источниками находится в http://www.fuin.org/utils4j/index.html, но вы найдете много других в Интернете, и его достаточно легко закодировать, если он не подходит.

19

Если у вас есть связанный FileChannel с FileInputStream, вы можете использовать метод position, чтобы указать указатель файла в любом месте файла.

FileInputStream fis = new FileInputStream("/etc/hosts"); 
FileChannel  fc = fis.getChannel(); 


fc.position(100);// set the file pointer to byte position 100;