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


person Hamza Yerlikaya    schedule 28.07.2009    source источник
comment
Получаете ли вы что-нибудь вообще из потока или он генерирует исключение в первый раз в цикле?   -  person skaffman    schedule 28.07.2009
comment
он читает первый объект, который я сохранил, тогда я получаю исключение.   -  person Hamza Yerlikaya    schedule 28.07.2009
comment
В приведенном выше коде я вижу только один объект, записанный в файл, поэтому только один будет прочитан, верно?   -  person aperkins    schedule 28.07.2009
comment
первый фрагмент находится в функции, он вызывается несколько раз, когда задание завершено. но я могу прочитать только первое, что я написал.   -  person Hamza Yerlikaya    schedule 28.07.2009
comment
Ваш код здесь записывает только 1 объект в поток, есть ли что-то не показанное, что записывает в этот поток более 1 объекта?   -  person nos    schedule 28.07.2009


Ответы (6)


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

person Andreas Dolk    schedule 28.07.2009
comment
Умная! Я думаю, что проблема заключается в заголовке потока, и в этом случае все должно работать прекрасно, но проверяли ли вы его, чтобы быть уверенным? - person Michael Myers; 28.07.2009
comment
Спасибо за ваши комментарии :-) Да, я разместил проверенный код (просто забыл упомянуть об этом в ответе) - person Andreas Dolk; 28.07.2009
comment
+1, спасибо. Предупреждение: writeStreamHeader() вызывается конструктором ObjectOutputStream. Это затрудняет обработку обоих случаев одним классом. Вы можете создать простой фабричный метод, который возвращает fileExists? новый AppendingObjectOutputStream(out) : новый ObjectOutputStream(out); - person Andy Thomas; 15.09.2011
comment
Вы действительно пробовали это? Не только писать, но и читать стрим потом? Это не работает правильно, потому что есть другое внутреннее состояние, которое не восстанавливается должным образом, если вы просто добавляете к OOS. - person jrudolph; 02.08.2012
comment
Да, конечно, я тестировал его в 2009 году с Sun Java. Думаю, это был JDK5. Не знаю, изменились ли они внутренности с тех пор. - person Andreas Dolk; 02.08.2012
comment
Обратите внимание, что это только для добавления к существующему файлу! новый файл должен быть создан с обычным потоком ObjectOutputStream, потому что нам нужен заголовок в начале файла. - person Andreas Dolk; 02.08.2012
comment
очевидно, есть проблема с этим решением, если вы можете достаточно повозиться с ним. Посмотрите на этот вопрос: stackoverflow.com/ вопросы/12279245/ - person sasidhar; 16.09.2012

Как API сообщает, что конструктор ObjectOutputStream записывает заголовок потока сериализации в базовый поток. И ожидается, что этот заголовок будет только один раз, в начале файла. Так зовет

new ObjectOutputStream(fos);

несколько раз в FileOutputStream, который ссылается на один и тот же файл, несколько раз запишет заголовок и повредит файл.

person Tadeusz Kopec    schedule 28.07.2009

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

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

person Michael Myers    schedule 28.07.2009

Самый простой способ избежать этой проблемы — держать OutputStream открытым при записи данных, а не закрывать его после каждого объекта. Вызов reset() может быть целесообразным, чтобы избежать утечки памяти.

Альтернативой может быть чтение файла как серии последовательных потоков ObjectInputStream. Но для этого требуется, чтобы вы подсчитывали, сколько байтов вы читаете (это можно реализовать с помощью FilterInputStream), затем закрываете InputStream, открываете его снова, пропускаете это количество байтов и только затем заключаете его в ObjectInputStream().

person Michael Borgwardt    schedule 28.07.2009

Я расширил принятое решение, чтобы создать класс, который можно использовать как для добавления, так и для создания нового файла.

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();
        }

    }

}
person Pratanu Mandal    schedule 07.08.2019

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

person user4147874    schedule 09.11.2014