Come posso implementare un OutputStream che posso riavvolgere?
-
05-07-2019 - |
Domanda
Dopo aver scritto del contenuto elaborato su un flusso di output, devo rivisitare l'inizio del flusso e scrivere alcuni metadati del contenuto. I dati che sto scrivendo sono molto grandi, fino a 4 GB, e possono essere scritti direttamente in un file o in un buffer in memoria, a seconda di vari fattori ambientali.
Come posso implementare un OutputStream che mi permetta di scrivere le intestazioni dopo aver completato la scrittura del contenuto?
Soluzione
Ecco un flusso di output del file ad accesso casuale.
Nota che se lo usi per una grande quantità di output in streaming puoi temporaneamente avvolgerlo in un BufferedOutputStream per evitare molte piccole scritture (assicurati solo di scaricarlo prima di scartare il wrapper o usare direttamente il flusso sottostante).
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
Altri suggerimenti
Se conosci la dimensione dell'intestazione, puoi inizialmente scrivere un'intestazione vuota, quindi tornare indietro per risolverlo con RandomAccessFile
alla fine. Se non si conosce la dimensione dell'intestazione, è fondamentale che i filesystem generalmente non consentano di inserire dati. Quindi è necessario scrivere su un file temporaneo e quindi scrivere il file reale.
Lucene sembra avere un'implementazione; e l'API sembra ok.
getFilePointer()
void seek(long pos)
Credo che racchiudano un RandomAccessFile
Un modo sarebbe quello di scrivere prima i contenuti iniziali su un buffer di memoria, quindi le intestazioni in un flusso di output "reale", seguito da uno svuotamento dei contenuti bufferizzati e da lì semplicemente scrivere nel flusso non bufferizzato. Sembra che il segmento iniziale non sia così lungo, per rendere ragionevole il buffering. Per quanto riguarda l'implementazione, puoi usare ByteArrayOutputStream per il buffering e quindi far sì che la tua classe OutputStream prenda " real " flusso di output come argomento; e basta passare tra i due, se necessario. Potrebbe essere necessario estendere l'API OutputStream per consentire la definizione di cosa devono scrivere i metadati, in quanto i trigger passano dalla modalità buffer.
Come menzionato dall'altra risposta, anche RandomAccessFile funzionerebbe, sebbene non implementasse OutputStream.