java序列化學習

定義:
Java序列化是指把Java對象轉換爲字節序列的過程,保存在硬盤中;而Java反序列化是指把字節序列恢復爲Java對象的過程。

優點:
好處一是實現了數據的持久化,通過序列化可以把數據永久地保存到硬盤上(通常存放在文件裏),二是,利用序列化實現遠程通信,即在網絡上傳送對象的字節序列。

JDK類庫中序列化API

java.io.ObjectOutputStream:表示對象輸出流

它的writeObject(Object obj)方法可以對參數指定的obj對象進行序列化,把得到的字節序列寫到一個目標輸出流中。(將對象序列化保存到文件中)

java.io.ObjectInputStream:表示對象輸入流

它的readObject()方法源輸入流中讀取字節序列,再把它們反序列化成爲一個對象,並將其返回。

簡單例子

1、先創建一個枚舉類


public enum Gender {
    MALE,FEMALE
}

2、創建一個Person類。
注意:
如果被寫對象的類型是String,或數組,或Enum,或Serializable,那麼就可以對該對象進行序列化,否則將拋出NotSerializableException。
序列化是指對象的序列化,只會關注對象的屬性,不會關注類中的靜態變量

public class Person implements Serializable{
    private String name;

    private Integer age;

    private Gender gender;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

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

    public Gender getGender() {
        return gender;
    }

    public void setGender(Gender gender) {
        this.gender = gender;
    }

   public Person() {  
        System.out.println("none-arg constructor");  
    }  

    public Person(String name, Integer age, Gender gender) {  
        System.out.println("arg constructor");  
        this.name = name;  
        this.age = age;  
        this.gender = gender;  
    }  

    @Override 
    public String toString() {  
        return "[" + name + ", " + age + ", " + gender + "]";  
    } 

}

3、創建一個測試類:

public class SimpleSerialTest {

    public static void main(String[] args) {
        //新建一個文件
        File file = new File("person.out");

        try {
            ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(file));
            Person person = new Person("john", 21, Gender.MALE);
            oout.writeObject(person);
            oout.close();
            //但必須確保該讀取程序的CLASSPATH中包含有Person.class
            //(哪怕在讀取Person對象時並沒有顯示地使用Person類,如上例所示),否則會拋出ClassNotFoundException。
            ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));
            Object per = oin.readObject();
            oin.close();
            System.out.println(per.toString());


        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

上述程序的輸出結果:
arg constructor
[john, 21, MALE]

*此時必須注意的是,當重新讀取被保存的Person對象時,並沒有調用Person的任何構造器,看起來就像是直接使用字節將Person對象還原出來的。
當Person對象被保存到person.out文件中之後,我們可以在其它地方去讀取該文件以還原對象,但必須確保該讀取程序的CLASSPATH中包含有Person.class(哪怕在讀取Person對象時並沒有顯示地使用Person類,如上例所示),否則會拋出ClassNotFoundException。*

默認的序列化機制
一個類被序列化,不僅他本身被序列化,還會對該對象引用的其他對象也將被序列化,所以當一個類對象引用了某個容器類對象,而容器對象所包含的元素也是容器對象,當被序列化的時候,會出現將容器對象也序列化,此時會帶來很大的內存開銷。

在現實應用中,有些時候不能使用默認序列化機制。比如,希望在序列化過程中忽略掉敏感數據,或者簡化序列化過程。下面將介紹若干影響序列化的方法。如:transient關鍵字、writeObject()方法與readObject()方法指定那些屬性被序列化和輸出、Externalizable接口、readResolve()方法。

1、transient關鍵字
當某個字段被聲明爲transient後,默認序列化機制就會忽略該字段。

/**
 * 測試transient功能
 * @author DELL
 *
 */
public class Animal implements Serializable{
    private String name;

    private int age;

    //使用transient,目的是爲了避免敏感信息在序列化的時候
    transient private String getter;//持有者
    //測試static類熟悉是否被序列化
    public static int weight;

    public void setGetter(String getter) {
        this.getter = getter;
    }
    ......省略set,get,構造方法
    @Override
    public String toString() {
        return "Animal [name=" + name + ", age=" + age + ", getter=" + getter
                + "]";
    }
}

測試類

public class Test {
    public static void main(String[] args) {
        File fileAnimal = new File("animal.out");
        writeFile(fileAnimal, new Animal("niuniu", 3, "張三"));
        readFile(fileAnimal);
    }

    //序列化公共處理方法
    public static void writeFile(File file,Object obj){
        try {
            ObjectOutputStream oout = new ObjectOutputStream(new FileOutputStream(file));
            //Person person = new Person("zhangfei", 23, Gender.FEMALE);
            oout.writeObject(obj);
            oout.close();
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    //反序列化公共處理方法
    public static void readFile(File file){
        try {
            ObjectInputStream oin = new ObjectInputStream(new FileInputStream(file));
            Object obj =  oin.readObject();
            oin.close();
            System.out.println(obj.toString());
            //System.out.println(SinglePerson.getInstance() == ((SinglePerson)obj));
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

測試結果:
Animal [name=niuniu, age=3, getter=null]
結果可見:Animal類對象中被transient修飾的持有者未被序列化。

writeObject()方法與readObject()方法

public class Person implements Serializable{
    .......
    transient private Integer age;

    ......

    //如果使用了transient關鍵字,則該屬性在序列化時會被忽略,使用私有的writeObject,readObject方法,會讓被忽略的屬性,再次被序列化。
    private void writeObject(ObjectOutputStream outputStream){
        try {
            outputStream.defaultWriteObject();
            System.out.println("this is a flag");
            outputStream.writeInt(age);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }

    private void readObject(ObjectInputStream inputStream){
        try {
            inputStream.defaultReadObject();
            age = inputStream.readInt();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

測試代碼:

public class Test {
    public static void main(String[] args) {
        File file = new File("test.out");
        writeFile(file,new Person("zhangfei", 23, Gender.FEMALE));
        readFile(file);
    }
    ......
}

測試結果:
arg constructor
this is a flag
[zhangfei, 23, FEMALE]

writeObject()方法中會先調用ObjectOutputStream中的defaultWriteObject()方法,該方法會執行默認的序列化機制,如5.1節所述,此時會忽略掉age字段。然後再調用writeInt()方法顯示地將age字段寫入到ObjectOutputStream中。readObject()的作用則是針對對象的讀取,將值設置到屬性中,其原理與writeObject()方法相同

Externalizable接口
無論是使用transient關鍵字,還是使用writeObject()和readObject()方法,其實都是基於Serializable接口的序列化。JDK中提供了另一個序列化接口–Externalizable,使用該接口之後,之前基於Serializable接口的序列化機制就將失效。

/**
 * 使用Externalizable接口,調用的是無參構造方法構造對象。
 * @author DELL
 *
 */
public class Project implements Externalizable{

    private String projectName;

    private String PorjectLeader;

    private int teamPerson;//團隊人數
    ......
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {

    }

    @Override
    public void readExternal(ObjectInput in) throws IOException,
            ClassNotFoundException {

    }
    public Project() {
        System.out.println("this method had constructor");
    }
    @Override
    public String toString() {
        ......
    }
}

測試代碼:
public class Test {
public static void main(String[] args) {
File fileProject = new File(“project.out”);
writeFile(fileProject, new Project(“XXXXXX”, “xiaoming”, 6));
readFile(fileProject);
}
}
輸出結果:
this method had constructor
Project [projectName=null, PorjectLeader=null, teamPerson=0]

從該結果方面,一個字段都沒有被序列化,Externalizable繼承於Serializable,當使用該接口時,序列化的細節需要由程序員去完成。如上所示的代碼,由於writeExternal()與readExternal()方法未作任何處理,那麼該序列化行爲將不會保存/讀取任何一個字段。這也就是爲什麼輸出結果中所有字段的值均爲空。
另外,使用Externalizable進行序列化時,當讀取對象時,會調用被序列化類的無參構造器去創建一個新的對象,然後再將被保存對象的字段的值分別填充到新對象中。這就是爲什麼在此次序列化過程中Project類的無參構造器會被調用。由於這個原因,實現Externalizable接口的類必須要提供一個無參的構造器,且它的訪問權限爲public。

因此對上面的project類做進一步修改:

public class Project implements Externalizable{

    ......將writeExternal,readExternal進行補全。。
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        out.writeObject(projectName);
        out.writeInt(teamPerson);
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException,
            ClassNotFoundException {
        projectName = (String) in.readObject();
        teamPerson = in.readInt();
    }
    ......

再次執行Test類,執行結果如下:
this method had constructor
Project [projectName=xxxxx, PorjectLeader=null, teamPerson=6]

readResolve()方法

public class SinglePerson implements Serializable{

    private String name;

    private String address;

    private int age;

    private static class InstanceHolder{
        private static final SinglePerson person = new SinglePerson("jack", "tianfulu", 12);
    }

    public static SinglePerson getInstance(){
        return InstanceHolder.person;
    }
    ......
    @Override
    public String toString() {
        return "SinglePerson [name=" + name + ", address=" + address + ", age="
                + age + "]";
    }
}

測試代碼:

public class Test {
    public static void main(String[] args) {
        /*File file = new File("test.out");
        writeFile(file,new Person("zhangfei", 23, Gender.FEMALE));
        readFile(file);
        File fileAnimal = new File("animal.out");
        writeFile(fileAnimal, new Animal("niuniu", 3, "張三"));
        readFile(fileAnimal);

        File fileProject = new File("project.out");
        writeFile(fileProject, new Project("xxxxx", "xiaoming", 6));
        readFile(fileProject);*/

        File fileSinglePerson = new File("singlePerson.out");
        writeFile(fileSinglePerson, SinglePerson.getInstance());
        readFile(fileSinglePerson);

    }
    ......

        //反序列化公共處理方法
    public static void readFile(File file){
        try {
            .......
            //新增
            System.out.println(SinglePerson.getInstance() == ((SinglePerson)obj));
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    }

測試結果:
SinglePerson [name=jack, address=tianfulu, age=12]
false

新增readResolve方法

public class SinglePerson implements Serializable{

    private String name;

    private String address;

    private int age;

    ..........省略

    private Object readResolve() throws  ObjectStreamException{
        return SinglePerson.getInstance();
    }
}

再次執行測試代碼:
SinglePerson [name=jack, address=tianfulu, age=12]
true

無論是實現Serializable接口,或是Externalizable接口,當從I/O流中讀取對象時,readResolve()方法都會被調用到。實際上就是用readResolve()中返回的對象直接替換在反序列化過程中創建的對象。

參考文章:http://developer.51cto.com/art/201202/317181.htm
http://blog.csdn.net/wangloveall/article/details/7992448/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章