`
wb284551926
  • 浏览: 554117 次
文章分类
社区版块
存档分类
最新评论

java序列化和反序列话总结(转载)

 
阅读更多

序列化:将java对象转换为字节序列的过程叫做序列化

反序列化:将字节对象转换为java对象的过程叫做反序列化

通常情况下,序列化有两种用途:、

1) 把对象的字节序列永久的保存在硬盘中

2)在网络上传输对象的字节序列

相应的API

  java.io.ObjectOutputStream

          writeObject(Object obj)

  java.io.ObjectInputStream

          readObject()

只有实现了Serializable或者Externalizable接口的类的对象才能够被序列化。否则当调用writeObject方法的时候会出现IOException。

需要注意的是Externalizable接口继承自Serializable接口。两者的区别如下:

  仅仅实现Serializable接口的类可应采用默认的序列化方式。比如String类。

    假设有一个Customer类的对象需要序列化,如果这个类仅仅实现了这个接口,那么序列化和反序列化的方式如下:ObjectOutputStream采用默认的序列化方式,对于这个类的非static,非transient的实例变量进行序列化。ObjectInputStream采用默认的反序列化方式,对于这个类的非static,非transient的实例变量进行反序列化。

    如果这个类不仅实现了Serializable接口,而且定义了readObject(ObjectInputStream in)和 writeObject(ObjectOutputStream out)方法,那么将按照如下的方式进行序列化和反序列化:ObjectOutputStream会调用这个类的writeObject方法进行序列化,ObjectInputStream会调用相应的readObject方法进行反序列化。

  实现Externalizable接口的类完全由自身来控制序列化的行为。而且必须实现writeExternal(ObjectOutput out)和readExternal(ObjectInput in)。那么将按照如下的方式进行序列化和反序列化:ObjectOutputStream会调用这个类的writeExternal方法进行序列化,ObjectInputStream会调用相应的readExternal方法进行反序列化。

下面来看一个最简单的例子:

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
49
package com.java;
 
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
public class simpleSerializableTest {
    public static void main(String[] args) throws Exception {
        ObjectOutputStream out=new ObjectOutputStream(new FileOutputStream("d:\\objectFile.obj"));
         
        String strObj="name";
        Customer customer=new Customer("rollen");
        //序列化,此处故意将同一对象序列化2次
        out.writeObject(strObj);
        out.writeObject(customer);
        out.writeObject(customer);
        out.close();
        //反序列化
        ObjectInputStream in=new ObjectInputStream(new FileInputStream("d:\\objectFile.obj"));
        String strobj1=(String)in.readObject();
        Customer cus1=(Customer)in.readObject();
        Customer cus2=(Customer)in.readObject();<br>            in.close();
        System.out.println(strobj1+": "+cus1);
        System.out.println(strObj==strobj1);
        System.out.println(cus1==customer);
        System.out.println(cus1==cus2);
    }
}
 
class Customer implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
 
    public Customer() {
        System.out.println("无参构造方法");
    }
 
    public Customer(String name) {
        System.out.println("有参构造方法");
        this.name = name;
    }
 
    public String toString() {
        return "[ "+name+" ]";
    }
     
}

输出结果为:

有参构造方法
name: [ rollen ]
false
false
true

可以看出,在进行反序列话的时候,并没有调用类的构造方法。而是直接根据他们的序列化数据在内存中创建新的对象。另外需要注意的是,如果由一个ObjectOutputStream对象多次序列化同一个对象,那么右一个objectInputStream对象反序列化后的也是同一个对象。(cus1==cus2结果为true可以看出)

看一段代码,证明static是不会被序列化的:

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
49
50
51
52
53
54
55
package com.java;
 
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.net.ServerSocket;
import java.net.Socket;
 
public class SerializableServer {
    public void send(Object obj) throws IOException {
        ServerSocket serverSocket = new ServerSocket(8000);
        while (true) {
            Socket socket = serverSocket.accept();
            ObjectOutputStream out = new ObjectOutputStream(
                    socket.getOutputStream());
            out.writeObject(obj);
            out.writeObject(obj);
            out.close();
            socket.close();
        }
    }
 
    public static void main(String[] args) throws Exception {
 
        Customer customer = new Customer("rollen", "male");
        new SerializableServer().send(customer);
    }
}
 
class Customer implements Serializable {
    private static final long serialVersionUID = 1L;
    private String name;
    private static int count;
    private transient String sex;
 
    static {
        System.out.println("调用静态代码块");
    }
 
    public Customer() {
        System.out.println("无参构造方法");
    }
 
    public Customer(String name, String sex) {
        System.out.println("有参构造方法");
        this.name = name;
        this.sex = sex;
        count++;
 
    }
 
    public String toString() {
        return "[ " + count + " " + name + " " + sex + " ]";
    }
}

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package com.java;
 
import java.io.ObjectInputStream;
import java.net.Socket;
 
public class SerializableClient {
    public void recive() throws Exception {
        Socket socket = new Socket("localhost", 8000);
        ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
        Object obj1 = in.readObject();
        Object obj2 = in.readObject();
        System.out.println(obj1);
        System.out.println(obj1==obj2);
    }
 
    public static void main(String[] args) {
        try {
            new SerializableClient().recive();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

  运行结果中,count的值为0.

我们来看另外一种情况:

1
2
3
4
5
6
7
8
class A implements Serializable{
    B b;
    //...
}
 
class B implements Serializable{
    //...
}

  当我们在序列化A的对象的时候,也会自动序列化和他相关联的B的对象。也就是说在默认的情况下,对象输出流会对整个对象图进行序列化。因此会导致出现下面的问题,看代码(例子中是使用双向列表作为内部结构的,只是给出了demo,并没有完整的实现,只是为了说明情况):

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
49
50
51
52
53
54
55
56
57
58
59
60
package com.java;
 
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
public class SeriListTest implements Serializable {
    private static final long serialVersionUID = 1L;
    private int size;
    private Node head = null;
    private Node end = null;
 
    private static class Node implements Serializable {
        private static final long serialVersionUID = 1L;
        String data;
        Node next;
        Node previous;
    }
 
    // 列表末尾添加一个字符串
    public void add(String data) {
        Node node = new Node();
        node.data = data;
        node.next = null;
        node.previous = end;
        if (null != end) {
            end.next = node;
        }
        size++;
        end = node;
        if (size == 1) {
            head = end;
        }
    }
 
    public int getSize() {
        return size;
    }
 
    // other methods...
 
    public static void main(String[] args) throws Exception {
        SeriListTest list = new SeriListTest();
        for (int i = 0; i < 10000; ++i) {
            list.add("rollen" + i);
        }
 
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(buf);
        out.writeObject(list);
 
        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
                buf.toByteArray()));
        list = (SeriListTest) in.readObject();
        System.out.println("size is :" + list.getSize());
    }
 
}

  这段代码会出现如下错误:

Exception in thread "main" java.lang.StackOverflowError
  at java.lang.ref.ReferenceQueue.poll(ReferenceQueue.java:82)
  at java.io.ObjectStreamClass.processQueue(ObjectStreamClass.java:2234)
      ....

整个就是因为序列化的时候,对整个对象图进行序列化引起的问题。在这种情况下啊,我们需要自定义序列化的过程:

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
package com.java;
 
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
public class SeriListTest implements Serializable {
    private static final long serialVersionUID = 1L;
    transient private int size;
    transient private Node head = null;
    transient private Node end = null;
 
    private static class Node implements Serializable {
        private static final long serialVersionUID = 1L;
        String data;
        Node next;
        Node previous;
    }
 
    // 列表末尾添加一个字符串
    public void add(String data) {
        Node node = new Node();
        node.data = data;
        node.next = null;
        node.previous = end;
        if (null != end) {
            end.next = node;
        }
        size++;
        end = node;
        if (size == 1) {
            head = end;
        }
    }
 
    public int getSize() {
        return size;
    }
 
    // other methods...
 
    private void writeObject(ObjectOutputStream outStream) throws IOException {
        outStream.defaultWriteObject();
        outStream.writeInt(size);
        for (Node node = head; node != null; node = node.next) {
            outStream.writeObject(node.data);
        }
    }
 
    private void readObject(ObjectInputStream inStream) throws IOException,
            ClassNotFoundException {
        inStream.defaultReadObject();
        int count = inStream.readInt();
        for (int i = 0; i < count; ++i) {
            add((String) inStream.readObject());
        }
    }
 
    public static void main(String[] args) throws Exception {
        SeriListTest list = new SeriListTest();
        for (int i = 0; i < 10000; ++i) {
            list.add("rollen" + i);
        }
 
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(buf);
        out.writeObject(list);
 
        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
                buf.toByteArray()));
        list = (SeriListTest) in.readObject();
        System.out.println("size is :" + list.getSize());
    }
 
}

  运行结果为:10000

现在我们总结一下,在什么情况下我们需要自定义序列化的方式:

  1)为了确保序列化的安全性,对于一些敏感信息加密

  2)确保对象的成员变量符合正确的约束条件

  3)优化序列化的性能(之前的那个例子已经解释了这种情况)

下面我们来用例子解释一下这些:

先来看看:为了确保序列化的安全性,对于一些敏感信息加密

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
49
50
51
52
53
54
55
56
package com.java;
 
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
public class SeriDemo1 implements Serializable {
    private String name;
    transient private String password; // 注意此处的transient
 
    public SeriDemo1() {
    }
 
    public SeriDemo1(String name, String password) {
        this.name = name;
        this.password = password;
    }
 
    // 此处模拟对密码进行加密,进行了简化
    private String change(String password) {
        return password + "rollen";
    }
 
    private void writeObject(ObjectOutputStream outStream) throws IOException {
        outStream.defaultWriteObject();
        outStream.writeObject(change(password));
    }
 
    private void readObject(ObjectInputStream inStream) throws IOException,
            ClassNotFoundException {
        inStream.defaultReadObject();
        String strPassowrd = (String) inStream.readObject();
        //此处模拟对密码解密
        password = strPassowrd.substring(0, strPassowrd.length() - 6);
    }
 
    @Override
    public String toString() {
        return "SeriDemo1 [name=" + name + ", password=" + password + "]";
    }
 
    public static void main(String[] args) throws Exception {
        SeriDemo1 demo = new SeriDemo1("hello", "1234");
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(buf);
        out.writeObject(demo);
 
        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
                buf.toByteArray()));
        demo = (SeriDemo1) in.readObject();
        System.out.println(demo);
    }
}

  然后我们看看:确保对象的成员变量符合正确的约束条件。比如一般情况下我们会在构造函数中对于参数进行合法性检查,但是默认的序列化并不会调用类的构造函数,直接由对象的序列化数据来构造出一个对象,这个我们就有可能提供遗传非法的序列化数据,来构造一个不满足约束条件的对象。

为了避免这种情况,我们可以自定义反序列话的方式。比如在readObject方法中,进行检查。当数据不满足约束的时候(比如年龄小于0等等不满足约束的情况),可以抛出异常之类的。

接下来我们看看readResolve()方法在单例模式中的使用:

单例模式大家应该都清楚,我就不多说了,看看下面的代码:

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
package com.java;
 
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
public class ReadResolveDemo implements Serializable {
    private static final long serialVersionUID = 1L;
 
    private ReadResolveDemo() {
    }
 
    public static ReadResolveDemo getInstance() {
        return new ReadResolveDemo();
    }
    public static void main(String[] args) throws Exception {
        ReadResolveDemo demo=ReadResolveDemo.getInstance();
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(buf);
        out.writeObject(demo);
 
        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
                buf.toByteArray()));
        ReadResolveDemo demo1 = (ReadResolveDemo) in.readObject();
        System.out.println(demo==demo1); //false
    }
}

  本来单例模式中,只有一个实例,但是在序列化的时候,无论采用默认的方式,还是自定义的方式,在反序列化的时候都会产生一个新的对象,所以上面的程序运行输出false。

因此可以看出反序列化打破了单例模式只有一个实例的约定,为了避免这种情况,我们可以使用readReslove

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
package com.java;
 
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
 
public class ReadResolveDemo implements Serializable {
    private static final long serialVersionUID = 1L;
    private static final ReadResolveDemo INSTANCE = new ReadResolveDemo();
 
    private ReadResolveDemo() {
    }
 
    public static ReadResolveDemo getInstance() {
        return INSTANCE;
    }
 
    private Object readResolve() {
        return INSTANCE;
    }
 
    public static void main(String[] args) throws Exception {
        ReadResolveDemo demo = ReadResolveDemo.getInstance();
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(buf);
        out.writeObject(demo);
 
        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
                buf.toByteArray()));
        ReadResolveDemo demo1 = (ReadResolveDemo) in.readObject();
        System.out.println(demo == demo1); // true
    }
}

  最后我们简单的说一下实现Externalizable接口。 实现Externalizable接口的类完全由自身来控制序列化的行为。而且必须实现writeExternal(ObjectOutput out)和readExternal(ObjectInput in)。

注意在对实现了这个接口的对象进行反序列化的时候,会先调用类的不带参数的构造函数,这个和之前的默认反序列化方式是不一样的。

例子如下:

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
49
50
51
52
53
54
package com.java;
 
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.Externalizable;
import java.io.IOException;
import java.io.ObjectInput;
import java.io.ObjectInputStream;
import java.io.ObjectOutput;
import java.io.ObjectOutputStream;
 
public class ExternalizableDemo implements Externalizable {
    private String name;
    static {
        System.out.println("调用静态代码块");
    }
 
    public ExternalizableDemo() {
        System.out.println("调用默认无参构造函数");
    }
 
    public ExternalizableDemo(String name) {
        this.name = name;
        System.out.println("调用有参构造函数");
    }
 
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(name);
    }
 
    @Override
    public void readExternal(ObjectInput in) throws IOException,
            ClassNotFoundException {
        name = (String) in.readObject();
    }
 
    @Override
    public String toString() {
        return "[" + name + "]";
    }
 
    public static void main(String[] args) throws Exception {
        ExternalizableDemo demo = new ExternalizableDemo("rollen");
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        ObjectOutputStream out = new ObjectOutputStream(buf);
        out.writeObject(demo);
 
        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(
                buf.toByteArray()));
        demo = (ExternalizableDemo) in.readObject();
        System.out.println(demo);
    }
}

  输出:

调用静态代码块
调用有参构造函数
调用默认无参构造函数
[rollen]

 

原帖地址:http://www.cnblogs.com/rollenholt/archive/2012/11/26/2789445.html

分享到:
评论

相关推荐

    浅谈java中为什么实体类需要实现序列化

    浅谈java中为什么实体类需要实现序列化 在Java中,实体类需要实现序列化是因为序列化是将对象转换为字节流的过程,而...因此,在设计实体类时,需要考虑实现Serializable接口,以便在需要时可以将其序列化和反序列化。

    [转载] JAXB中Java对象和XML之间的转换

    JAXB允许我们将Java类与XML Schema定义的元素和类型绑定,从而实现XML的序列化和反序列化。 在Java对象和XML之间的转换过程中,JAXB主要涉及到以下几个关键概念: 1. **Java类与XML Schema绑定**: JAXB使用`@...

    java 时间转换(转载)

    9. **日期时间的序列化和反序列化**: 在JSON序列化时,可能需要将日期时间转换为字符串,可以使用`Jackson`库的`@JsonFormat`注解来指定序列化格式。 在提供的`Test.java`文件中,可能包含了对上述知识点的实际...

    深入分析 Java I/O 的工作机制(转载)

    ObjectInputStream和ObjectOutputStream负责对象的序列化和反序列化操作。 7. **NIO(非阻塞I/O)** Java 1.4引入了NIO(New I/O)框架,提供了一种更有效率的I/O模型,特别是在多路复用I/O(Selector)方面。NIO...

    Java利用Preferences设置个人偏好,转载自:燕窝

    Preferences API默认只支持字符串,但可以通过序列化和反序列化将其他类型的对象转换为字符串进行存储。例如,可以使用`ObjectOutputStream`和`ObjectInputStream`来处理非字符串数据。 3. **安全性和权限**: ...

    Castor学习笔记 (转载)

    它通过XML Schema或XML Mapping文件定义Java类与XML元素之间的映射关系,实现了XML数据的自动序列化和反序列化。 2. XML绑定与映射文件 在Castor中,映射文件通常以`.xml`扩展名,如`mapping.xml`,定义了Java类与...

    com.fasterxml.jackson

    使用databind模块,直接对Java对象进行序列化和反序列化 通常来说,我们在日常开发中使用的是第3种方式,有时为了简便也会使用第2种方式,比如你要从一个很大的Json对象中只读取那么一两个字段的时候,采用databind...

    java编程事项(转载收集整理版)

    6. **输入/输出(I/O)**:Java的I/O流系统包括字符流和字节流,以及用于文件操作、网络通信和对象序列化的API。学会使用BufferedReader、FileWriter等类进行文件操作,是开发中常见的需求。 7. **网络编程**:Java...

    dwr中文教程~转载的,共享一下

    1. **AjaxEngine**:这是DWR的核心,它负责处理JavaScript到Java的通信,包括序列化和反序列化、错误处理等。 2. **Caucho Hessian/Burlap**:DWR可以使用Hessian或Burlap作为传输协议,它们是轻量级的二进制/文本...

    java编写建议(转载)

    - 实现`Serializable`接口以支持序列化。 #### 二、主方法与程序入口 **2.1 主方法** - 每个可执行的Java程序都应包含一个`main`方法作为入口点。 - 避免删除源代码中的`main`方法,即使在调试过程中也不应该移除...

    转载 xStream完美转换XML、JSON

    xStream是一个强大的库,它提供了一种简单的方式来序列化和反序列化Java对象到XML,反之亦然。在这个场景中,它同样能够处理JSON转换,虽然JSON不是其原生支持的格式,但通过一些额外的配置和库,如json-simple,...

    tomcat使用memcached集群session共享(转载并修改)

    该工具有多种序列化策略,例如 msm-javolution-serializer,用于将 Java 对象转换为可在网络上传输的字节序列。 下面是实现 Tomcat 使用 Memcached 集群进行 Session 共享的详细步骤: 1. **安装和配置 Memcached*...

    deeplearning4j.zip (自己封装,deeplearning4j和apache的资源)欢迎转载

    Deeplearning4j是中国开发者社区基于Java开发的一款深度学习库,它旨在使Java和Scala的开发者能够方便地进行机器学习,特别是在深度学习领域的应用。这个压缩包“deeplearning4j.zip”包含了开发者封装的资源,结合...

    javac的词法分析,转载自:百度文库

    **Java编译器 javac 的词法分析** Java编译器`javac`是Java开发不可或...通过识别关键字、标识符、字面量等,`javac`为解析和理解Java程序的结构打下基础。正确地进行词法分析对于保证代码的正确编译和执行至关重要。

    Android面试专题(七):Serializable 和 Parcelable 的区别

    不诗意的女程序媛不是好厨师~ 转载请注明出处,From李诗雨—...Serializable是序列化的意思,表示将一个对象转换成可存储或可传输的状态。序列化后的对象可以在网络上进行传输,也可以存储到本地。 Parcelable(androi

    WCF 分布式开发转载

    类中的属性通常带有`[DataMember]`特性,表示需要序列化。 2. **服务接口**: 服务接口定义了可供调用的操作,使用`[ServiceContract]`和`[OperationContract]`标记。操作方法通常是服务的实际业务逻辑。 3. **服务...

    word源码java-csdn-blogs:这是一个存储我的csdn博客的存储库

    4. 文件和I/O流操作:文件读写、缓冲流、对象序列化等。 5. 多线程编程:线程同步、线程池、并发工具类等。 6. 网络编程:Socket通信、HTTP请求等。 7. Java异常处理:自定义异常、异常链、try-catch-finally语句块...

    jfreechat 中文API

    提供的文件如`jfreechart 中文api_百度知道.mht`和`JFreeChart中文API[转载] - 新手上路,多多关照 - BlogJava.mht`可能包含更多实例、常见问题解答和社区讨论,对深入学习JFreeChart非常有帮助。 总之,JFreeChart...

Global site tag (gtag.js) - Google Analytics