java序列化与反序列化

java序列化与反序列化

  • 这几天在学习一下Java基本知识,看到ArrayList的源码的时候,ArrayList内部是由数组实现的,ArrayList类内部有个属性,transient Object[] elementData; 第一次见到由transient关键字修饰的类的属性,以往见到transient是在实际开发中实体类的属性带有@transient注解,带有此注解的属性不会被持久化到数据库。

  • @Transient表示该属性并非一个到数据库表的字段的映射,ORM框架将忽略该属性.在项目查了下,mongodb中确实没有此字段,因此也适用于mongodb。
    
  • 百度了一下java transient关键字:当对象被序列化时(写入字节序列到目标文件)时,transient阻止实例中那些用此关键字声明的变量持久化;当对象被反序列化时(从源文件读取字节序列进行重构),这样的实例变量值不会被持久化和恢复。

  • 看到这里我就有一个疑惑,既然ArrayList中的元素都在这个elementData数组中存放,难道ArrayList不需要序列化和反序列化吗?显然这是不可能的,由此我去查看了Java有关序列化和反序列化的相关知识。

1. 什么是Java序列化和反序列化

序列化:把对象转换为字节序列的过程称为对象的序列化。
反序列化:把字节序列恢复为对象的过程称为对象的反序列化。
对象序列化的目标是将对象保存到磁盘中,或允许在网络中直接传输对象。对象序列化机制允许把内存中Java对象转换成平台无关的二进制流,从而允许把这种二进制流持久地保存在磁盘上,通过网络将这种二进制流传输到另一个网络节点。其他程序一旦获得了这种二进制流,都可以将这种二进制流恢复成原来的Java对象。对象序列化机制是Java语言内建的一中对象持久化方式,通过对象序列化,可以把对象保存为字节数组,这些字节数组可以保存在磁盘上,或通过网络传输。在有需要的时候将这个字节数组通过反序列化的方式再转换成Java对象。对象序列话可以很容易的在JVM中的活动对象和字节数组(流)之间进行转换。在Java中,对象的序列化与反序列化被广泛的应用到远程方法调用的网络传输中。

2.Java如何实现序列化与反序列化

java.io类库中提供了一个接口,叫Serializable,看一下这个接口的描述

Serializability of a class is enabled by the class
implementing the java.io.Serializable interface. Classes that do not implement this interface will not have any of their state serialized or deserialized. All subtypes of a serializable class are themselves serializable. The serialization interface has no methods or fields and serves only to identify the semantics of being
serializable.

意思是类的可序列化操作是可以有类实现Serializable接口来实现, 未实现此接口的类将不会将其任何状态序列化或反序列化。 可序列化类的所有子类型本身都是可序列化的。 序列化接口没有方法或字段,仅用于标识存在的语义序列化。
java.io中还提供了一个接口叫做Externalizable,此接口继承了Serializable接口,

Only the identity of the class of an Externalizable instance is written in the serialization stream and it is the responsibility of the class to save and restore the contents of its instances.

只有Externalizable实例的类的标识才会写入序列化流中,类的责任是保存和恢复其实例的内容。
总的来说,Java对象的序列化与反序列化与接口Serializable密不可分。下面来探究一下具体的实现方式。

java.io.ObjectOutputStream:对象输出流,他的writeObject(Object obj)方法可以对参数指定的的对象obj进行序列化,把得到的字节序列写入到一个目标输出流中去。
java.io.ObjectInputStream:对象输入流,它的readObject()方法从源输入流中读取字节序列,再把他们发序列化为一个对象,并将其返回。

只有实现了Serializable或Externalizable接口的类的对象才能被序列化,否则抛出异常。
假设有一个Student类:
1.若Student类仅仅实现了Serializable接口,那么Student对象在序列化和反序列化时,会采用默认的方式。即ObjectOutputStream采用默认的序列化方式,对Student对象中的非transient成员变量进行序列化。ObjectInputStream采用默认的反序列化方式,对Student对象中的非transient成员变量进行反序列化。
2.若Student类实现了Serializable,且在类中定义了writeObject()和readObject()方法,则采用以下方式进行序列化和反序列化:
ObjectOutputStream调用Student对象中的writeObject()方法对Student对象进行序列化,ObjectInputStream调用Student对象中的readObject()方法对Student对象进行序列化。我们熟悉的ArrayList就是采用这种方式进行序列化和反序列化的。
3.若Student类实现了Externalnalizable接口,且Student类必须实现readExternal(ObjectInput in)和writeExternal(ObjectOutput out)方法,则按照以下方式进行序列化与反序列化,ObjectOutputStream调用Student对象的writeExternal(ObjectOutput out))的方法进行序列化 ,ObjectInputStream会调用Student对象的readExternal( ObectInputStream)进行反序列化。
下面用一个简单的例子模拟以下JDk中Student类的序列化与反序列化过程

package serializable;

import java.io.Serializable;

public class Student implements Serializable{

    /**

        *     每一次去修改该类的时候都会生成一个新的序列化标识的值

        *   需要重新读,这是基本方法。想办法来固定该类的标识ID,人为设定。

        *   这样即使再次修改类的内容,只要ID固定了就可以保证,在读取的时候一直是匹配的。

      * 

      */



     private static final long serialVersionUID = 
7017720472098045390L;

    
     private Long id;

     private String yearsOld;

     private String name;

     transient String grade;

    //省略getter,setter方法及构造函数   

}
import java.io.FileInputStream;

import java.io.FileOutputStream;

import java.io.IOException;

import java.io.ObjectInputStream;

import java.io.ObjectOutputStream;



public class TestSerializable {

     public static void main(String[] args) throws IOException, ClassNotFoundException {

          Student stu = new Student(10l,"20岁","Jack","三年级");

          //序列化

          FileOutputStream fos= new FileOutputStream("E:\\object.out");

          ObjectOutputStream oos = new ObjectOutputStream(fos);

          oos.writeObject(stu);

          oos.flush();

          oos.close();

          //反序列化

          FileInputStream fis = new FileInputStream("E:\\object.out");

          ObjectInputStream ois = new ObjectInputStream(fis);

          Student stu1 = (Student)ois.readObject();

          ois.close();

          System.out.println(stu1.getGrade()+""+stu1.getName());

     }

}


运行结果 : nullJack
可以看出被transient修饰的属性是不会被序列化和反序列化的,变为默认值。
注意事项:

  1. 序列化时,只对对象的状态进行保存,而不管对象的方法。
  2. 父类实现了Serializable,子类不需要再显式实现此接口。
  3. 如果一个对象的成员变量是一个对象,那么这个对象的数据成员也会被保存!这是能用序列化解决深拷贝的重要原因当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;
  4. 声明为static和transient类型的成员数据不能被序列化。因为static代表类的状态,transient代表对象的临时数据。
  5. 如果一个对象的成员变量是一个对象,那么这个对象的数据成员也会被保存!这是能用序列化解决深拷贝的重要原因

现在我们可以试着探究一下ArrayList的序列化与反序列化:
ArrayList的属性 : transient Object[] elementData,显然在ArrayList序列化的时候不会保存该属性,那ArrayList中的元素是如何序列化的呢?翻看源码,发现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();

        }

    }

 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();

            }

        }

    }


这样来看,ArrayList的序列化与反序列化采用的是第二种方式,我们来看一下这两个方法的具体实现:

// Write out all elements in the proper order.

        for (int i=0; i<size; i++) {

            s.writeObject(elementData[i]);

        }

对于被关键字transient修饰的elementData数组,ArrayList并没有序列化数组本身,而是逐一序列化其中的元素,这样解决了上面的问题。但是看到这里,心里有个疑问,既然要序列化,为何多此一举呢?仔细回想ArrayList的自动扩容机制,elementData数组相当于容器,容器容量不足便会扩容,所以容器的容量往往是大于容器内元素的个数的,这便造成了内存浪费。比如现在有8个元素,而容器的容量可能是8x1.5=16。当元素个数较大时,这种浪费就较为可观了。

参考:

  1. 那些鲜为人知的序列化和反序列化底层实现原理!
  2. 序列化和反序列化的概念,作用的通俗易懂的解释
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值