Java序列化与反序列化

Java序列化与反序列化

    • 1. 什么是序列化?为什么要使用序列化?
    • 2. 使用序列化
      • 2.1 错误使用示例
      • 2.2 正确使用示例
      • 2.3 集合中序列化的使用
      • 2.4 正确使用进阶
    • 3. 总结

1. 什么是序列化?为什么要使用序列化?

序列化就是将对象的状态信息转换成可以存储或传输的过程。将对象当前状态存储起来。而反序列化就是从存储区将对象重新创建出来。

简单来说,序列化就是将对象转换成一个二进制字节流,方便保存到本地或进行网络传输。

2. 使用序列化

Java io库提供了两个流来实现对象的序列化与反序列化,ObjectOutputStream和ObjectInputStream。并且调用writeObject()和readObject()方法来进行序列化和反序列化

2.1 错误使用示例

//user没有实现serializable接口
public class User {

	private int age;
	private String name;
	private transient String number;
	public User(int age, String name, String number) {
		super();
		this.age = age;
		this.name = name;
		this.number = number;
	}
	
}
public class test1 {

	public static void main(String[] args) throws IOException {
		FileOutputStream out = new FileOutputStream("D:\\user");
		ObjectOutputStream os = new ObjectOutputStream(out);
		User user = new User(10, "zhangyi", "123456");
		os.writeObject(user);
	}
}

输出:User类没有进行序列化
Java序列化与反序列化_第1张图片

2.2 正确使用示例

  • 将要序列化的对象的类实现Serializable接口
  • 调用ObjectOutputStream.writeObject(user)进行序列化
  • 调用ObjectInputStream.readObject()进行反序列化
  • 如果某个属性不需要进行序列化,就可以用transient 修饰
//User类实现Serializable接口
public class User implements Serializable{

	private int age;
	private String name;
	private transient String number;
	public User(int age, String name, String number) {
		super();
		this.age = age;
		this.name = name;
		this.number = number;
	}
	@Override
	public String toString() {
		return "User [age=" + age + ", name=" + name + ", number=" + number + "]";
	}
	
}

public class test1 {

	public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException {
		FileOutputStream out = new FileOutputStream("D:\\user");
		ObjectOutputStream os = new ObjectOutputStream(out);
		User user = new User(10, "zhangyi", "123456");
		//序列化
		os.writeObject(user);
		
		new Thread().sleep(5);
		FileInputStream in = new FileInputStream("D:\\user");
		ObjectInputStream is = new ObjectInputStream(in);
		//反序列化
		User user2 = (User) is.readObject();
		System.out.println(user2);
	}
	
}

输出:
在这里插入图片描述

2.3 集合中序列化的使用

如果要序列化的对象的成员变量,包含了其他对象的引用,我们还需要对其他变量也进行序列化实现Serializable接口。

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    transient Object[] elementData;
}

所以在集合中,ArrayList、LinkedArrayList、HashMap、HashSet都实现了Serializable接口。但是ArrayList像保存数值的数组被transient修饰了,表示列表的数值不能被序列化,这岂不是矛盾了?

实际上集合中都实现了Serializable接口的writeObject方法和readObject方法。在writeObject方法中实际上进行了序列化,序列化了ArrayList中实际保存的元素,而不是数组容量大小的元素,数组容量大小大于实际保存的元素,所以这样减少了空间浪费。

    private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // Write out element count, and any hidden stuff
        int expectedModCount = modCount;
        s.defaultWriteObject();

        // Write out size as capacity for behavioural compatibility with clone()
        s.writeInt(size);

        // Write out all elements in the proper order.
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }

        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }
    }

    /**
     * Reconstitute the ArrayList instance from a stream (that is,
     * deserialize it).
     */
    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        elementData = EMPTY_ELEMENTDATA;

        // Read in size, and any hidden stuff
        s.defaultReadObject();

        // Read in capacity
        s.readInt(); // ignored

        if (size > 0) {
            // be like clone(), allocate array based upon size not capacity
            int capacity = calculateCapacity(elementData, size);
            SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, capacity);
            ensureCapacityInternal(size);

            Object[] a = elementData;
            // Read in all elements in the proper order.
            for (int i=0; i<size; i++) {
                a[i] = s.readObject();
            }
        }
    }

为什么还需要实现Serializable接口的writeObject方法和readObject方法?这与ObjectOutputStream.writeObject(user)和ObjectInputStream.readObject()有什么关系?

  • 实际上ObjectOutputStream在调用writeObject()方法进行序列化的时候,会通过反射在对象user中寻找方法名为writeObject,参数为ObjectOutputStream的方法
  • 如果找到了就会调用user.writeObject(os)的方法;没找到的话会使用默认的实现。

通过反射的方式,实现了序列化。所以这就是为什么在实体类中实现writeObject方法和readObject方法了,我们可以自己定义要序列化的成员变量。

2.4 正确使用进阶

  • 自己实现writeObject方法和readObject方法,我们只序列化age属性

所以通过重写writeObject方法和readObject方法,就可以自定义实现我们的序列化方式。而ObjectOutputStream是通过反射来实现调用User的这两个方法的。

public class User implements Serializable{

	private static final long serialVersionUID = -2658984781134384274L;
	private int age;
	private String name;
	private transient String number;
	public User(int age, String name, String number) {
		super();
		this.age = age;
		this.name = name;
		this.number = number;
	}
	
	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	@Override
	public String toString() {
		return "User [age=" + age + ", name=" + name + ", number=" + number + "]";
	}
	
	private void writeObject(java.io.ObjectOutputStream s) throws IOException {
		s.writeObject(age);
	}
	private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
		this.setAge((int)s.readObject());
	}
}

public class test1 {

	public static void main(String[] args) throws IOException, InterruptedException, ClassNotFoundException {
		FileOutputStream out = new FileOutputStream("D:\\user");
		ObjectOutputStream os = new ObjectOutputStream(out);
		User user = new User(10, "zhangyi", "123456");
		//序列化
		os.writeObject(user);
		
		new Thread().sleep(5);
		FileInputStream in = new FileInputStream("D:\\user");
		ObjectInputStream is = new ObjectInputStream(in);
		//反序列化
		User user2 = (User) is.readObject();
		System.out.println(user2);
	}
	
}

输出:从结果可以看出,只序列化了age
在这里插入图片描述

此外我们还可以看到在序列化类的时候有一个属性serialVersionUID,这是自动生成的。它是干什么的?
因为不同的编译器实现的不同,反序列化过程中可能会导致意外。所以serialVersionUID来保证不同编译器实现的一致性。

3. 总结

  • 其实我们使用spring mvc框架的时候,将数据转换成JSON格式传递到前台。其实也是一种序列化的方式
  • 在Socket通信中,将对象在客户端与服务端传输,就需要将对象进行序列化。
  • 序列化只是对原对象的一个拷贝,保持了原对象的属性状态值,但与原来的对象不是同一个。

你可能感兴趣的:(java基础)