为什么 ObjectOutputStream 和 ObjectInputStream 抛出 EOFException 的这种奇怪行为?
Why this strange behaviour of ObjectOutputStream and ObjectInputStream throwing EOFException?
我写了一个自定义 serializing/de-serializing 逻辑来保存一些数据,因为 Java 默认序列化结果是时间和内存都非常昂贵。为此,我为需要持久化的 class(es) 编写了 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
字段),我在从 [=23] 读取它时得到 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[=11=](PerformanceTest.java:160)
at com.smartstream.common.PerformanceTest.mapRow(PerformanceTest.java:119)
at com.smartstream.common.PerformanceTest.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.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)
所以我虽然会在写完所有必填字段后在末尾放置一些额外的 byte/s 信息,但不会读取它们,这样我在读取时就不会到达文件末尾。我尝试了所有这些 out.writeByte(-1)
、out.writeInt(-1)
、out.writeLong(2342343l)
、out.writeUTF("END_OF_STREAM")
,但这些都没有区别。最后我做了 out.writeObject(new String("END_OF_STREAM"))
并且效果很好。如果 none 的信息是使用 writeObject()
方法编写的,有人可以解释为什么 outputstream 会遗漏一些信息吗?下面是我如何读写 to/from streams;
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.doInPreparedStatement(ProcessExecutionAuditDetailDao.java:115)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:586)
... 23t
END_OF_STREAM
PS ----表示不可读字节
您的持久数据不完整,因为您在刷新 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 toggles block data mode at the beginning and ending of the method, and changing the block data mode performs a drain 将所有数据写入底层 OutputStream,对于 ByteArrayOutputStream,它实际上与刷新相同。
此问题是由于 writeObject
方法和其他一些 non-generic 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]);
}
}
我写了一个自定义 serializing/de-serializing 逻辑来保存一些数据,因为 Java 默认序列化结果是时间和内存都非常昂贵。为此,我为需要持久化的 class(es) 编写了 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
字段),我在从 [=23] 读取它时得到 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[=11=](PerformanceTest.java:160)
at com.smartstream.common.PerformanceTest.mapRow(PerformanceTest.java:119)
at com.smartstream.common.PerformanceTest.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.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)
所以我虽然会在写完所有必填字段后在末尾放置一些额外的 byte/s 信息,但不会读取它们,这样我在读取时就不会到达文件末尾。我尝试了所有这些 out.writeByte(-1)
、out.writeInt(-1)
、out.writeLong(2342343l)
、out.writeUTF("END_OF_STREAM")
,但这些都没有区别。最后我做了 out.writeObject(new String("END_OF_STREAM"))
并且效果很好。如果 none 的信息是使用 writeObject()
方法编写的,有人可以解释为什么 outputstream 会遗漏一些信息吗?下面是我如何读写 to/from streams;
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.doInPreparedStatement(ProcessExecutionAuditDetailDao.java:115)
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:586)
... 23t
END_OF_STREAM
PS ----表示不可读字节
您的持久数据不完整,因为您在刷新 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 toggles block data mode at the beginning and ending of the method, and changing the block data mode performs a drain 将所有数据写入底层 OutputStream,对于 ByteArrayOutputStream,它实际上与刷新相同。
此问题是由于 writeObject
方法和其他一些 non-generic 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]);
}
}