Как я могу добавить существующий java.io.ObjectStream?

Пока я получаю java.io.StreamCorruptedException, когда пытаюсь добавить объект. Я искал Интернет, чтобы преодолеть это. Ответ, который я нашел до сих пор, это невозможно. Способом решения этой проблемы является запись объектов в список, а затем запись списка в файл.

Но я должен перезаписывать этот файл каждый раз, когда добавляю новые объекты. Кажется, это не оптимальное решение в овертайме.

Есть ли способ добавить объекты к существующему потоку объектов?

Ответ 1

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

class NoHeaderObjectOutputStream extends ObjectOutputStream {
  public NoHeaderObjectOutputStream(OutputStream os) {
    super(os);
  }
  protected void writeStreamHeader() {}
}

Затем просто используйте стандартный ObjectInputStream для чтения всего файла.

Ответ 2

Лучшая статья, которую я нашел на эту тему: http://codify.flansite.com/2009/11/java-serialization-appending-objects-to-an-existing-file/

"Решение", которое переопределяет ObjectOutputStream, просто неверно. Я только что закончил расследование ошибки, которая была вызвана этим (растрачивая два драгоценных дня). Мало того, что он иногда искажал сериализованный файл, но даже умел читать, не бросая исключений и в конце предоставляя данные мусора (поля смешивания). Для тех, кто не верит, я прикрепляю код, который раскрывает проблему:

import java.io.*;
import java.util.HashMap;
import java.util.Map;

public class Main {

    public static void main(String[] args) throws Exception {

        File storageFile = new File("test");
        storageFile.delete();

        write(storageFile, getO1());
        write(storageFile, getO2());
        write(storageFile, getO2());

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(storageFile));
        read(ois, getO1());
        read(ois, getO2());
        read(ois, getO2());
    }

    private static void write(File storageFile, Map<String, String> o) throws IOException {
        ObjectOutputStream oos = getOOS(storageFile);
        oos.writeObject(o);
        oos.close();
    }

    private static void read(ObjectInputStream ois, Map<String, String> expected) throws ClassNotFoundException, IOException {
        Object actual = ois.readObject();
        assertEquals(expected, actual);
    }

    private static void assertEquals(Object o1, Object o2) {
        if (!o1.equals(o2)) {
            throw new AssertionError("\n expected: " + o1 + "\n actual:   " + o2);
        }
    }

    private static Map<String, String> getO1() {
        Map<String, String> nvps = new HashMap<String, String>();
        nvps.put("timestamp", "1326382770000");
        nvps.put("length", "246");
        return nvps;
    }

    private static Map<String, String> getO2() {
        Map<String, String> nvps = new HashMap<String, String>();
        nvps.put("timestamp", "0");
        nvps.put("length", "0");
        return nvps;
    }

    private static ObjectOutputStream getOOS(File storageFile) throws IOException {
        if (storageFile.exists()) {
            // this is a workaround so that we can append objects to an existing file
            return new AppendableObjectOutputStream(new FileOutputStream(storageFile, true));
        } else {
            return new ObjectOutputStream(new FileOutputStream(storageFile));
        }
    }

    private static class AppendableObjectOutputStream extends ObjectOutputStream {

        public AppendableObjectOutputStream(OutputStream out) throws IOException {
            super(out);
        }

        @Override
        protected void writeStreamHeader() throws IOException {
            // do not write a header
        }
    }
}

Как указано в этой статье, вы можете использовать одно из следующих решений:

Решение №1: Fake несколько файлов в одном потоке

...

Напишите свою "транзакцию" в ByteArrayOutputStream, затем напишите длину и содержимое этого ByteArrayOutputStream в файл через DataOutputStream.

Решение №2: Повторно открыть и пропустить

Другое решение включает сохранение позиции файла, используя:

long pos = fis.getChannel().position();

закрытие файла, повторное открытие файла и переход к этой позиции перед чтением следующей транзакции.

Ответ 3

Огромное спасибо Джорджу Хэтегану за проблему разоблачения кода. Я тоже некоторое время изучал его. Затем он ударил меня. Если вы используете подклассифицированный ObjectOutputStream с переопределением метода writeStreamHeader() для записи данных, вы должны использовать параллельный подкласс ObjectInputStream с переопределением метода readStreamHeader() для чтения данных. Конечно, мы можем zig-zag между различными реализациями объектов записи и чтения, но пока мы используем соответствующие пары подклассов в процессе записи/чтения, мы будем (надеюсь) в порядке. Том.

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;

public class SerializationDemo {

    public static void main(String[] args) throws Exception {
        File storageFile = new File("test.ser");
        storageFile.delete();
        write(storageFile, getO1());
        write(storageFile, getO2());
        write(storageFile, getO2());
        FileInputStream fis = new FileInputStream(storageFile);
        read(fis, getO1());
        read(fis, getO2());
        read(fis, getO2());
        fis.close();
    }

    private static void write(File storageFile, Map<String, String> o)
                    throws IOException {
        ObjectOutputStream oos = getOOS(storageFile);
        oos.writeObject(o);
        oos.flush();
        oos.close();
    }

    private static void read(FileInputStream fis, Map<String, String> expected)
                    throws ClassNotFoundException, IOException {
        Object actual = getOIS(fis).readObject();
        assertEquals(expected, actual);
        System.out.println("read serialized " + actual);
    }

    private static void assertEquals(Object o1, Object o2) {
        if (!o1.equals(o2)) {
            throw new AssertionError("\n expected: " + o1 + "\n actual:   " + o2);
        }
    }

    private static Map<String, String> getO1() {
        Map<String, String> nvps = new HashMap<String, String>();
        nvps.put("timestamp", "1326382770000");
        nvps.put("length", "246");
        return nvps;
    }

    private static Map<String, String> getO2() {
        Map<String, String> nvps = new HashMap<String, String>();
        nvps.put("timestamp", "0");
        nvps.put("length", "0");
        return nvps;
    }

    private static ObjectOutputStream getOOS(File storageFile)
                    throws IOException {
        if (storageFile.exists()) {
            // this is a workaround so that we can append objects to an existing file
            return new AppendableObjectOutputStream(new FileOutputStream(storageFile, true));
        } else {
            return new ObjectOutputStream(new FileOutputStream(storageFile));
        }
    }

    private static ObjectInputStream getOIS(FileInputStream fis)
                    throws IOException {
        long pos = fis.getChannel().position();
        return pos == 0 ? new ObjectInputStream(fis) : 
            new AppendableObjectInputStream(fis);
    }

    private static class AppendableObjectOutputStream extends
                    ObjectOutputStream {

        public AppendableObjectOutputStream(OutputStream out)
                        throws IOException {
            super(out);
        }

        @Override
        protected void writeStreamHeader() throws IOException {
            // do not write a header
        }
    }

    private static class AppendableObjectInputStream extends ObjectInputStream {

        public AppendableObjectInputStream(InputStream in) throws IOException {
            super(in);
        }

        @Override
        protected void readStreamHeader() throws IOException {
            // do not read a header
        }
    }
}

Ответ 4

Вам нужно будет создать новый ObjectInputStream для соответствия каждому ObjectOutputStream. Я не знаю, как передать состояние из полного ObjectInputStream в ObjectOutputStream (без полной повторной реализации, которая в любом случае немного сложна в чистой Java).