Java-对象序列化

原文: java基础复习(18)-对象序列化

对象序列化的深入探究

我研究了一下jdk的实现,希望对你有所帮助,研究情况如下:

在我本机测试代码,查看序列化的文件guo.txt,在ultraEdit下, 用本地编码看会是一串乱码,但是用十六进制查看,就可以发现规律,文件内容如下: AC ED 00 05 7A 00 00 02 FD 11 00 0A 0D 00 0A 07.....(后面内容省略) 每次执行,发现前面的AC ED 00 05总会存在。先解释这个吧。 我在此只是想以代码进一步证明: 对于创建一个对象输出流时,查看构造器的代码如下:

Java代码

public ObjectOutputStream(OutputStream out) throws IOException {
    verifySubclass();
    bout = new BlockDataOutputStream(out);
    handles = new HandleTable(10, (float) 3.00);
    subs = new ReplaceTable(10, (float) 3.00);  
    enableOverride = false;
    writeStreamHeader();
    bout.setBlockDataMode(true);
    if (extendedDebugInfo) {
        debugInfoStack = new DebugTraceInfoStack();
    } else {
        debugInfoStack = null;
    }
}

writeStreamHeader(); //这句很重要,即只要你针对文件打开一个对象输出流,它就会向其中写入4个字节的内容。可以查看到写入的内容是:

Java代码

protected void writeStreamHeader() throws IOException {
    bout.writeShort(STREAM_MAGIC);
    bout.writeShort(STREAM_VERSION);
}
写入的两个常量在接口ObjectStreamConstants定义如下:

Java代码

public interface ObjectStreamConstants {  
/**
 * Magic number that is written to the stream header.
 */
final static short STREAM_MAGIC = (short)0xaced;

/**
 * Version number that is written to the stream header.
 */  
final static short STREAM_VERSION = 5;

从注释就可以知道,ACED只是写入序列化文件的一个固定标记,起标识作用。

BlockDataOutputStream类的writeShort方法在内部又使用了Bits类的方法,具体感兴趣的同学可以自己查看源代码。由于写进的是short,在java中short占两个字节,所以文件头就变成了AC ED 00 05。

然后解释7A。其实当你测试比较少的数据时,当写入的数据长度小于255个时,文件的前面部分会有另外一种结果: AC ED 00 05 77 05 01 10 13 0A 06

boutOjbectOutStream的私有静态内部类BlockDataOutputStream的实例。 该类持有

Java代码

/** buffer for writing block data headers */
private final byte[] hbuf = new byte[MAX_HEADER_SIZE];
// 缓存数据块的头部信息

/** buffer for writing general/block data */
private final byte[] buf = new byte[MAX_BLOCK_SIZE];
// 该缓冲数组保存要写入的数据,但长度大于MAX_BLOCK_SIZE(1024)时,会刷新缓冲将内容写入文件。

在api中也清楚的注明,摘录入下:

基本数据(不包括 serializable 字段和 externalizable 数据)以块数据记录的形式写入 ObjectOutputStream 中。块数据记录由头部和数据组成。 块数据部分包括标记和跟在部分后面的字节数。连续的基本写入数据被合并在一个块数据记录中。块数据记录的分块因子为 1024 字节。每个块数据记录都将填满 1024 字节,或者在终止块数据模式时被写入。

源码如下:

Java代码

private void writeBlockHeader(int len) throws IOException {  
    if (len <= 0xFF) {  
        hbuf[0] = TC_BLOCKDATA;
        hbuf[1] = (byte) len;
        out.write(hbuf, 0, 2);
    } else {
        hbuf[0] = TC_BLOCKDATALONG;
        Bits.putInt(hbuf, 1, len);
        out.write(hbuf, 0, 5);
    }
}

以上代码中的TC_BLOCKDATA也定义在接口ObjectStreamConstants中。

Java代码

/**
 * Block of optional data. Byte following tag indicates number
 * of bytes in this block data.
 */

final static byte TC_BLOCKDATA = (byte) 0x77; // 跟在77后面的字节记录一个数据块中实际写入的字节数,这里只有5个字节的数据内容,由于只有一个字节记录,所以最多只能有 255个字节的数据部分,数据长度该句代码hbuf[1] = (byte) len;产生。

/**
 * long Block data. The long following the tag indicates the
 * number of bytes in this block data.
 */
final static byte TC_BLOCKDATALONG= (byte)0x7A; // 同样的后面跟的四个字节来记录写入的数据长度
每次数据块缓冲区满时(大于1024)就会刷新缓冲区,将块头和数据写入文件保存,所以保存的数据较多时,你会发现在文件的后面部分也会出现多次77 xx或7A xx,那是多次写入引起的 。每次刷新缓冲后,会重置块缓冲buf偏移pos为0,从头写入。数据长度有该句代码产生 Bits.putInt(hbuf, 1, len);

继续查看类Bits的该方法:

Java代码

static void putInt(byte[] b, int off, int val) {
    b[off + 3] = (byte) (val >>> 0);
    b[off + 2] = (byte) (val >>> 8);
    b[off + 1] = (byte) (val >>> 16);
    b[off + 0] = (byte) (val >>> 24);
}

该方法相信大家应该看得懂,其实就是把长度(int)的每个字节取出放到了hbuf中和7A一起做头部。

还有一点就是,如果你调用输出流的write方法后,却不去关闭输出流,当数据量小于1024个字节时,文件中只会包含序列化的文件头: AC ED 00 05,而没有真正写入其他数据.数据量大于1024个字节的话,由于超过容量会刷新缓冲区,文件当然就包含数据咯。所以我们在写入较少数据的时候,注意要关闭输出流,这样就可以在关闭时,将缓冲区的数据写入到文件中去。

还有部分非关键代码就不贴出来了,想研究的同学直接查看就是了。以上内容只是本人针对源码结合测试得出的结论,不足之处,还请大家批评指正。

针对对象序列化,会将序列化的类和字段的基本信息保存在序列化文件中,也可以想见反序列化总会依赖一定信息吧,不可能直接针对一个普通文本文件就反序列化,这样就欠考虑了,呵呵。它也会首先根据文件头(即AC ED 00 05)来判定是不是一个序列化文件,如果不是就直接抛出异常。

针对对象序列化的问题,内容补充:
  • 当你想序列化的类实现了Serialiazble接口时,序列化后再次反序列化时,不会调用该类的默认构造器。
  • 而你的类如果实现了Externalizable接口时,在反序列化产生实例时,会调用默认构造器,初始化成员变量。
参考:
补充:

The following symbols in java.io.ObjectStreamConstants define the terminal and constant values expected in a stream.

final static short STREAM_MAGIC = (short)0xaced;
final static short STREAM_VERSION = 5;
final static byte TC_NULL = (byte)0x70;
final static byte TC_REFERENCE = (byte)0x71;
final static byte TC_CLASSDESC = (byte)0x72;
final static byte TC_OBJECT = (byte)0x73;
final static byte TC_STRING = (byte)0x74;
final static byte TC_ARRAY = (byte)0x75;
final static byte TC_CLASS = (byte)0x76;
final static byte TC_BLOCKDATA = (byte)0x77;
final static byte TC_ENDBLOCKDATA = (byte)0x78;
final static byte TC_RESET = (byte)0x79;
final static byte TC_BLOCKDATALONG = (byte)0x7A;
final static byte TC_EXCEPTION = (byte)0x7B;
final static byte TC_LONGSTRING = (byte) 0x7C;
final static byte TC_PROXYCLASSDESC = (byte) 0x7D;
final static byte TC_ENUM = (byte) 0x7E;
final static  int   baseWireHandle = 0x7E0000;

The flag byte classDescFlags may include values of

final static byte SC_WRITE_METHOD = 0x01; //if SC_SERIALIZABLE
 final static byte SC_BLOCK_DATA = 0x08;    //if SC_EXTERNALIZABLE
final static byte SC_SERIALIZABLE = 0x02;
final static byte SC_EXTERNALIZABLE = 0x04;
final static byte SC_ENUM = 0x10;

转载于:https://www.cnblogs.com/yYang365/p/11275559.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值