国产xxxx99真实实拍_久久不雅视频_高清韩国a级特黄毛片_嗯老师别我我受不了了小说

資訊專欄INFORMATION COLUMN

從java的序列化和反序列化說起

whlong / 1648人閱讀

摘要:從的序列化和反序列化說起序列化是將對象的狀態信息轉換為可以存儲或傳輸的形式的過程,而相反的過程就稱為反序列化。當使用接口來進行序列化與反序列化的時候需要開發人員重寫與方法。

從java的序列化和反序列化說起

序列化 (Serialization)是將對象的狀態信息轉換為可以存儲或傳輸的形式的過程,而相反的過程就稱為反序列化。

在java中允許我們創建可復用的對象,但是這些對象僅僅存在jvm的堆內存中,有可能被垃圾回收器回收掉而消失,也可能隨著jvm的停止而消失,但是有的時候我們希望這些對象被持久化下來,能夠在需要的時候重新讀取出來。比如我們需要在網絡中傳輸對象,首先就需要把對象序列化二進制,然后在網絡中傳輸,接收端收到這些二進制數據后進行反序列化還原成對象,完成對象的網絡傳輸,java的序列化和反序列化功能就可以幫助我們現實此功能。

那么java要怎么樣才能實現序列化和反序列化呢?

Serializable接口

在java中要實現序列化和和反序列化只需要實現Serializable接口,任何視圖將沒有實現此接口的對象進行序列化和反序列化操作都會拋出NotSerializableException,下面是實現:

public  byte[] serializer(T obj) {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    ObjectOutputStream oos = null;
    try {
        oos = new ObjectOutputStream(baos);
        oos.writeObject(obj);
    } catch (IOException e) {
        logger.error("java序列化發生異常:{}",e);
        throw new RuntimeException(e);
    }finally{
        try {
            if(oos != null)oos.close();
        } catch (IOException e) {
            logger.error("java序列化發生異常:{}",e);
        }
    }
    return baos.toByteArray();
}

public  T deserializer(byte[] data, Class clazz) {
    ByteArrayInputStream bais = new ByteArrayInputStream(data);
    ObjectInputStream ois = null;
    try {
        ois = new ObjectInputStream(bais);
        return (T)ois.readObject();
    } catch (Exception e) {
        logger.error("java反序列化發生異常:{}",e);
        throw new RuntimeException(e);
    }finally{
        try {
            ois.close();
        } catch (IOException e) {
            logger.error("java反序列化發生異常:{}",e);
            throw new RuntimeException(e);
        }
    }
}

transient關鍵字

正常情況下,在序列化過程中,對象里面的屬性都會被序列化,但是有的時候,我們想過濾掉某個屬性不要被序列化,該怎么辦呢,很簡單java給我們提供了一個關鍵字來實現:transient,只要被transient關鍵字修飾了,就會被過濾掉

readObject和writeObject方法

在序列化過程中,如果被序列化的類中定義了writeObject 和 readObject 方法,虛擬機會試圖調用對象類里的 writeObject 和 readObject 方法,進行用戶自定義的序列化和反序列化。

如果沒有這樣的方法,則默認調用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。

用戶自定義的 writeObject 和 readObject 方法可以允許用戶控制序列化的過程,比如可以在序列化的過程中動態改變序列化的數值。

細心的你肯定也發現了,我們在序列化的類里面定于了這兩個方法,但是并沒有顯式的調用這兩個方法,那到底是誰調用的,又是何時被調用的呢?

深入ByteArrayOutputStream類源碼會發現其調用棧:

ObjectOutputStream.writeObject(Object obj)----------->writeObject0(Object obj, boolean unshared)----------->writeOrdinaryObject(Object obj,ObjectStreamClass desc,boolean unshared)----------->writeSerialData(Object obj, ObjectStreamClass desc)

在writeSerialData方法里面會先獲取序列化類里面是否有writeObject(ObjectOutputStream out),有就會反射的調用,沒有就執行默認的序列化方法defaultWriteFields(obj, slotDesc)。

ByteArrayInputStream也是同樣的原理。

如果您讀過在ArrayList的源碼,你可能會發現在ArrayList中的字段elementData被關鍵字transient修飾了,而elementData字段是ArrayList存儲元素的,難道ArrayList存儲的元素序列化會被忽略嗎?但是你會發現并沒有被忽略,而是能正常的序列化和反序列化,這是為什么呢?答案就是,ArrayList寫有上面提到的readObject和writeObject兩個方法,ArrayList實際上是動態數組,每次在放滿以后自動增長設定的長度值,如果數組自動增長長度設為50,而實際只放了1個元素,那就會序列化49個null元素。為了保證在序列化的時候不會將這么多null同時進行序列化,ArrayList把元素數組設置為transient,自定義序列化過程,這樣可以優化存儲。

Externalizable接口

除了Serializable 之外,java中還提供了另一個序列化接口Externalizable,繼承了Serializable,該接口中定義了兩個抽象方法:writeExternal()與readExternal()。當使用Externalizable接口來進行序列化與反序列化的時候需要開發人員重寫writeExternal()與readExternal()方法。

序列化ID

虛擬機是否允許反序列化,不僅取決于類路徑和功能代碼是否一致,一個非常重要的一點是兩個類的序列化 ID 是否一致(就是 private static final long serialVersionUID)

序列化 ID 在 Eclipse 下提供了兩種生成策略,一個是固定的 1L,一個是隨機生成一個不重復的 long 類型數據(實際上是使用 JDK 工具生成),在這里有一個建議,如果沒有特殊需求,就是用默認的 1L 就可以,這樣可以確保代碼一致時反序列化成功。那么隨機生成的序列化 ID 有什么作用呢,有些時候,通過改變序列化 ID 可以用來限制某些用戶的使用。

Protobuf

我們知道java自帶的序列化效率是非常低的,因為它序列化生成的字節數非常多(包含了很多類的信息),不太適合用于存儲和在網絡上傳輸,下面來介紹下google給我們提供一個序列化效率相當高的框架protobuff,比起java原生的序列化出來的字節數小十幾倍。那么它是如何做到的呢?

以int類型為例,int在java的占用4個字節,如果我們不做特殊處理,int類型的值轉化成二進制也需要占用4個字節的空間,但是protobuff卻不是這樣做的,請看下面代碼:

while (true) {
    if ((value & ~0x7F) == 0) {
        UnsafeUtil.putByte(buffer, position++, (byte) value);
        break;
    } else {
        UnsafeUtil.putByte(buffer, position++, (byte) ((value & 0x7F) | 0x80));
        value >>>= 7;
    }
}

value & ~0x7F 是什么意思呢?0x7F取反跟value相與,那么value的低7位全部被置0了,如果此時相與的值等于0,說明value的值不會大于0x7F=127,就可以用一個字節來表示,大大節省了字節數,看個列子:

value=0x00000067,轉換成二進制:

0000 0000  0000 0000  0000 0000  0110 0111

& 1111 1111 1111 1111 1111 1111 1000 0000

= 0000 0000 0000 0000 0000 0000 0000 0000

此時value & ~0x7F=0,當把value強制轉換成byte類型時,int會被截斷,只剩下低位字節,于是當value值小于128時,序列化后的字節就變成:0110 0111,一個字節就可以表示了。

問題來了,如果value的值大于0x7F呢,接著看(value & 0x7F) | 0x80這句代碼,假設value=2240,

 0000 0000  0000 0000  0000 1000  1100 0000     0x000008C0

& 0000 0000 0000 0000 0000 0000 0111 1111 0x0000007F

= 0000 0000 0000 0000 0000 0000 1100 0000 0x000000C0

| 0000 0000 0000 0000 0000 0000 1000 0000 0x00000080

= 0000 0000 0000 0000 0000 0000 1100 0000 0x000000C0

這個過程意思就是獲取value的最低位字節,把這個字節的最高位置為1,表示后面還有可讀字節。

對0x000000C0強轉byte類型就變成:1100 0000,然后向右移7位:

0000 0000 0000 0000 0000 0000 0001 0001

重復上面的步驟,得到0001 0001,循環結束,最后得到:

1100 0000 0001 0001

2個字節就可以表示2240了,但是此時你會發現我們每次向右移動的是7位,移4次才能表示28位,但是int要占用32位,如果value的值比較大,假如等于2147483647,那么這是就需要5個字節來表示,綜上所述protobuff表示一個int類型的值就不會固定4個字節,而是用1-5個字節動態來表示;那么你可能又會有疑問了,5個字節來表示一個int,字節數不是變多了么?其實從概率角度來看,我們業務上不能可能每一個int值都是一個非常大的值,所以還是可以為我們節省非常大的字節空間。同理long,double,float也是同樣的原理。下面就以proptobuff3來介紹下protobuff的使用

一、整備

從protobuff官網下載protoc.exe可執行文件

二、編寫proto文件,具體的語法參見官網文檔

syntax = "proto3";
option java_package = "com.yanghui.serialize.protobuf3";
option java_outer_classname = "PersonModule";
message Person {
    int32 age = 1;
    int64 time = 2;
    string name = 3;
    map properties = 4;
}

三、編譯成java類

e:/study/protobuf/bin/protoc.exe -I=D:/workspace/serialize/src/main/java/com/yanghui/serialize/protobuf3 --java_out=D:/workspace/serialize/src/main/java person.proto

-I:表示proto文件所在目錄

--java_out:表示輸出java的類

執行以上命令就會在指定的目錄生成一個java類PersonModule.java,接下來就可以使用了

@Test
public void testProtobuffSerialize() throws InvalidProtocolBufferException {
    Builder builder = PersonModule.Person.newBuilder();
    builder.setAge(21);
    builder.setTime(100L);
    builder.setName("yanghui");
    builder.putProperties("key1", "value1");
    com.yanghui.serialize.protobuf3.PersonModule.Person person = builder.build();

    byte[] personBytes = person.toByteArray();
    System.out.println(Arrays.toString(personBytes));
    System.out.println(personBytes.length);

    com.yanghui.serialize.protobuf3.PersonModule.Person p = 
        com.yanghui.serialize.protobuf3.PersonModule.Person.parseFrom(personBytes);
    System.out.println(p.toString());
}


文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。

轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/76591.html

相關文章

  • java列化和反列化

    摘要:引語平時我們在運行程序的時候創建的對象都在內存中當程序停止或者中斷了對象也就不復存在了如果我們能將對象保存起來在需要使用它的時候在拿出來使用就好了并且對象的信息要和我們保存時的信息一致序列化就可以解決了這樣的問題序列化當然不止一種方式如下序 引語: ????平時我們在運行程序的時候,創建的對象都在內存中,當程序停止或者中斷了,對象也就不復存在了.如果我們能將對象保存起來,在需要使用它的...

    snowell 評論0 收藏0
  • java對象列化和反列化

    摘要:序列化對象和平臺無關,序列化得到的字節流可以在任何平臺反序列化。從文件中或網絡上獲得序列化的字節流后,根據字節流中所保存的對象狀態及描述信息,通過反序列化重建對象。因此意味著不要序列化靜態變量不屬于對象狀態的一部分,因此它不參與序列化。 一.序列化和反序列化(1)序列化:將內存中的對象轉化為字節序列,用于持久化到磁盤中或者通過網絡傳輸。對象序列化的最主要的用處就是傳遞和保存對象,保證對...

    chadLi 評論0 收藏0
  • Java 列化和反列化

    摘要:把字節序列恢復為對象的過程稱為對象的反序列化。代表對象輸入流,它的方法從一個源輸入流中讀取字節序列,再把它們反序列化為一個對象,并將其返回。接口繼承自接口,實現接口的類完全由自身來控制序列化的行為,而僅實現接口的類可以采用默認的序列化方式。 把對象轉換為字節序列的過程稱為對象的序列化。把字節序列恢復為對象的過程稱為對象的反序列化。    對象的序列化主要有兩種用途:   1) 把...

    jcc 評論0 收藏0
  • springboot學習(三)——使用HttpMessageConverter進行http列化和反

    摘要:序列化反序列化主要體現在程序這個過程中,包括網絡和磁盤。如果是開發應用,一般這兩個注解對應的就是序列化和反序列化的操作。協議的處理過程,字節流內部對象,就涉及這兩種序列化。進行第二步操作,也就是序列化和反序列化的核心是。 以下內容,如有問題,煩請指出,謝謝! 對象的序列化/反序列化大家應該都比較熟悉:序列化就是將object轉化為可以傳輸的二進制,反序列化就是將二進制轉化為程序內部的...

    stackfing 評論0 收藏0
  • Java IO (三) 讀取其他進程數據,RandomAccessFile,列化和反列化

    摘要:虛擬機讀取其他進程的數據對象的方法可以運行平臺上的其他程序該方法產生一個對象對象代表由該程序啟動啟動的子進程類提供如下三個方法用于和其子進程通信獲取子進程的錯誤流獲取子進程的輸入流獲取子進程的輸出流這里的輸入流輸出流容易混淆從程序的角度思考 Java虛擬機讀取其他進程的數據 Runtime對象的exec方法可以運行平臺上的其他程序,該方法產生一個Process對象,Process對象...

    zhangfaliang 評論0 收藏0

發表評論

0條評論

whlong

|高級講師

TA的文章

閱讀更多
最新活動
閱讀需要支付1元查看
<