【问题标题】:Why this strange behaviour of ObjectOutputStream and ObjectInputStream throwing EOFException?为什么 ObjectOutputStream 和 ObjectInputStream 会抛出 EOFException 的这种奇怪行为?
【发布时间】:2015-10-05 23:27:32
【问题描述】:

我编写了一个自定义序列化/反序列化逻辑来持久化一些数据,因为 Java 默认序列化结果既费时又费内存。为此,我为需要持久化的类编写了readObject(ObjectInput in)writeObject(ObjectOutput out) 方法。但是我注意到,如果我在writeObject(ObjectOutput out) 方法中不使用任何out.writeObject(obj),那么它总是会抛出EOFException

考虑以下示例:

Data.java

public class Data implements BaseData {

private String messageUID;
private String rawData;
private String data;
private Long type;
private Boolean processed = false;
private String processedMessage;
private String processedDetaildMessage;

// getter setter

public void readObject(ObjectInput in) throws IOException, ClassNotFoundException {
    messageUID = in.readUTF();
    rawData = in.readUTF();
    data = in.readUTF();
    type = in.readLong();
    processed = in.readBoolean();
    if (processed) {
        processedMessage = in.readUTF();
        processedDetaildMessage = in.readUTF();
    }
}

public void writeObject(ObjectOutput out) throws IOException {
    out.writeUTF(messageUID);
    out.writeUTF(rawData);
    out.writeUTF(data);
    out.writeLong(type);
    out.writeBoolean(processed);
    if (processed) {
        out.writeUTF(processedMessage);
        String tempDetailsMessage[] = processedDetaildMessage.split(" more");
        out.writeUTF(tempDetailsMessage[tempDetailsMessage.length - 1]);
    }
}

但是,每当我使用上面的代码时,out 流总是在末尾缺少一些信息(来自processedDetaildMessage 字段),我在从in 读取它时得到EOFException,下面的堆栈跟踪(Data.java 行216 是processedDetaildMessage = in.readUTF());

java.io.EOFException
    at java.io.ObjectInputStream$BlockDataInputStream.readByte(ObjectInputStream.java:2766)
    at java.io.ObjectInputStream$BlockDataInputStream.readUTFChar(ObjectInputStream.java:3158)
    at java.io.ObjectInputStream$BlockDataInputStream.readUTFBody(ObjectInputStream.java:3055)
    at java.io.ObjectInputStream$BlockDataInputStream.readUTF(ObjectInputStream.java:2864)
    at java.io.ObjectInputStream.readUTF(ObjectInputStream.java:1072)
    at com.smartstream.common.Data.readObject(Data.java:216)
    at com.smartstream.common.PerformanceTest.getObjectFromBytes(PerformanceTest.java:168)
    at com.smartstream.common.PerformanceTest.access$0(PerformanceTest.java:160)
    at com.smartstream.common.PerformanceTest$1.mapRow(PerformanceTest.java:119)
    at com.smartstream.common.PerformanceTest$1.mapRow(PerformanceTest.java:1)
    at org.springframework.jdbc.core.RowMapperResultSetExtractor.extractData(RowMapperResultSetExtractor.java:92)
    at org.springframework.jdbc.core.RowMapperResultSetExtractor.extractData(RowMapperResultSetExtractor.java:60)
    at org.springframework.jdbc.core.JdbcTemplate$1.doInPreparedStatement(JdbcTemplate.java:651)
    at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:589)
    at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:639)
    at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:668)
    at org.springframework.jdbc.core.JdbcTemplate.query(JdbcTemplate.java:676)
    at org.springframework.jdbc.core.JdbcTemplate.queryForObject(JdbcTemplate.java:731)
    at com.smartstream.common.PerformanceTest.readFromDb(PerformanceTest.java:109)
    at com.smartstream.common.PerformanceTest.main(PerformanceTest.java:66)

所以我虽然在写入所有必填字段后会在末尾添加一些额外的字节/s 信息,并且不会读取它们,这样我在读取时不会到达文件末尾。我尝试了所有这些out.writeByte(-1)out.writeInt(-1)out.writeLong(2342343l)out.writeUTF("END_OF_STREAM"),但这些都没有区别。最后我做到了out.writeObject(new String("END_OF_STREAM")),它工作正常。如果没有使用writeObject() 方法写入任何信息,有人可以解释为什么 outputstream 会丢失一些信息。下面是我如何读写流;

private byte[] getObjectAsBytes(Data data) {
    byte[] byteArray = null;
    ByteArrayOutputStream bos = null;
    ObjectOutputStream oos = null;
    try {
        bos = new ByteArrayOutputStream();
        oos = new ObjectOutputStream(bos);
        // Use this for java default serialization
        // oos.writeObject(data);
        data.writeObject(oos);
        byteArray = bos.toByteArray();
    } catch (IOException e) {
        e.printStackTrace();
    } finally {
        if (oos != null) {
            try {
                oos.flush();
                oos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    return byteArray;
}

private Data getObjectFromBytes(byte[] byteArray) {
    Data data = new Data();
    ByteArrayInputStream bais = null;
    ObjectInputStream ois = null;
    try {
        bais = new ByteArrayInputStream(byteArray);
        ois = new ObjectInputStream(bais);
        // Use this for java default serialization
        // data = (Data) ois.readObject();
        data.readObject(ois);
    } catch (IOException e) {
        e.printStackTrace();
    } catch (ClassNotFoundException e) {
        e.printStackTrace();
    } finally {
        if (ois != null) {
            try {
                ois.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    return data;
}

如果有人感兴趣,下面是流中写的内容;

带有原始代码的持久化数据(抛出 EOFException 和缺失信息)(不要将堆栈跟踪与原始问题混淆,此堆栈跟踪作为字段 processedDetailedMessage 持久化)

¬í---z-------3507319347632941385----FEEDER-----1437052314954 ---This is a random string---N---þ%J---!this is message of processed dataÛ
Caused by: java.sql.SQLException: ORA-01691: unable to extend lob segment TLM_DBO.SYS_LOB0000076335C00008$$ by 8192 in tablespace WIN_SL_TABLE
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:439)
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:395)
at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:802)
at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:436)
at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:186)
at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:521)
at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:205)
at oracle.jdbc.driver.T4CPreparedStatement.executeForRows(T4CPreparedStatement.java:1008)
at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1307)
at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3449)
at oracle.jdbc.driver.OraclePre

使用writeObject方法在末尾写入额外字符串后持久化数据

¬í---z-------3507319347632941385----FEEDER-----1437052314954 ---This is a random string---N---þ%J---!this is message of processed dataÛ
Caused by: java.sql.SQLException: ORA-01691: unable to extend lob segment TLM_DBO.SYS_LOB0000076335C00008$$ by 8192 in tablespace WIN_SL_TABLE
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:439)
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:395)
at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:802)
at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:436)
at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:186)
at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:521)
at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:205)
at oracle.jdbc.driver.T4CPreparedStatement.executeForRows(T4CPreparedStatement.java:1008)
at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1307)
at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3449)
at oracle.jdbc.driver.OraclePrz-----NeparedStatement.execute(OraclePreparedStatement.java:3550)
at oracle.jdbc.driver.OraclePreparedStatementWrapper.execute(OraclePreparedStatementWrapper.java:1374)
at com.ibm.ws.rsadapter.jdbc.WSJdbcPreparedStatement.pmiExecute(WSJdbcPreparedStatement.java:975)
at com.ibm.ws.rsadapter.jdbc.WSJdbcPreparedStatement.execute(WSJdbcPreparedStatement.java:642)
at com.smartstream.control.engine.config.dao.jdbc.ProcessExecutionAuditDetailDao$1.doInPreparedStatement(ProcessExecutionAuditDetailDao.java:115)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:586)
... 23t 
END_OF_STREAM

PS ----代表不可读的字节

【问题讨论】:

  • 这不是答案,而是建议查看 java Externalizable,因为它基本上是一个自定义序列化接口。这可能会更干净一些:)
  • @red13 感谢您的推荐,但我们不能使用 Externalizable,因为在运行此代码的平台中,该类需要可序列化,并且由于该平台不支持 Externalizable,因此会引发异常。所以这个类是可序列化的,但编写自定义逻辑背后的原因是内存占用和性能。
  • 您不需要这些readObject()writeObject() 方法。他们不会为您做任何默认序列化也不会做的事情。我会删除它们并使用Data data = (Data)ois.readObject(); 而不是data.readObject(ois);,和oos.writeObject(data); 而不是data.writeObject(oos);。正如你现在所拥有的,这些方法是公开的,这是一个安全漏洞。
  • @EJP 恕我直言,您没有得到问题中的问题。我已经提到默认序列化是内存和性能昂贵。我添加的代码 sn-p 只是问题的最小表示,而不是实际代码。在实际代码中,该类具有许多复杂的数据类型,而 java 在序列化时会写入所有这些完全限定的类名,这完全没有必要。在我们的实际代码中,如果使用 java 默认序列化,则序列化类占用超过 8000 字节,而使用自定义序列化程序我们设法在 3000 字节以下完成。

标签: java objectinputstream objectoutputstream


【解决方案1】:

您的持久数据不完整,因为您是在刷新 ObjectOutputStream 之前创建字节数组。在 getObjectAsBytes(Data) 中,在 finally 块之后移动 byteArray = bos.toByteArray(); 以使其工作。或者,该方法可以更简洁地编写如下(需要 Java 7+):

private byte[] getObjectAsBytes(Data data) {
    ByteArrayOutputStream bos = new ByteArrayOutputStream();
    try (ObjectOutputStream oos = new ObjectOutputStream(bos)) {
        data.writeObject(oos);
    } catch (IOException e) {
        e.printStackTrace();
    }
    return bos.toByteArray();
}

我在自己的程序中测试了这两种方式,它们都阻止了 EOFException 被抛出。

至于为什么 writeObject 起作用,那是因为 underlying writeObject implementation 在方法的开始和结束时切换块数据模式,而 changing the block data mode performs a drain 将所有数据写入底层 OutputStream,对于 ByteArrayOutputStream 是实际上与冲洗相同。

【讨论】:

  • 或者只是改变 return byteArray;返回 bos.toByteArray();
  • 非常感谢您解释问题的实际原因。
【解决方案2】:

这个问题是由于writeObject 方法和其他一些非泛型write* 方法(即writeUTF)的不同实现引起的。 writeObject 方法在方法开始和结束时切换到数据块模式,这导致所有数据都写入底层OutputStream,这与在outputStream 上调用flush 具有相同的效果。这意味着在将剩余数据刷新到流之前,您不能创建另一个 byteArray。最好现在坚持使用writeObject 方法;即

public void writeObject(ObjectOutput out) throws IOException {
    out.writeUTF(messageUID);
    out.writeUTF(rawData);
    out.writeUTF(data);
    out.writeLong(type);
    out.writeBoolean(processed);
    if (processed) {
        out.writeUTF(processedMessage);
        String tempDetailsMessage[] = processedDetaildMessage.split(" more");
        out.writeObject(tempDetailsMessage[tempDetailsMessage.length - 1]);
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-16
    • 2020-12-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-03-13
    相关资源
    最近更新 更多