zoukankan      html  css  js  c++  java
  • 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方法进行反序列化。

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

    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();
          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是不会被序列化的:

    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 + " ]";
    	}
    }
    

      

    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.

    我们来看另外一种情况:

    class A implements Serializable{
    	B b;
    	//...
    }
    
    class B implements Serializable{
    	//...
    }
    

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

    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)
          ....

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

    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)优化序列化的性能(之前的那个例子已经解释了这种情况)

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

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

    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()方法在单例模式中的使用:

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

    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

    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)。

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

    例子如下:

    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]

     参考资料:

    1.java序列化高级认识


    ==============================================================================

    本博客已经废弃,不在维护。新博客地址:http://wenchao.ren


    我喜欢程序员,他们单纯、固执、容易体会到成就感;面对压力,能够挑灯夜战不眠不休;面对困难,能够迎难而上挑战自我。他
    们也会感到困惑与傍徨,但每个程序员的心中都有一个比尔盖茨或是乔布斯的梦想“用智慧开创属于自己的事业”。我想说的是,其
    实我是一个程序员

    ==============================================================================
  • 相关阅读:
    编译安装zabbix3.2
    编译安装PHP的参数 --with-mysql --with-mysqli --with-apxs2默认路径
    MySql中指定符号分割并分行展示
    Linux中vim编辑器常用命令
    Ubuntu中安装配置 JDK与apache
    Ubuntu中python链接本地数据库
    oracle函数笔记(1)
    Oracle的五种约束
    斐波那契数列
    python计算圆面积
  • 原文地址:https://www.cnblogs.com/rollenholt/p/2789445.html
Copyright © 2011-2022 走看看