摘要:從的序列化和反序列化說起序列化是將對象的狀態(tài)信息轉(zhuǎn)換為可以存儲(chǔ)或傳輸?shù)男问降倪^程,而相反的過程就稱為反序列化。當(dāng)使用接口來進(jìn)行序列化與反序列化的時(shí)候需要開發(fā)人員重寫與方法。
從java的序列化和反序列化說起
序列化 (Serialization)是將對象的狀態(tài)信息轉(zhuǎn)換為可以存儲(chǔ)或傳輸?shù)男问降倪^程,而相反的過程就稱為反序列化。
在java中允許我們創(chuàng)建可復(fù)用的對象,但是這些對象僅僅存在jvm的堆內(nèi)存中,有可能被垃圾回收器回收掉而消失,也可能隨著jvm的停止而消失,但是有的時(shí)候我們希望這些對象被持久化下來,能夠在需要的時(shí)候重新讀取出來。比如我們需要在網(wǎng)絡(luò)中傳輸對象,首先就需要把對象序列化二進(jìn)制,然后在網(wǎng)絡(luò)中傳輸,接收端收到這些二進(jìn)制數(shù)據(jù)后進(jìn)行反序列化還原成對象,完成對象的網(wǎng)絡(luò)傳輸,java的序列化和反序列化功能就可以幫助我們現(xiàn)實(shí)此功能。
那么java要怎么樣才能實(shí)現(xiàn)序列化和反序列化呢?
Serializable接口
在java中要實(shí)現(xiàn)序列化和和反序列化只需要實(shí)現(xiàn)Serializable接口,任何視圖將沒有實(shí)現(xiàn)此接口的對象進(jìn)行序列化和反序列化操作都會(huì)拋出NotSerializableException,下面是實(shí)現(xiàn):
publicbyte[] serializer(T obj) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = null; try { oos = new ObjectOutputStream(baos); oos.writeObject(obj); } catch (IOException e) { logger.error("java序列化發(fā)生異常:{}",e); throw new RuntimeException(e); }finally{ try { if(oos != null)oos.close(); } catch (IOException e) { logger.error("java序列化發(fā)生異常:{}",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反序列化發(fā)生異常:{}",e); throw new RuntimeException(e); }finally{ try { ois.close(); } catch (IOException e) { logger.error("java反序列化發(fā)生異常:{}",e); throw new RuntimeException(e); } } }
transient關(guān)鍵字
正常情況下,在序列化過程中,對象里面的屬性都會(huì)被序列化,但是有的時(shí)候,我們想過濾掉某個(gè)屬性不要被序列化,該怎么辦呢,很簡單java給我們提供了一個(gè)關(guān)鍵字來實(shí)現(xiàn):transient,只要被transient關(guān)鍵字修飾了,就會(huì)被過濾掉
readObject和writeObject方法
在序列化過程中,如果被序列化的類中定義了writeObject 和 readObject 方法,虛擬機(jī)會(huì)試圖調(diào)用對象類里的 writeObject 和 readObject 方法,進(jìn)行用戶自定義的序列化和反序列化。
如果沒有這樣的方法,則默認(rèn)調(diào)用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。
用戶自定義的 writeObject 和 readObject 方法可以允許用戶控制序列化的過程,比如可以在序列化的過程中動(dòng)態(tài)改變序列化的數(shù)值。
細(xì)心的你肯定也發(fā)現(xiàn)了,我們在序列化的類里面定于了這兩個(gè)方法,但是并沒有顯式的調(diào)用這兩個(gè)方法,那到底是誰調(diào)用的,又是何時(shí)被調(diào)用的呢?
深入ByteArrayOutputStream類源碼會(huì)發(fā)現(xiàn)其調(diào)用棧:
ObjectOutputStream.writeObject(Object obj)----------->writeObject0(Object obj, boolean unshared)----------->writeOrdinaryObject(Object obj,ObjectStreamClass desc,boolean unshared)----------->writeSerialData(Object obj, ObjectStreamClass desc)
在writeSerialData方法里面會(huì)先獲取序列化類里面是否有writeObject(ObjectOutputStream out),有就會(huì)反射的調(diào)用,沒有就執(zhí)行默認(rèn)的序列化方法defaultWriteFields(obj, slotDesc)。
ByteArrayInputStream也是同樣的原理。
如果您讀過在ArrayList的源碼,你可能會(huì)發(fā)現(xiàn)在ArrayList中的字段elementData被關(guān)鍵字transient修飾了,而elementData字段是ArrayList存儲(chǔ)元素的,難道ArrayList存儲(chǔ)的元素序列化會(huì)被忽略嗎?但是你會(huì)發(fā)現(xiàn)并沒有被忽略,而是能正常的序列化和反序列化,這是為什么呢?答案就是,ArrayList寫有上面提到的readObject和writeObject兩個(gè)方法,ArrayList實(shí)際上是動(dòng)態(tài)數(shù)組,每次在放滿以后自動(dòng)增長設(shè)定的長度值,如果數(shù)組自動(dòng)增長長度設(shè)為50,而實(shí)際只放了1個(gè)元素,那就會(huì)序列化49個(gè)null元素。為了保證在序列化的時(shí)候不會(huì)將這么多null同時(shí)進(jìn)行序列化,ArrayList把元素?cái)?shù)組設(shè)置為transient,自定義序列化過程,這樣可以優(yōu)化存儲(chǔ)。
Externalizable接口
除了Serializable 之外,java中還提供了另一個(gè)序列化接口Externalizable,繼承了Serializable,該接口中定義了兩個(gè)抽象方法:writeExternal()與readExternal()。當(dāng)使用Externalizable接口來進(jìn)行序列化與反序列化的時(shí)候需要開發(fā)人員重寫writeExternal()與readExternal()方法。
序列化ID
虛擬機(jī)是否允許反序列化,不僅取決于類路徑和功能代碼是否一致,一個(gè)非常重要的一點(diǎn)是兩個(gè)類的序列化 ID 是否一致(就是 private static final long serialVersionUID)
序列化 ID 在 Eclipse 下提供了兩種生成策略,一個(gè)是固定的 1L,一個(gè)是隨機(jī)生成一個(gè)不重復(fù)的 long 類型數(shù)據(jù)(實(shí)際上是使用 JDK 工具生成),在這里有一個(gè)建議,如果沒有特殊需求,就是用默認(rèn)的 1L 就可以,這樣可以確保代碼一致時(shí)反序列化成功。那么隨機(jī)生成的序列化 ID 有什么作用呢,有些時(shí)候,通過改變序列化 ID 可以用來限制某些用戶的使用。
Protobuf
我們知道java自帶的序列化效率是非常低的,因?yàn)樗蛄谢傻淖止?jié)數(shù)非常多(包含了很多類的信息),不太適合用于存儲(chǔ)和在網(wǎng)絡(luò)上傳輸,下面來介紹下google給我們提供一個(gè)序列化效率相當(dāng)高的框架protobuff,比起java原生的序列化出來的字節(jié)數(shù)小十幾倍。那么它是如何做到的呢?
以int類型為例,int在java的占用4個(gè)字節(jié),如果我們不做特殊處理,int類型的值轉(zhuǎn)化成二進(jìn)制也需要占用4個(gè)字節(jié)的空間,但是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了,如果此時(shí)相與的值等于0,說明value的值不會(huì)大于0x7F=127,就可以用一個(gè)字節(jié)來表示,大大節(jié)省了字節(jié)數(shù),看個(gè)列子:
value=0x00000067,轉(zhuǎn)換成二進(jìn)制:
0000 0000 0000 0000 0000 0000 0110 0111
& 1111 1111 1111 1111 1111 1111 1000 0000
= 0000 0000 0000 0000 0000 0000 0000 0000
此時(shí)value & ~0x7F=0,當(dāng)把value強(qiáng)制轉(zhuǎn)換成byte類型時(shí),int會(huì)被截?cái)啵皇O碌臀蛔止?jié),于是當(dāng)value值小于128時(shí),序列化后的字節(jié)就變成:0110 0111,一個(gè)字節(jié)就可以表示了。
問題來了,如果value的值大于0x7F呢,接著看(value & 0x7F) | 0x80這句代碼,假設(shè)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
這個(gè)過程意思就是獲取value的最低位字節(jié),把這個(gè)字節(jié)的最高位置為1,表示后面還有可讀字節(jié)。
對0x000000C0強(qiáng)轉(zhuǎn)byte類型就變成:1100 0000,然后向右移7位:
0000 0000 0000 0000 0000 0000 0001 0001
重復(fù)上面的步驟,得到0001 0001,循環(huán)結(jié)束,最后得到:
1100 0000 0001 0001
2個(gè)字節(jié)就可以表示2240了,但是此時(shí)你會(huì)發(fā)現(xiàn)我們每次向右移動(dòng)的是7位,移4次才能表示28位,但是int要占用32位,如果value的值比較大,假如等于2147483647,那么這是就需要5個(gè)字節(jié)來表示,綜上所述protobuff表示一個(gè)int類型的值就不會(huì)固定4個(gè)字節(jié),而是用1-5個(gè)字節(jié)動(dòng)態(tài)來表示;那么你可能又會(huì)有疑問了,5個(gè)字節(jié)來表示一個(gè)int,字節(jié)數(shù)不是變多了么?其實(shí)從概率角度來看,我們業(yè)務(wù)上不能可能每一個(gè)int值都是一個(gè)非常大的值,所以還是可以為我們節(jié)省非常大的字節(jié)空間。同理long,double,float也是同樣的原理。下面就以proptobuff3來介紹下protobuff的使用
一、整備
從protobuff官網(wǎng)下載protoc.exe可執(zhí)行文件
二、編寫proto文件,具體的語法參見官網(wǎng)文檔
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; mapproperties = 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的類
執(zhí)行以上命令就會(huì)在指定的目錄生成一個(gè)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()); }
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/76591.html
摘要:引語平時(shí)我們在運(yùn)行程序的時(shí)候創(chuàng)建的對象都在內(nèi)存中當(dāng)程序停止或者中斷了對象也就不復(fù)存在了如果我們能將對象保存起來在需要使用它的時(shí)候在拿出來使用就好了并且對象的信息要和我們保存時(shí)的信息一致序列化就可以解決了這樣的問題序列化當(dāng)然不止一種方式如下序 引語: ????平時(shí)我們在運(yùn)行程序的時(shí)候,創(chuàng)建的對象都在內(nèi)存中,當(dāng)程序停止或者中斷了,對象也就不復(fù)存在了.如果我們能將對象保存起來,在需要使用它的...
摘要:序列化對象和平臺無關(guān),序列化得到的字節(jié)流可以在任何平臺反序列化。從文件中或網(wǎng)絡(luò)上獲得序列化的字節(jié)流后,根據(jù)字節(jié)流中所保存的對象狀態(tài)及描述信息,通過反序列化重建對象。因此意味著不要序列化靜態(tài)變量不屬于對象狀態(tài)的一部分,因此它不參與序列化。 一.序列化和反序列化(1)序列化:將內(nèi)存中的對象轉(zhuǎn)化為字節(jié)序列,用于持久化到磁盤中或者通過網(wǎng)絡(luò)傳輸。對象序列化的最主要的用處就是傳遞和保存對象,保證對...
摘要:把字節(jié)序列恢復(fù)為對象的過程稱為對象的反序列化。代表對象輸入流,它的方法從一個(gè)源輸入流中讀取字節(jié)序列,再把它們反序列化為一個(gè)對象,并將其返回。接口繼承自接口,實(shí)現(xiàn)接口的類完全由自身來控制序列化的行為,而僅實(shí)現(xiàn)接口的類可以采用默認(rèn)的序列化方式。 把對象轉(zhuǎn)換為字節(jié)序列的過程稱為對象的序列化。把字節(jié)序列恢復(fù)為對象的過程稱為對象的反序列化。 對象的序列化主要有兩種用途: 1) 把...
摘要:序列化反序列化主要體現(xiàn)在程序這個(gè)過程中,包括網(wǎng)絡(luò)和磁盤。如果是開發(fā)應(yīng)用,一般這兩個(gè)注解對應(yīng)的就是序列化和反序列化的操作。協(xié)議的處理過程,字節(jié)流內(nèi)部對象,就涉及這兩種序列化。進(jìn)行第二步操作,也就是序列化和反序列化的核心是。 以下內(nèi)容,如有問題,煩請指出,謝謝! 對象的序列化/反序列化大家應(yīng)該都比較熟悉:序列化就是將object轉(zhuǎn)化為可以傳輸?shù)亩M(jìn)制,反序列化就是將二進(jìn)制轉(zhuǎn)化為程序內(nèi)部的...
摘要:虛擬機(jī)讀取其他進(jìn)程的數(shù)據(jù)對象的方法可以運(yùn)行平臺上的其他程序該方法產(chǎn)生一個(gè)對象對象代表由該程序啟動(dòng)啟動(dòng)的子進(jìn)程類提供如下三個(gè)方法用于和其子進(jìn)程通信獲取子進(jìn)程的錯(cuò)誤流獲取子進(jìn)程的輸入流獲取子進(jìn)程的輸出流這里的輸入流輸出流容易混淆從程序的角度思考 Java虛擬機(jī)讀取其他進(jìn)程的數(shù)據(jù) Runtime對象的exec方法可以運(yùn)行平臺上的其他程序,該方法產(chǎn)生一個(gè)Process對象,Process對象...
閱讀 3243·2021-11-24 10:43
閱讀 4205·2021-11-24 10:33
閱讀 3782·2021-11-22 09:34
閱讀 2134·2021-10-11 10:58
閱讀 3754·2021-10-11 10:58
閱讀 866·2021-09-27 13:36
閱讀 3585·2019-08-30 15:54
閱讀 2974·2019-08-29 18:41