Как я могу реализовать OutputStream, который я могу перемотать?
-
05-07-2019 - |
Вопрос
После записи некоторого обработанного содержимого в выходной поток мне нужно вернуться к началу потока и записать некоторые метаданные содержимого. Данные, которые я пишу, очень большие, до 4 ГБ, и могут быть записаны либо непосредственно в файл, либо в буфер в памяти, в зависимости от различных факторов окружающей среды.
Как я могу реализовать OutputStream, который позволяет мне записывать заголовки после завершения записи содержимого?
Решение
Вот поток вывода файла произвольного доступа. Р>
Обратите внимание, что если вы используете его для большого количества потоковых выходных данных, вы можете временно обернуть его в BufferedOutputStream, чтобы избежать большого количества мелких записей (просто обязательно очистите его перед удалением оболочки или непосредственным использованием базового потока). р>
import java.io.*;
/**
* A positionable file output stream.
* <p>
* Threading Design : [x] Single Threaded [ ] Threadsafe [ ] Immutable [ ] Isolated
*/
public class RandomFileOutputStream
extends OutputStream
{
// *****************************************************************************
// INSTANCE PROPERTIES
// *****************************************************************************
protected RandomAccessFile randomFile; // the random file to write to
protected boolean sync; // whether to synchronize every write
// *****************************************************************************
// INSTANCE CONSTRUCTION/INITIALIZATON/FINALIZATION, OPEN/CLOSE
// *****************************************************************************
public RandomFileOutputStream(String fnm) throws IOException {
this(fnm,false);
}
public RandomFileOutputStream(String fnm, boolean syn) throws IOException {
this(new File(fnm),syn);
}
public RandomFileOutputStream(File fil) throws IOException {
this(fil,false);
}
public RandomFileOutputStream(File fil, boolean syn) throws IOException {
super();
File par; // parent file
fil=fil.getAbsoluteFile();
if((par=fil.getParentFile())!=null) { IoUtil.createDir(par); }
randomFile=new RandomAccessFile(fil,"rw");
sync=syn;
}
// *****************************************************************************
// INSTANCE METHODS - OUTPUT STREAM IMPLEMENTATION
// *****************************************************************************
public void write(int val) throws IOException {
randomFile.write(val);
if(sync) { randomFile.getFD().sync(); }
}
public void write(byte[] val) throws IOException {
randomFile.write(val);
if(sync) { randomFile.getFD().sync(); }
}
public void write(byte[] val, int off, int len) throws IOException {
randomFile.write(val,off,len);
if(sync) { randomFile.getFD().sync(); }
}
public void flush() throws IOException {
if(sync) { randomFile.getFD().sync(); }
}
public void close() throws IOException {
randomFile.close();
}
// *****************************************************************************
// INSTANCE METHODS - RANDOM ACCESS EXTENSIONS
// *****************************************************************************
public long getFilePointer() throws IOException {
return randomFile.getFilePointer();
}
public void setFilePointer(long pos) throws IOException {
randomFile.seek(pos);
}
public long getFileSize() throws IOException {
return randomFile.length();
}
public void setFileSize(long len) throws IOException {
randomFile.setLength(len);
}
public FileDescriptor getFD() throws IOException {
return randomFile.getFD();
}
} // END PUBLIC CLASS
Другие советы
Если вы знаете размер заголовка, вы можете сначала написать пустой заголовок, а затем вернуться, чтобы исправить его, добавив RandomAccessFile
в конце. Если вы не знаете размер заголовка, то у вас есть основополагающее свойство: файловые системы обычно не позволяют вставлять данные. Поэтому вам нужно записать во временный файл, а затем записать реальный файл.
У Lucene, похоже, есть реализация; и API выглядят хорошо.
getFilePointer()
void seek(long pos)
Я предполагаю, что они обертывают RandomAccessFile а> р>
Одним из способов было бы сначала записать начальное содержимое в буфер памяти, затем заголовки в «реальный» выходной поток с последующей очисткой буферизованного содержимого, а затем просто записать в небуферизованный поток. Похоже, начальный сегмент не будет таким длинным, чтобы сделать буферизацию разумной. Что касается его реализации, вы можете использовать ByteArrayOutputStream для буферизации, а затем сделать так, чтобы ваш класс OutputStream принял " real " выходной поток в качестве аргумента; и просто переключайтесь между двумя при необходимости. Вам может потребоваться расширить API-интерфейс OutputStream, чтобы разрешить определение метаданных для записи, поскольку эти триггеры переключаются из буферизованного режима.
Как уже упоминалось в другом ответе, RandomAccessFile также будет работать, хотя и не будет реализовывать OutputStream.