天天看點

Java Serializable總結

一.序列化簡介

1.什麼是序列化

Java平台允許我們在記憶體中建立可複用的Java對象,但一般情況下,隻有當JVM處于運作時,這些對象才可能存在,即,這些對象的生命周期不會比JVM的生命周期更長。但在現實應用中,就可能要求在JVM停止運作之後能夠儲存(持久化)指定的對象,并在将來重新讀取被儲存的對象。Java對象序列化就能夠幫助我們實作該功能。

使用Java對象序列化,在儲存對象時,會把其狀态儲存為一組位元組,在未來,再将這些位元組組裝成對象。必須注意地是,對象序列化儲存的是對象的"狀态",即它的成員變量。由此可知,對象序列化不會關注類中的靜态變量。

除了在持久化對象時會用到對象序列化之外,當使用RMI(遠端方法調用),或在網絡中傳遞對象時,都會用到對象序列化。Java序列化API為處理對象序列化提供了一個标準機制,該API簡單易用,在本文的後續章節中将會陸續講到。

2.序列化和持久化

序列化 :  把對象轉存為二進制資料(如網絡傳輸,存儲資料庫等),必須實作序列化接口 (Java.io.Serializable).

持久化 :  把對象儲存在媒體上(如寫檔案,讀檔案不是), 沒有接口實作,一般指方法調用. 

解釋說明: 

       如果按照存儲媒體和生命周期的長短劃分,所有的資料都以兩種形式存在,其中:

         一種是儲存于記憶體中的運作時對象;另一種則是存儲于持久化實體媒體中的檔案,比如資料庫檔案等。

        資料的持久化關注于相同的資料在不同形态資料之間的轉化,解決的是如何将記憶體對象持久化存儲,以及從實體媒體中加載資料并建立記憶體對象。

        資料的持久化是序列化的又一個典型的應用,對象隻有在序列化之後才能進行持久化存儲,從持久化存儲媒體加載的資料通過反序列化轉變成運作時對象。

二.什麼情況需要序列化

a)當你想把的記憶體中的對象儲存到一個檔案中或者資料庫中時候;

b)當你想用套接字在網絡上傳送對象的時候;

c)當你想通過RMI傳輸對象的時候;

三.序列化和反序列化執行個體

java序列化主要使用ObjectOutputStream.write()方法,反序列化使用ObjectInputStream.read()方法。

package com.java.serializable.test2;

import java.io.*;

/**
 * Created by laobo on 2016/1/6.
 */
public class TestSerializable {

    public static void main(String[] arg) throws IOException, ClassNotFoundException {
        SerializablePerson();
        DeserializablePerson();
    }

    private static void SerializablePerson() throws IOException {
        Person person = new Person();
        person.setName("laobo");
        person.setAge(27);
        person.setSex("男");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(new File("/Users/duanxiangchao/java_test/person.txt")));
        objectOutputStream.writeObject(person);
        System.out.println("Person序列化成功!");
        objectOutputStream.close();
    }

    private static void DeserializablePerson() throws IOException, ClassNotFoundException {
        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(new File("/Users/duanxiangchao/java_test/person.txt")));
        Person person = (Person)objectInputStream.readObject();
        System.out.println("Person反序列化成功!");
        System.out.println("姓名:" + person.getName());
        System.out.println("性别:" + person.getSex());
        System.out.println("年齡:" + person.getAge());
    }

}
           
package com.java.serializable.test2;

import java.io.Serializable;

/**
 * Created by duanxiangchao on 2016/1/6.
 */
public class Person implements Serializable {

    private static final long serialVersionUID = -5809782578272943999L;

    private String name;
    private String sex;
    private Integer age;

    public String getName() {
        return name;
    }

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

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public Integer getAge() {
        return age;
    }

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

執行程式,會在java_test檔案夾下生成person.txt   

console輸出如下

Person序列化成功!
Person反序列化成功!
姓名:laobo
性别:男
年齡:27
           

四.序列化常見問題

1. Serializable的作用

    為什麼一個類實作了Serializable接口,它就可以被序列化呢?在上節的示例中,使用ObjectOutputStream來持久化對象,在該類中有如下代碼:

private void writeObject0(Object obj, boolean unshared)
        throws IOException
    {
        boolean oldMode = bout.setBlockDataMode(false);
        depth++;
        try {
            ......省略
            // remaining cases
            if (obj instanceof String) {
                writeString((String) obj, unshared);
            } else if (cl.isArray()) {
                writeArray(obj, desc, unshared);
            } else if (obj instanceof Enum) {
                writeEnum((Enum<?>) obj, desc, unshared);
            } else if (obj instanceof Serializable) {
                writeOrdinaryObject(obj, desc, unshared);
            } else {
                if (extendedDebugInfo) {
                    throw new NotSerializableException(
                        cl.getName() + "\n" + debugInfoStack.toString());
                } else {
                    throw new NotSerializableException(cl.getName());
                }
            }
        } finally {
            depth--;
            bout.setBlockDataMode(oldMode);
        }
    }
           

從上述代碼可知, 如果被寫對象的類型是String,或數組,或Enum,或Serializable,那麼就可以對該對象進行序列化,否則将抛出NotSerializableException。

2.預設序列化機制和transient關鍵字

預設機制:在序列化對象時,不僅會序列化目前對象本身,還會對該對象引用的其它對象也進行序列化,同樣地,這些其它對象引用的另外對象也将被序列化,以此類推。

       是以,如果一個對象包含的成員變量是容器類對象,而這些容器所含有的元素也是容器類對象,那麼這個序列化的過程就會較複雜,開銷也較大。

  transient關鍵字

      被transient修飾的屬性,不會序列化到指定的目标中。

      例如:上面Person中的屬性sex設定為 transient

transient private String sex;
           

則輸出結果為:

Person序列化成功!
Person反序列化成功!
姓名:laobo
性别:null
年齡:27
           

3.Java對象序列化成字元串和反序列化

     上邊的例子,是把對象序列化到檔案中;更多的時候,我們需要把序列化的對象儲存的資料庫或者緩存,這就需要把序列化的對象義字元串的形式傳回。這就需要用到ByteArrayOutputStream和ByteArrayInputStream作為ObjectOutputStream、ObjectInputStream的緩沖區,具體使用如下:

/**
 * Created by laobo on 16/11/10.
 */
public class SerializableToString {

    private static final String TEMP_ENCODING = "ISO-8859-1";
    private static final String DEFAULT_ENCODING = "UTF-8";

    public static void main(String[] args) {

        Person person = new Person("zhangsan", "男", 25);
        String str = writeToStr(person);
        System.out.println(str);

        Person person1 = (Person) writeToObject(str);
        System.out.println("姓名:" + person1.getName());
        System.out.println("性别:" + person1.getSex());
        System.out.println("年齡:" + person1.getAge());

    }

    private static String writeToStr(Object obj) {
        //使用ByteArrayOutputStream緩沖區接收序列化對象
        //緩沖區會随着資料的不斷寫入而自動增長;可使用toByteArray()和toString()擷取資料
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        //專門用于序列化對象的流
        ObjectOutputStream oos = null;
        String serializableStr = null;
        try {
            oos = new ObjectOutputStream(bos);
            oos.writeObject(obj);
            //使用ByteArrayOutputStream.toString()方法,傳回字元串
            serializableStr = bos.toString(TEMP_ENCODING);
            serializableStr = URLEncoder.encode(serializableStr, DEFAULT_ENCODING);
            return serializableStr;
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                oos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    private static Object writeToObject(String str) {
        ByteArrayInputStream bais = null;
        ObjectInputStream ois = null;

        try {
            String str1 = URLDecoder.decode(str, DEFAULT_ENCODING);
            byte[] bytes = str1.getBytes(TEMP_ENCODING);
            bais = new ByteArrayInputStream(bytes);
            ois = new ObjectInputStream(bais);
            Person person = (Person) ois.readObject();
            return person;
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                ois.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

}
           

4.Java序列化版本serialVersionUID的作用

       通常在需要序列化的類中回生命如下屬性

private static final long serialVersionUID = -5809782578272943999L;
           

       簡單來說,Java的序列化機制是通過在運作時判斷類的serialVersionUID來驗證版本一緻性的。在進行反序列化時,JVM會把傳來的位元組流中的serialVersionUID與本地相應實體(類)的serialVersionUID進行比較,如果相同就認為是一緻的,可以進行反序列化,否則就會出現序列化版本不一緻的異常。

      當實作java.io.Serializable接口的實體(類)沒有顯式地定義一個名為serialVersionUID,類型為long的變量時,Java序列化機制會根據編譯的class自動生成一個serialVersionUID作序列化版本比較用,這種情況下,隻有同一次編譯生成的class才會生成相同的serialVersionUID 。

      如果我們不希望通過編譯來強制劃分軟體版本,即實作序列化接口的實體能夠相容先前版本,未作更改的類,就需要顯式地定義一個名為serialVersionUID,類型為long的變量,不修改這個變量值的序列化實體都可以互相進行串行化和反串行化。 

      serialVersionUID主要來判斷不同版本的相容性。