Вопрос

Нельзя ли добавить к 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, это невозможно.Мне было интересно, знает ли кто-нибудь способ?

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

Решение

Вот трюк:подкласс 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 который относится к одному и тому же файлу, запишет заголовок несколько раз и повредит файл.

Из-за точного формата сериализованного файла добавление действительно приведет к его повреждению.Вы должны записать все объекты в файл как часть одного и того же потока, иначе произойдет сбой при чтении метаданных потока во время ожидания объекта.

Вы могли бы прочитать Спецификация сериализации для более подробной информации или (проще) прочтите эта тема где Роуди Грин говорит в основном то же, что я только что сказал.

Самый простой способ избежать этой проблемы — оставлять выходной поток открытым при записи данных, а не закрывать его после каждого объекта.Вызов 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();
        }

    }

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