java.io.StreamCorruptedException: invalid type code: AC

Problem Description: when ObjectOutputStream serializes and writes objects to a file by appending, it only wants to append objects to the end of the file each time instead of overwriting them. You can use fileoutputstream (file name, true) as a constructor parameter and write objects with the writeObject() method.

ObjectInputStream ois = new ObjectInputStream(new FileInputStream("MatchedTransports.txt", true));

When serializing and writing object information for the first time and deserializing and reading object reading, no exception will be thrown. When the file is opened after the stream is closed, the object is written and appended, and the object is read again through deserialization, the exception information of java.io.StreamCorruptedException: invalid type code: AC will be thrown.

Cause analysis: every time a new ObjectOutputStream object is created, the writeStreamHeader() method is called to write the header information of the stream in its constructor, and then the object information is written. As can be seen from the source code of objectoutputstream constructor:

 /**
  * Creates an ObjectInputStream that reads from the specified InputStream.
  * A serialization stream header is read from the stream and verified.
  * This constructor will block until the corresponding ObjectOutputStream
  * has written and flushed the header.
  *
  * <p>If a security manager is installed, this constructor will check for
  * the "enableSubclassImplementation" SerializablePermission when invoked
  * directly or indirectly by the constructor of a subclass which overrides
  * the ObjectInputStream.readFields or ObjectInputStream.readUnshared
  * methods.
  *
  * @param   in input stream to read from
  * @throws  StreamCorruptedException if the stream header is incorrect
  * @throws  IOException if an I/O error occurs while reading stream header
  * @throws  SecurityException if untrusted subclass illegally overrides
  *          security-sensitive methods
  * @throws  NullPointerException if <code>in</code> is <code>null</code>
  * @see     ObjectInputStream#ObjectInputStream()
  * @see     ObjectInputStream#readFields()
  * @see     ObjectOutputStream#ObjectOutputStream(OutputStream)
  */
 public ObjectInputStream(InputStream in) throws IOException {
     verifySubclass();
     bin = new ObjectInputStream.BlockDataInputStream(in);
     handles = new ObjectInputStream.HandleTable(10);
     vlist = new ObjectInputStream.ValidationList();
     serialFilter = ObjectInputFilter.Config.getSerialFilter();
     enableOverride = false;
     readStreamHeader();
     bin.setBlockDataMode(true);
 }

The readStreamHeader() method writes two short type stream information before writing object information.

/**
 * The writeStreamHeader method is provided so subclasses can append or
 * prepend their own header to the stream.  It writes the magic number and
 * version to the stream.
 *
 * @throws  IOException if I/O errors occur while writing to the underlying
 *          stream
 */
protected void writeStreamHeader() throws IOException {
    /**
     * Magic number that is written to the stream header.
     * final static short STREAM_MAGIC = (short)0xaced;
     */    
    bout.writeShort(STREAM_MAGIC);
   /**
     * Version number that is written to the stream header.
     * final static short STREAM_VERSION = 5;
     */ 
    bout.writeShort(STREAM_VERSION);  
}

When we create the ObjectInputStream object input stream, we can only read the first added StreamHeader information, so an exception will be thrown when we read the subsequent additional StreamHeader information again.

/**
 * Creates an ObjectInputStream that reads from the specified InputStream.
 * A serialization stream header is read from the stream and verified.
 * This constructor will block until the corresponding ObjectOutputStream
 * has written and flushed the header.
 *
 * <p>If a security manager is installed, this constructor will check for
 * the "enableSubclassImplementation" SerializablePermission when invoked
 * directly or indirectly by the constructor of a subclass which overrides
 * the ObjectInputStream.readFields or ObjectInputStream.readUnshared
 * methods.
 *
 * @param   in input stream to read from
 * @throws  StreamCorruptedException if the stream header is incorrect
 * @throws  IOException if an I/O error occurs while reading stream header
 * @throws  SecurityException if untrusted subclass illegally overrides
 *          security-sensitive methods
 * @throws  NullPointerException if <code>in</code> is <code>null</code>
 * @see     ObjectInputStream#ObjectInputStream()
 * @see     ObjectInputStream#readFields()
 * @see     ObjectOutputStream#ObjectOutputStream(OutputStream)
 */
public ObjectInputStream(InputStream in) throws IOException {
    verifySubclass();
    bin = new ObjectInputStream.BlockDataInputStream(in);
    handles = new ObjectInputStream.HandleTable(10);
    vlist = new ObjectInputStream.ValidationList();
    serialFilter = ObjectInputFilter.Config.getSerialFilter();
    enableOverride = false;
    readStreamHeader();
    bin.setBlockDataMode(true);
}

/**
 * The readStreamHeader method is provided to allow subclasses to read and
 * verify their own stream headers. It reads and verifies the magic number
 * and version number.
 *
 * @throws  IOException if there are I/O errors while reading from the
 *          underlying <code>InputStream</code>
 * @throws  StreamCorruptedException if control information in the stream
 *          is inconsistent
 */
protected void readStreamHeader() throws IOException, StreamCorruptedException
{
    short s0 = bin.readShort();
    short s1 = bin.readShort();
    if (s0 != STREAM_MAGIC || s1 != STREAM_VERSION) {
        throw new StreamCorruptedException(
                String.format("invalid stream header: %04X%04X", s0, s1));
    }
}

Solution: understand the problem, that is, each time you create an output stream serialization to save the object information, the stream information will be written before writing the object, while the deserialization can only read the first stream information added when reading the file information.

StreamHeader information added for the first time (object input stream can only read this stream information, and subsequent stream information cannot be read)
Object information added for the first time
StreamHeader information added for the second time
Object information added for the second time
······

To sum up, the problem is that there are multiple stream information in the file when reading the file information, so the best solution is to write the stream information only when saving the object for the first time. We can customize a class AppendObjectOutputStream to append and save objects, inherit from ObjectOutputStream class, and override the writeStreamHeader() method of the parent class to save only one stream information.

public class AppendObjectOutputStream extends ObjectOutputStream {
    public static File file = null;

    public AppendAndObjectOutputStream(File file) throws IOException{
        super(new FileOutputStream(file,true));
    }

    
    @Override
    public void writeStreamHeader() throws IOException{
        if(file != null){
            if(file.length() == 0){
                super.writeStreamHeader();
            }else{
                this.reset();
            }
        }else{            
            super.writeStreamHeader();
        }
    }
}

Keywords: Java Programmer Exception

Added by bmpco on Thu, 18 Nov 2021 18:49:59 +0200