java序列化

【java】java序列化、ArrayList序列化、序列化策略 1、如果一个类想被序列化,需要实现Serializable接口。否则将抛出异常,这是因为,在序列化操作过程中会对类型进行检查,要求被序列化的类必须属于Enum、Array和Serializable类型其中的任何一种。2、在变量声明前加上该关键字,可以阻止该变量被序列化到文件中。3、在类中增加writeObject 和 readObject 方法可以实现自定义序列化策略。 阅读详情

原文出处: 天凉好个秋

转发:http://www.importnew.com/24490.html

前言

对于Java的序列化,一直只知道只需要实现Serializbale这个接口就可以了,具体内部实现一直不是很了解,正好这次在重复造RPC的轮子的时候涉及到序列化问题,就抽时间看了下 Java序列化的底层实现,这篇文章算是这次的学习小结吧。

第一部分:What

Java序列化是指把Java对象保存为二进制字节码的过程,Java反序列化是指把二进制码重新转换成Java对象的过程。

那么为什么需要序列化呢?

第一种情况是:一般情况下Java对象的声明周期都比Java虚拟机的要短,实际应用中我们希望在JVM停止运行之后能够持久化指定的对象,这时候就需要把对象进行序列化之后保存。

第二种情况是:需要把Java对象通过网络进行传输的时候。因为数据只能够以二进制的形式在网络中进行传输,因此当把对象通过网络发送出去之前需要先序列化成二进制数据,在接收端读到二进制数据之后反序列化成Java对象。

第二部分:How

本部分以序列化到文件为例讲解Java序列化的基本用法。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

package com.beautyboss.slogen;

 

import java.io.*;

 

/**

* Author : Slogen

* AddTime : 17/4/30

*/

public class SerializableTest {

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

FileOutputStream fos = new FileOutputStream("temp.out");

ObjectOutputStream oos = new ObjectOutputStream(fos);

TestObject testObject = new TestObject();

oos.writeObject(testObject);

oos.flush();

oos.close();

 

FileInputStream fis = new FileInputStream("temp.out");

ObjectInputStream ois = new ObjectInputStream(fis);

TestObject deTest = (TestObject) ois.readObject();

System.out.println(deTest.testValue);

System.out.println(deTest.parentValue);

System.out.println(deTest.innerObject.innerValue);

}

}

 

class Parent implements Serializable {

 

private static final long serialVersionUID = -4963266899668807475L;

 

public int parentValue = 100;

}

 

class InnerObject implements Serializable {

 

private static final long serialVersionUID = 5704957411985783570L;

 

public int innerValue = 200;

}

 

class TestObject extends Parent implements Serializable {

 

private static final long serialVersionUID = -3186721026267206914L;

 

public int testValue = 300;

 

public InnerObject innerObject = new InnerObject();

}

程序执行完用vim打开temp.out文件,可以看到

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

0000000: aced 0005 7372 0020 636f 6d2e 6265 6175 ....sr. com.beau

0000010: 7479 626f 7373 2e73 6c6f 6765 6e2e 5465 tyboss.slogen.Te

0000020: 7374 4f62 6a65 6374 d3c6 7e1c 4f13 2afe stObject..~.O.*.

0000030: 0200 0249 0009 7465 7374 5661 6c75 654c ...I..testValueL

0000040: 000b 696e 6e65 724f 626a 6563 7474 0023 ..innerObjectt.#

0000050: 4c63 6f6d 2f62 6561 7574 7962 6f73 732f Lcom/beautyboss/

0000060: 736c 6f67 656e 2f49 6e6e 6572 4f62 6a65 slogen/InnerObje

0000070: 6374 3b78 7200 1c63 6f6d 2e62 6561 7574 ct;xr..com.beaut

0000080: 7962 6f73 732e 736c 6f67 656e 2e50 6172 yboss.slogen.Par

0000090: 656e 74bb 1eef 0d1f c950 cd02 0001 4900 ent......P....I.

00000a0: 0b70 6172 656e 7456 616c 7565 7870 0000 .parentValuexp..

00000b0: 0064 0000 012c 7372 0021 636f 6d2e 6265 .d...,sr.!com.be

00000c0: 6175 7479 626f 7373 2e73 6c6f 6765 6e2e autyboss.slogen.

00000d0: 496e 6e65 724f 626a 6563 744f 2c14 8a40 InnerObjectO,..@

00000e0: 24fb 1202 0001 4900 0a69 6e6e 6572 5661 $.....I..innerVa

00000f0: 6c75 6578 7000 0000 c8 luexp....

 

第三部分:Why

调用ObjectOutputStream.writeObject()和ObjectInputStream.readObject()之后究竟做了什么?temp.out文件中的二进制分别代表什么意思?

别急,且听我娓娓道来。

1. ObjectStreamClass类

官方文档对这个类的介绍如下

Serialization’s descriptor for classes. It contains the name and serialVersionUID of the class. The ObjectStreamClass for a specific class loaded in this Java VM can be found/created using the lookup method.

可以看到ObjectStreamClass这个是类的序列化描述符,这个类可以描述需要被序列化的类的元数据,包括被序列化的类的名字以及序列号。可以通过lookup()方法来查找/创建在这个JVM中加载的特定的ObjectStreamClass对象。

2. 序列化:writeObject()

在调用wroteObject()进行序列化之前会先调用ObjectOutputStream的构造函数生成一个ObjectOutputStream对象,构造函数如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

public ObjectOutputStream(OutputStream out) throws IOException {

    verifySubclass();

    // bout表示底层的字节数据容器

    bout = new BlockDataOutputStream(out);

    handles = new HandleTable(10, (float) 3.00);

    subs = new ReplaceTable(10, (float) 3.00);

    enableOverride = false;

    writeStreamHeader(); // 写入文件头

    bout.setBlockDataMode(true); // flush数据

    if (extendedDebugInfo) {

        debugInfoStack = new DebugTraceInfoStack();

    } else {

        debugInfoStack = null;

    }

}

构造函数中首先会把bout对绑定到底层的字节数据容器,接着会调用writeStreamHeader()方法,该方法实现如下:

1

2

3

4

protected void writeStreamHeader() throws IOException {

    bout.writeShort(STREAM_MAGIC);

    bout.writeShort(STREAM_VERSION);

}

在writeStreamHeader()方法中首先会往底层字节容器中写入表示序列化的Magic Number以及版本号,定义为

1

2

3

4

5

6

7

8

9

/**

 * 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;

接下来会调用writeObject()方法进行序列化,实现如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

public final void writeObject(Object obj) throws IOException {

    if (enableOverride) {

        writeObjectOverride(obj);

        return;

    }

    try {

        // 调用writeObject0()方法序列化

        writeObject0(obj, false);

    } catch (IOException ex) {

        if (depth == 0) {

            writeFatalException(ex);

        }

        throw ex;

    }

}

正常情况下会调用writeObject0()进行序列化操作,该方法实现如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

private void writeObject0(Object obj, boolean unshared)

    throws IOException

{

    // 一些省略代码

    try {

        // 一些省略代码

        Object orig = obj;

        // 获取要序列化的对象的Class对象

        Class cl = obj.getClass();

        ObjectStreamClass desc;

        for (;;) {

            Class repCl;

            // 创建描述cl的ObjectStreamClass对象

            desc = ObjectStreamClass.lookup(cl, true);

            // 其他省略代码

        }

        // 一些省略代码

        // 根据实际的类型进行不同的写入操作

        // remaining cases

        if (obj instanceof String) {

            writeString((String) obj, unshared);

        } else if (cl.isArray()) {

            writeArray(obj, desc, unshared);

        } else if (obj instanceof Enum) {

            writeEnum((Enum) obj, desc, unshared);

        } else if (obj instanceof Serializable) {

            // 被序列化对象实现了Serializable接口

            writeOrdinaryObject(obj, desc, unshared);

        } else {

            if (extendedDebugInfo) {

                throw new NotSerializableException(

                    cl.getName() + "\n" + debugInfoStack.toString());

            } else {

                throw new NotSerializableException(cl.getName());

            }

        }

    } finally {

        depth--;

        bout.setBlockDataMode(oldMode);

    }

}

从代码里面可以看到,程序会

  1. 生成一个描述被序列化对象的类的类元信息的ObjectStreamClass对象。
  2. 根据传入的需要序列化的对象的实际类型进行不同的序列化操作。从代码里面可以很明显的看到,对于String类型、数组类型和Enum可以直接进行序列化。如果被序列化对象实现了Serializable对象,则会调用writeOrdinaryObject()方法进行序列化。

这里可以解释一个问题:Serializbale接口是个空的接口,并没有定义任何方法,为什么需要序列化的接口只要实现Serializbale接口就能够进行序列化。

答案是:Serializable接口这是一个标识,告诉程序所有实现了”我”的对象都需要进行序列化。

因此,序列化过程接下来会执行到writeOrdinaryObject()这个方法中,该方法实现如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

private void writeOrdinaryObject(Object obj,

                                 ObjectStreamClass desc,

                                 boolean unshared) throws IOException

{

    if (extendedDebugInfo) {

        debugInfoStack.push(

            (depth == 1 ? "root " : "") + "object (class \"" +

            obj.getClass().getName() + "\", " + obj.toString() + ")");

    }

    try {

        desc.checkSerialize();

 

        bout.writeByte(TC_OBJECT); // 写入Object标志位

        writeClassDesc(desc, false); // 写入类元数据

        handles.assign(unshared ? null : obj);

        if (desc.isExternalizable() && !desc.isProxy()) {

            writeExternalData((Externalizable) obj);

        } else {

            writeSerialData(obj, desc); // 写入被序列化的对象的实例数据

        }

    } finally {

        if (extendedDebugInfo) {

            debugInfoStack.pop();

        }

    }

}

在这个方法中首先会往底层字节容器中写入TC_OBJECT,表示这是一个新的Object

1

2

3

4

/**

 * new Object.

 */

final static byte TC_OBJECT =       (byte)0x73;

接下来会调用writeClassDesc()方法写入被序列化对象的类的类元数据,writeClassDesc()方法实现如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

private void writeClassDesc(ObjectStreamClass desc, boolean unshared)

    throws IOException

{

    int handle;

    if (desc == null) {

        // 如果desc为null

        writeNull();

    } else if (!unshared && (handle = handles.lookup(desc)) != -1) {

        writeHandle(handle);

    } else if (desc.isProxy()) {

        writeProxyDesc(desc, unshared);

    } else {

        writeNonProxyDesc(desc, unshared);

    }

}

在这个方法中会先判断传入的desc是否为null,如果为null则调用writeNull()方法

1

2

3

4

5

private void writeNull() throws IOException {

    // TC_NULL =         (byte)0x70;

    // 表示对一个Object引用的描述的结束

    bout.writeByte(TC_NULL);

}

如果不为null,则一般情况下接下来会调用writeNonProxyDesc()方法,该方法实现如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

private void writeNonProxyDesc(ObjectStreamClass desc, boolean unshared)

    throws IOException

{

    // TC_CLASSDESC =    (byte)0x72;

    // 表示一个新的Class描述符

    bout.writeByte(TC_CLASSDESC);

    handles.assign(unshared ? null : desc);

 

    if (protocol == PROTOCOL_VERSION_1) {

        // do not invoke class descriptor write hook with old protocol

        desc.writeNonProxy(this);

    } else {

        writeClassDescriptor(desc);

    }

 

    Class cl = desc.forClass();

    bout.setBlockDataMode(true);

    if (cl != null && isCustomSubclass()) {

        ReflectUtil.checkPackageAccess(cl);

    }

    annotateClass(cl);

    bout.setBlockDataMode(false);

    bout.writeByte(TC_ENDBLOCKDATA);

 

    writeClassDesc(desc.getSuperDesc(), false);

}

在这个方法中首先会写入一个字节的TC_CLASSDESC,这个字节表示接下来的数据是一个新的Class描述符,接着会调用writeNonProxy()方法写入实际的类元信息,writeNonProxy()实现如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

void writeNonProxy(ObjectOutputStream out) throws IOException {

    out.writeUTF(name); // 写入类的名字

    out.writeLong(getSerialVersionUID()); // 写入类的序列号

 

    byte flags = 0;

    // 获取类的标识

    if (externalizable) {

        flags |= ObjectStreamConstants.SC_EXTERNALIZABLE;

        int protocol = out.getProtocolVersion();

        if (protocol != ObjectStreamConstants.PROTOCOL_VERSION_1) {

            flags |= ObjectStreamConstants.SC_BLOCK_DATA;

        }

    } else if (serializable) {

        flags |= ObjectStreamConstants.SC_SERIALIZABLE;

    }

    if (hasWriteObjectData) {

        flags |= ObjectStreamConstants.SC_WRITE_METHOD;

    }

    if (isEnum) {

        flags |= ObjectStreamConstants.SC_ENUM;

    }

    out.writeByte(flags); // 写入类的flag

 

    out.writeShort(fields.length); // 写入对象的字段的个数

    for (int i = 0; i < fields.length; i++) {

        ObjectStreamField f = fields[i];

        out.writeByte(f.getTypeCode());

        out.writeUTF(f.getName());

        if (!f.isPrimitive()) {

            // 如果不是原始类型,即是对象或者Interface

            // 则会写入表示对象或者类的类型字符串

            out.writeTypeString(f.getTypeString());

        }

    }

}

writeNonProxy()方法中会按照以下几个过程来写入数据:

1. 调用writeUTF()方法写入对象所属类的名字,对于本例中name = com.beautyboss.slogen.TestObject.对于writeUTF()这个方法,在写入实际的数据之前会先写入name的字节数,代码如下:

1

2

3

4

5

6

7

8

9

10

11

12

void writeUTF(String s, long utflen) throws IOException {

        if (utflen > 0xFFFFL) {

            throw new UTFDataFormatException();

        }

        // 写入两个字节的s的长度

        writeShort((int) utflen);

        if (utflen == (long) s.length()) {

            writeBytes(s);

        } else {

            writeUTFBody(s);

        }

    }

2. 接下来会调用writeLong()方法写入类的序列号UID,UID是通过getSerialVersionUID()方法来获取。
3. 接着会判断被序列化的对象所属类的flag,并写入底层字节容器中(占用两个字节)。类的flag分为以下几类:

  • final static byte SC_EXTERNALIZABLE = 0×04;表示该类为Externalizable类,即实现了Externalizable接口。
  • final static byte SC_SERIALIZABLE = 0×02;表示该类实现了Serializable接口。
  • final static byte SC_WRITE_METHOD = 0×01;表示该类实现了Serializable接口且自定义了writeObject()方法。
  • final static byte SC_ENUM = 0×10;表示该类是个Enum类型。

对于本例中flag = 0×02表示只是Serializable类型。

4. 第四步会依次写入被序列化对象的字段的元数据。

<1> 首先会写入被序列化对象的字段的个数,占用两个字节。本例中为2,因为TestObject类中只有两个字段,一个是int类型的testValue,一个是InnerObject类型的innerValue。

<2> 依次写入每个字段的元数据。每个单独的字段由ObjectStreamField类来表示。

1) 写入字段的类型码,占一个字节。 类型码的映射关系如下:

2) 调用writeUTF()方法写入每个字段的名字。注意,writeUTF()方法会先写入名字占用的字节数。

3) 如果被写入的字段不是基本类型,则会接着调用writeTypeString()方法写入代表对象或者类的类型字符串,该方法需要一个参数,表示对应的类或者接口的字符串,最终调用的还是writeString()方法,实现如下

1

2

3

4

5

6

7

8

9

10

11

12

13

private void writeString(String str, boolean unshared) throws IOException {

    handles.assign(unshared ? null : str);

    long utflen = bout.getUTFLength(str);

    if (utflen <= 0xFFFF) {

        // final static byte TC_STRING = (byte)0x74;

        // 表示接下来的字节表示一个字符串

        bout.writeByte(TC_STRING);

        bout.writeUTF(str, utflen);

    } else {

        bout.writeByte(TC_LONGSTRING);

        bout.writeLongUTF(str, utflen);

    }

}

在这个方法中会先写入一个标志位TC_STRING表示接下来的数据是一个字符串,接着会调用writeUTF()写入字符串。

执行完上面的过程之后,程序流程重新回到writeNonProxyDesc()方法中

1

2

3

4

5

6

7

8

9

10

11

private void writeNonProxyDesc(ObjectStreamClass desc, boolean unshared)

    throws IOException

{

    // 其他省略代码

 

    // TC_ENDBLOCKDATA = (byte)0x78;

    // 表示对一个object的描述块的结束

    bout.writeByte(TC_ENDBLOCKDATA);

 

    writeClassDesc(desc.getSuperDesc(), false); // 尾递归调用,写入父类的类元数据

}

接下来会写入一个字节的标志位TC_ENDBLOCKDATA表示对一个object的描述块的结束。

然后会调用writeClassDesc()方法,传入父类的ObjectStreamClass对象,写入父类的类元数据。

需要注意的是writeClassDesc()这个方法是个递归调用,调用结束返回的条件是没有了父类,即传入的ObjectStreamClass对象为null,这个时候会写入一个字节的标识位TC_NULL.

在递归调用完成写入类的类元数据之后,程序执行流程回到wriyeOrdinaryObject()方法中,

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

private void writeOrdinaryObject(Object obj,

                                 ObjectStreamClass desc,

                                 boolean unshared) throws IOException

{

    // 其他省略代码

    try {

        desc.checkSerialize();

        // 其他省略代码

        if (desc.isExternalizable() && !desc.isProxy()) {

            writeExternalData((Externalizable) obj);

        } else {

            writeSerialData(obj, desc); // 写入被序列化的对象的实例数据

        }

    } finally {

        if (extendedDebugInfo) {

            debugInfoStack.pop();

        }

    }

}

从上面的分析中我们可以知道,当写入类的元数据的时候,是先写子类的类元数据,然后递归调用的写入父类的类元数据。

接下来会调用writeSerialData()方法写入被序列化的对象的字段的数据,方法实现如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

private void writeSerialData(Object obj, ObjectStreamClass desc)

    throws IOException

{

    // 获取表示被序列化对象的数据的布局的ClassDataSlot数组,父类在前

    ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();

    for (int i = 0; i < slots.length; i++) {

        ObjectStreamClass slotDesc = slots[i].desc;

        if (slotDesc.hasWriteObjectMethod()) {

           // 如果被序列化对象自己实现了writeObject()方法,则执行if块里的代码

 

           // 一些省略代码

        } else {

            // 调用默认的方法写入实例数据

            defaultWriteFields(obj, slotDesc);

        }

    }

}

在这个方法中首先会调用getClassDataSlot()方法获取被序列化对象的数据的布局,关于这个方法官方文档中说明如下:

1

2

3

4

5

6

7

8

/**

 * Returns array of ClassDataSlot instances representing the data layout

 * (including superclass data) for serialized objects described by this

 * class descriptor.  ClassDataSlots are ordered by inheritance with those

 * containing "higher" superclasses appearing first.  The final

 * ClassDataSlot contains a reference to this descriptor.

 */

 ClassDataSlot[] getClassDataLayout() throws InvalidClassException;

需要注意的是这个方法会把从父类继承的数据一并返回,并且表示从父类继承的数据的ClassDataSlot对象在数组的最前面。

对于没有自定义writeObject()方法的对象来说,接下来会调用defaultWriteFields()方法写入数据,该方法实现如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

private void defaultWriteFields(Object obj, ObjectStreamClass desc)

    throws IOException

{

    // 其他一些省略代码

 

    int primDataSize = desc.getPrimDataSize();

    if (primVals == null || primVals.length < primDataSize) {

        primVals = new byte[primDataSize];

    }

    // 获取对应类中的基本数据类型的数据并保存在primVals字节数组中

    desc.getPrimFieldValues(obj, primVals);

    // 把基本数据类型的数据写入底层字节容器中

    bout.write(primVals, 0, primDataSize, false);

 

    // 获取对应类的所有的字段对象

    ObjectStreamField[] fields = desc.getFields(false);

    Object[] objVals = new Object[desc.getNumObjFields()];

    int numPrimFields = fields.length - objVals.length;

    // 把对应类的Object类型(非原始类型)的对象保存到objVals数组中

    desc.getObjFieldValues(obj, objVals);

    for (int i = 0; i < objVals.length; i++) {

        // 一些省略的代码

 

        try {

            // 对所有Object类型的字段递归调用writeObject0()方法写入对应的数据

            writeObject0(objVals[i],

                         fields[numPrimFields + i].isUnshared());

        } finally {

            if (extendedDebugInfo) {

                debugInfoStack.pop();

            }

        }

    }

}

可以看到,在这个方法中会做下面几件事情:

<1> 获取对应类的基本类型的字段的数据,并写入到底层的字节容器中。
<2> 获取对应类的Object类型(非基本类型)的字段成员,递归调用writeObject0()方法写入相应的数据。

从上面对写入数据的分析可以知道,写入数据是是按照先父类后子类的顺序来写的。

至此,Java序列化过程分析完毕,总结一下,在本例中序列化过程如下:

现在可以来分析下第二步中写入的temp.out文件的内容了。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

aced        Stream Magic

0005        序列化版本号

73          标志位:TC_OBJECT,表示接下来是个新的Object

72          标志位:TC_CLASSDESC,表示接下来是对Class的描述

0020        类名的长度为32

636f 6d2e 6265 6175 7479 626f 7373 2e73 com.beautyboss.s

6c6f 6765 6e2e 5465 7374 4f62 6a65 6374 logen.TestObject

d3c6 7e1c 4f13 2afe 序列号

02          flag,可序列化

00 02       TestObject的字段的个数,为2

49          TypeCode,I,表示int类型

0009        字段名长度,占9个字节

7465 7374 5661 6c75 65      字段名:testValue

4c          TypeCode:L,表示是个Class或者Interface

000b        字段名长度,占11个字节

696e 6e65 724f 626a 6563 74 字段名:innerObject

74          标志位:TC_STRING,表示后面的数据是个字符串

0023        类名长度,占35个字节

4c63 6f6d 2f62 6561 7574 7962 6f73 732f  Lcom/beautyboss/

736c 6f67 656e 2f49 6e6e 6572 4f62 6a65  slogen/InnerObje

6374 3b                                  ct;

78          标志位:TC_ENDBLOCKDATA,对象的数据块描述的结束

接下来开始写入数据,从父类Parent开始

1

2

0000 0064 parentValue的值:100

0000 012c testValue的值:300

接下来是写入InnerObject的类元信息

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

73 标志位,TC_OBJECT:表示接下来是个新的Object

72 标志位,TC_CLASSDESC:表示接下来是对Class的描述

0021 类名的长度,为33

636f 6d2e 6265 6175 7479 626f 7373 com.beautyboss

2e73 6c6f 6765 6e2e 496e 6e65 724f .slogen.InnerO

626a 6563 74 bject

4f2c 148a 4024 fb12 序列号

02 flag,表示可序列化

0001 字段个数,1个

49 TypeCode,I,表示int类型

00 0a 字段名长度,10个字节

69 6e6e 6572 5661 6c75 65 innerValue

78 标志位:TC_ENDBLOCKDATA,对象的数据块描述的结束

70 标志位:TC_NULL,Null object reference.

0000 00c8 innervalue的值:200

 

3. 反序列化:readObject()

反序列化过程就是按照前面介绍的序列化算法来解析二进制数据。

有一个需要注意的问题就是,如果子类实现了Serializable接口,但是父类没有实现Serializable接口,这个时候进行反序列化会发生什么情况?

答:如果父类有默认构造函数的话,即使没有实现Serializable接口也不会有问题,反序列化的时候会调用默认构造函数进行初始化,否则的话反序列化的时候会抛出.InvalidClassException:异常,异常原因为no valid constructor。

第四部分:Other

1. static和transient字段不能被序列化。

序列化的时候所有的数据都是来自于ObejctStreamClass对象,在生成ObjectStreamClass的构造函数中会调用fields = getSerialFields(cl);这句代码来获取需要被序列化的字段,getSerialFields()方法实际上是调用getDefaultSerialFields()方法的,getDefaultSerialFields()实现如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

private static ObjectStreamField[] getDefaultSerialFields(Class<?> cl) {

    Field[] clFields = cl.getDeclaredFields();

    ArrayList<ObjectStreamField> list = new ArrayList<>();

    int mask = Modifier.STATIC | Modifier.TRANSIENT;

 

    for (int i = 0; i < clFields.length; i++) {

        if ((clFields[i].getModifiers() & mask) == 0) {

            // 如果字段既不是static也不是transient的才会被加入到需要被序列化字段列表中去

            list.add(new ObjectStreamField(clFields[i], false, true));

        }

    }

    int size = list.size();

    return (size == 0) ? NO_FIELDS :

        list.toArray(new ObjectStreamField[size]);

}

从上面的代码中可以很明显的看到,在计算需要被序列化的字段的时候会把被static和transient修饰的字段给过滤掉。

在进行反序列化的时候会给默认值。

2. 如何实现自定义序列化和反序列化?

只需要被序列化的对象所属的类定义了void writeObject(ObjectOutputStream oos)和void readObject(ObjectInputStream ois)方法即可,Java序列化和反序列化的时候会调用这两个方法,那么这个功能是怎么实现的呢?

1. 在ObjectClassStream类的构造函数中有下面几行代码:

1

2

3

4

5

6

7

8

9

10

cons = getSerializableConstructor(cl);

writeObjectMethod = getPrivateMethod(cl, "writeObject",

    new Class<?>[] { ObjectOutputStream.class },

    Void.TYPE);

readObjectMethod = getPrivateMethod(cl, "readObject",

    new Class<?>[] { ObjectInputStream.class },

    Void.TYPE);

readObjectNoDataMethod = getPrivateMethod(

    cl, "readObjectNoData", null, Void.TYPE);

hasWriteObjectData = (writeObjectMethod != null);

getPrivateMethod()方法实现如下:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

private static Method getPrivateMethod(Class<?> cl, String name,

                                   Class<?>[] argTypes,

                                   Class<?> returnType)

{

    try {

        Method meth = cl.getDeclaredMethod(name, argTypes);

        meth.setAccessible(true);

        int mods = meth.getModifiers();

        return ((meth.getReturnType() == returnType) &&

                ((mods & Modifier.STATIC) == 0) &&

                ((mods & Modifier.PRIVATE) != 0)) ? meth : null;

    } catch (NoSuchMethodException ex) {

        return null;

    }

}

可以看到在ObejctStreamClass的构造函数中会查找被序列化类中有没有定义为void writeObject(ObjectOutputStream oos) 的函数,如果找到的话,则会把找到的方法赋值给writeObjectMethod这个变量,如果没有找到的话则为null。

2. 在调用writeSerialData()方法写入序列化数据的时候有

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

private void writeSerialData(Object obj, ObjectStreamClass desc)

    throws IOException

{

    ObjectStreamClass.ClassDataSlot[] slots = desc.getClassDataLayout();

    for (int i = 0; i < slots.length; i++) {

        ObjectStreamClass slotDesc = slots[i].desc;

        if (slotDesc.hasWriteObjectMethod()) {

            // 其他一些省略代码

            try {

                curContext = new SerialCallbackContext(obj, slotDesc);

                bout.setBlockDataMode(true);

                // 在这里调用用户自定义的方法

                slotDesc.invokeWriteObject(obj, this);

                bout.setBlockDataMode(false);

                bout.writeByte(TC_ENDBLOCKDATA);

            } finally {

                curContext.setUsed();

                curContext = oldContext;

                if (extendedDebugInfo) {

                    debugInfoStack.pop();

                }

            }

 

            curPut = oldPut;

        } else {

            defaultWriteFields(obj, slotDesc);

        }

    }

}

首先会调用hasWriteObjectMethod()方法判断有没有自定义的writeObject(),代码如下

1

2

3

boolean hasWriteObjectMethod() {

    return (writeObjectMethod != null);

}

hasWriteObjectMethod()这个方法仅仅是判断writeObjectMethod是不是等于null,而上面说了,如果用户自定义了void writeObject(ObjectOutputStream oos)这么个方法,则writeObjectMethod不为null,在if()代码块中会调用slotDesc.invokeWriteObject(obj, this);方法,该方法中会调用用户自定义的writeObject()方法。

好书分享:Control and Estimation with MATLAB(5th Edition)pdf 基于MATLAB的控制与估计》(第5版)由八章组成,包括控制算法和设计。主要内容包括状态估计和数据平滑、RLS和卡尔曼滤波器状态估计、控制系统设计、自适应控制和PID。书中的概念已经使用MATLAB和Simulink实现。提供了可下载的MATLAB和Simulink文件。适合人群:大学生、研究生、控制类工程研究人员。需要电子版pdf,回复索取。 阅读详情

相关推荐

合并数组与非合并数组——SystemVerilog

合并型数组(packed): — 合并型数组可以实现连续的存储,赋值时不需要用 '{ }。 //合并型数组 bit [3] [7:0] b_pack // 表示3行 x 8列的数组,[3]是高维度(高维度),[7:0]是低维度(第一维) 数组中,数据排列为{b_pack[3],b_pack[2],b_pack[1],b_pack[0]},其中每个b_pack为8个bit; bit是二值逻辑,每位bit只占据1位。故24位bit只占据一个word(一般一个word为32bit)的存储空间

SummerXRT的博客 7638

初探Java反序列化漏洞

一、基本概念 Java序列化:就是将内存中的Java对象转换为字节序列的过程,可以理解为对Java对象打个快照。通过序列化,可以方便将Java对象保存在内存、文件、数据库等媒介中,也便于在网络中传输和共享Java对象。 Java反序列化:就是Java序列化的逆过程,将字节序列恢复为Java对象的过程。 序列化/反序列化并不是Java语言的独有特性,像PHP、Python、Ruby等动态语言也有类似的特性。序列化/反序列化的主要目的是: 1、远程过程调用(RPC):为不同系统或不同进程之间提供Java对象数据

秋̶᭄ꦿ攻城狮࿆ྂ 809

【VSCode】【Clangd】从零构建:基于LLVM/Clangd+MINGW+CMake的Windows高性能C/C++开发环境实战

本文详细介绍了如何在Windows平台搭建基于LLVM/Clangd+MINGW+CMake的高性能C/C++开发环境。通过VSCode配置Clangd语言服务器,结合MINGW提供的GCC工具链和CMake的跨平台构建能力,实现轻量级、高效率的开发体验。文章包含从环境准备到实战配置的完整指南,特别适合追求开发效率和跨平台兼容性的开发者。

weixin_28839629的博客 311

深入学习 Java 序列化

2019独角兽企业重金招聘Python工程师标准>>> ...

weixin_34362991的博客 209

PSIM联合VS--C++实现正弦波发生

在利用VS联合PSIM仿真中,想要用VS构建一个正弦波发生模块。可以利用Sin函数进行编写,重点是需要编写一个计数器程序。首先打算用for循环语句来构建计数器,但在for循环运行时无法跳出,不能实时获得计数器的值。同理,while循环也无法实现。最后将count变量设置为static型,解决了这一问题。 体现出对于c++程序的运行不够了解,需要多进行程序的编写和调试。 //时钟计数器 static int count = 0; count++; if (count > Sinfre)

weixin_41850228的博客 1087

AI图像去噪实战:5种最新深度学习模型在超声图像处理中的效果对比(附代码)

本文对比了2022至2024年间五种前沿深度学习模型在超声图像去噪领域的实战效果,包括FDDL-Net、MG-CNFNet、Dual-TranSpeckle、可控去噪模型及Wave-Mamba。文章深入剖析了各模型的核心创新与架构,并提供了可运行的代码片段,旨在帮助医学影像算法工程师根据不同的性能、速度与临床需求,选择最合适的AI图像去噪解决方案。

1a2s3d4f5g的博客 915

java的序列化机制原理分析2

  下面我们来分析下序列化后的字节流内容: 先写一段测试代码:     import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; im...

我的技术旅程 237

【GameFramework框架内置模块】2、数据节点(Data Node)

这一篇讲数据节点DataNode,基本开发程序都会有类似数据节点作用的东西,数据节点的作用其实就是保存游戏运行时的一些缓存数据,比如用户登录信息、用户名、任务节点等等。数据一般会有增删改查这四种操作,在运行时的数据一般是使用单例或者静态属性,将数据存储在内存中而数据节点也是将数据存储在内存中,但是不用序列化数据,比使用单例或者静态属性要容易维护的多,特别时它的树形结构可以让我们很好的组织我们的数据结构,方便做增删改查操作。数据节点DataNode模块的数据的存储方式是以树形结构存储。

优快云博客专家,Unity3D高级软件工程师,《Unity 3D从入门到实战》作者。 3184

java对象序列化

1、序列化是干什么的?       简单说就是为了保存在内存中的各种对象的状态(也就是实例变量,不是方法),并且可以把保存的对象状态再读出来。虽然你可以用你自己的各种各样的方法来保存object states,但是Java给你提供一种应该比你自己好的保存对象状态的机制,那就是序列化。       当两个进程在进行远程通信时,彼此可以发送各种类型的数据。无论是何种类型的数据,都会以二进制序列的形

honghailiang的专栏 650

博图SCL基础知识-寻址调用及新建SCL

和LAD/FBD不同,LAD/FBD在程序编辑器是一段一段的,编辑器可以插入新的网络段,每一个网络段可以有各自的注释。一般情况下,FB的简单数据类型形参可以没有对应实参,复杂数据类型的输入、输出也可以没有对应实参,所以FB可以隐藏或不隐藏不出现的形参。一般情况下,FB的简单数据类型形参可以没有对应实参,复杂数据类型的输入、输出也可以没有对应实参,所以FB可以隐藏或不隐藏不出现的形参。如图3所示,当FB的参数全部显示,在背景数据块右键可以激活"仅显示分配的参数";如图2所示,显示了一些FB调用的例子。

电工 2029

基于SVD矩阵分解的用户购物篮预测分析实战

基于SVD矩阵分解的用户购物篮预测分析实战

360

(六)java.io.ObjectStreamConstants接口详解

简介:写入对象序列化流中的常量; 1.final static short STREAM_MAGIC = (short)0xaced; 分析:写入流头的魔法数字; 2.final static short STREAM_VERSION = 5; 分析:写入流标头的版本号; 3.final static byte TC_BASE = 0x70; 分析:第一个标签值; 4.final s...

OceanSky的专栏 1121

RH850从0搭建Autosar开发环境【2】- Davinci Configurator配置工程导入DBC与CDD文件

Davinci Configurator配置工程导入DBC与CDD文件,开始Autosar开发

weixin_42412049的博客 8151

java序列化指的是什么,java的序列化是什么

序列化:指堆内存中的java对象数据,通过某种方式把对存储到磁盘文件中,或者传递给其他网络节点(网络传输)。这个过程称为序列化,通常是指将数据结构或对象转化成二进制的过程。 (推荐学习:java课程)即将对象转化为二进制,用于保存,或者网络传输。序列化的作用① 想把内存中的对象保存到一个文件中或者数据库中时候;② 想用套接字在网络上传送对象的时候;③ 想通过RMI传输...

weixin_39820226的博客 2019

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

【Java】Java序列化和反序列化

为了序列化对象,我们调用ObjectOutputStream类的writeObject()方法,而为了反序列化,则调用ObjectInputStream类的readObject()方法。SerializeISA类已序列化扩展了Person类的Student类对象,而Person类是可序列化的。序列化和反序列化过程是平台无关的,这意味着您可以在一个平台上对对象进行序列化,在另一个平台上进行反序列化。现在,id将不会序列化,因此在序列化之后反序列化对象时,您将不会得到id的值。它是序列化的逆操作。

double222222的博客 1142

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

什么是java序列化,如何实现java序列化?

Java序列化:把Java对象转换为二进制的数据流流。  反序列化就是把字节序列恢复为Java对象的过程。      实现:将需要序化的类实现Serializable接口就可以了,该接口没有任何方法,可以理解为一个标记,即表明这个类可以序列化。注意的是被关键字static、transient修饰的变量不能被序列化。在被序列化后,transient修饰的变量会被设为初始值。如int型的是0、对象型

m_h7769的博客 2396

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

从Java序列化到Hadoop序列化

一个原生的类讲解一下,自定义比较器讲解一下,内部类讲解一下。WritableComparator是 WritableComparables的比较器。这个基础实现类使用是自然的排序规则,如果要使用其他的排序规则,需要重写compare(WritableComparable,WritableComparable)方法。

Jenny19992018的博客 590

什么是Java序列化,如何实现java序列化

序列化解释: 序列化就是一种用来处理对象流的机制,所谓对象流也就是将对象的内容进行流化。可以对流化后的对象进行读写操作,也可将流化后的对象传输于网络之间。   序列化是为了解决在对对象流进行读写操作时所引发的问题。序列化的实现:将需要被序列化的类实现Serializable接口,该接口没有需要实现的方法,implements Serializable只是为了标注该对象是可被序列化的, 然后使用一个输出流(如:FileOutputStream)来构造一个ObjectOutputStream(...

qq_38087131的博客 652

java序列化 反序列化_Java序列化– Java序列化

java序列化 反序列化Serialization in Java was introduced in JDK 1.1 and it is one of the important feature of Core Java. Java的序列化是JDK 1.1中引入的,它是Core Java的重要功能之一。 Java序列化 (Serialization in Java) Serializati...

从零开始的教程世界 191

什么是Java序列化,如何实现序列化

1 序列化概念 序列化就是一个把Java对象编码成一串二进制的过程 序列化的用途:可以将对象放入文件存储、或者放入网络传输 反序列化就是一个解码的过程 序列化没有固定的标准,有各种各样序列化的方法 Java自带序列化接口 创建一个User类 在main方法里创建User对象,通过ObjectOutputStream把对象写入到文件中 编译报错 因为该对象不能被序列化 让...

qq_39581763的博客 370

什么是 java 序列化,如何实现 java 序列化

Java序列化是将对象的状态转换为字节流的过程,这样对象的状态可以被存储在文件、数据库中,或者通过网络传输给另一个Java虚拟机(JVM)。反序列化是相反的过程,即从字节流中重建对象的状态。

weixin_45990682的博客 610
上一篇: 一条进程的栈区、堆区、数据区和代码区在内存中的映射
下一篇: 哈希表
蓝猫_虹
博客等级 码龄12年 49粉丝 43原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值