Присоединение к ObjectOutputStream
Разве нельзя добавить к ObjectOutputStream
?
Я пытаюсь добавить в список объектов. Следующий фрагмент - это функция, которая вызывается при завершении работы.
FileOutputStream fos = new FileOutputStream
(preferences.getAppDataLocation() + "history" , true);
ObjectOutputStream out = new ObjectOutputStream(fos);
out.writeObject( new Stuff(stuff) );
out.close();
Но когда я пытаюсь прочитать это, я получаю только первое в файле. Тогда я получаю java.io.StreamCorruptedException
,
Читать я использую
FileInputStream fis = new FileInputStream
( preferences.getAppDataLocation() + "history");
ObjectInputStream in = new ObjectInputStream(fis);
try{
while(true)
history.add((Stuff) in.readObject());
}catch( Exception e ) {
System.out.println( e.toString() );
}
Я не знаю, сколько объектов будет присутствовать, поэтому я читаю, пока нет никаких исключений. Из того, что Google говорит, это невозможно. Мне было интересно, если кто-нибудь знает способ?
5 ответов
Вот хитрость: подкласс ObjectOutputStream
и переопределить writeStreamHeader
метод:
public class AppendingObjectOutputStream extends ObjectOutputStream {
public AppendingObjectOutputStream(OutputStream out) throws IOException {
super(out);
}
@Override
protected void writeStreamHeader() throws IOException {
// do not write a header, but reset:
// this line added after another question
// showed a problem with the original
reset();
}
}
Чтобы использовать его, просто проверьте, существует файл истории или нет, и создайте экземпляр этого добавляемого потока (в случае, если файл существует = мы добавляем = нам не нужен заголовок) или исходного потока (в случае, если файл не существует = нам нужен заголовок).
редактировать
Я не был счастлив с первым наименованием класса. Этот лучше: он описывает "для чего это", а не "как это делается"
редактировать
Еще раз изменили имя, чтобы уточнить, что этот поток предназначен только для добавления в существующий файл. Его нельзя использовать для создания нового файла с данными объекта.
редактировать
Добавлен звонок в reset()
после того, как этот вопрос показал, что оригинальная версия, которая просто переопределена writeStreamHeader
быть неактивным может при некоторых условиях создать поток, который не может быть прочитан.
Как говорит API, ObjectOutputStream
Конструктор записывает заголовок потока сериализации в основной поток. И этот заголовок, как ожидается, будет только один раз, в начале файла. Так зовет
new ObjectOutputStream(fos);
несколько раз на FileOutputStream
который ссылается на один и тот же файл, напишет заголовок несколько раз и повредит файл.
Из-за точного формата сериализованного файла добавление действительно повредит его. Вы должны записать все объекты в файл как часть одного и того же потока, иначе произойдет сбой при чтении метаданных потока при ожидании объекта.
Вы можете прочитать Спецификацию сериализации для получения более подробной информации или (проще) прочитать эту ветку, где Роди Грин говорит в основном то, что я только что сказал.
Самый простой способ избежать этой проблемы - оставить OutputStream открытым при записи данных, а не закрывать его после каждого объекта. призвание reset()
может быть целесообразно избежать утечки памяти.
Альтернативой может быть чтение файла как серии последовательных ObjectInputStreams. Но это требует от вас подсчитать, сколько байтов вы прочитали (это может быть реализовано с помощью FilterInputStream), затем закройте InputStream, снова откройте его, пропустите столько байтов и только затем оберните его в ObjectInputStream().
Я расширил принятое решение, чтобы создать класс, который можно использовать как для добавления, так и для создания нового файла.
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
public class AppendableObjectOutputStream extends ObjectOutputStream {
private boolean append;
private boolean initialized;
private DataOutputStream dout;
protected AppendableObjectOutputStream(boolean append) throws IOException, SecurityException {
super();
this.append = append;
this.initialized = true;
}
public AppendableObjectOutputStream(OutputStream out, boolean append) throws IOException {
super(out);
this.append = append;
this.initialized = true;
this.dout = new DataOutputStream(out);
this.writeStreamHeader();
}
@Override
protected void writeStreamHeader() throws IOException {
if (!this.initialized || this.append) return;
if (dout != null) {
dout.writeShort(STREAM_MAGIC);
dout.writeShort(STREAM_VERSION);
}
}
}
Этот класс можно использовать как прямую расширенную замену ObjectOutputStream. Мы можем использовать класс следующим образом:
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
public class ObjectWriter {
public static void main(String[] args) {
File file = new File("file.dat");
boolean append = file.exists(); // if file exists then append, otherwise create new
try (
FileOutputStream fout = new FileOutputStream(file, append);
AppendableObjectOutputStream oout = new AppendableObjectOutputStream(fout, append);
) {
oout.writeObject(...); // replace "..." with serializable object to be written
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Как насчет того, чтобы каждый раз добавлять объект, читать и копировать все текущие данные в файл, а затем перезаписывать все вместе в файл.