public class User { private int id; private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
SocketServer
public class SocketServer { public static void main(String[] args) { ServerSocket serverSocket=null; BufferedReader in=null; try{ serverSocket=new ServerSocket(8080); Socket socket=serverSocket.accept(); ObjectInputStream objectInputStream=new ObjectInputStream(socket.getInputStream()); User user=(User)objectInputStream.readObject(); System.out.println(user); }catch (Exception e){ e.printStackTrace(); }finally { if(in != null){ try { in.close(); } catch (IOException e) { e.printStackTrace(); } } if(serverSocket != null){ try { serverSocket.close(); } catch (IOException e) { e.printStackTrace(); } } } }
SocketClientConsumer
public class SocketClientConsumer { public static void main(String[] args) { Socket socket = null; ObjectOutputStream out = null; try { socket = new Socket("127.0.0.1", 8080); User user = new User(); out = new ObjectOutputStream(socket.getOutputStream()); out.writeObject(user); } catch (IOException e) { e.printStackTrace(); } finally { if (out != null) { try { out.close(); } catch (IOException e) { e.printStackTrace(); } } } }
運行結果

如何解決報錯的問題呢?
public class User implements Serializable{ private int id; private String name; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; }
了解序列化的意義
我們發現對User這個類增加一個Serializable,就可以解決Java對象的網絡傳輸問題。這就是今天想給大家講解的序列化這塊的意義
Java平台允許我們在內存中創建可復用的Java對象,但一般情況下,只有當JVM處於運行時,這些對象才可能存在,即,這些對象的生命周期不會比JVM的生命周期更長。但在現實應用中,就可能要求在JVM停止運行之后能夠保存(持久化)指定的對象,並在將來重新讀取被保存的對象。Java對象序列化就能夠幫助我們實現該功能
簡單來說
序列化是把對象的狀態信息轉化為可存儲或傳輸的形式過程,也就是把對象轉化為字節序列的過程稱為對象的序列化
反序列化是序列化的逆向過程,把字節數組反序列化為對象,把字節序列恢復為對象的過程成為對象的反序列化
序列化的高階認識
簡單認識一下Java原生序列化
前面的代碼中演示了,如何通過JDK提供了Java對象的序列化方式實現對象序列化傳輸,主要通過輸出流java.io.ObjectOutputStream和對象輸入流java.io.ObjectInputStream來實現。
java.io.ObjectOutputStream:表示對象輸出流, 它的writeObject(Object obj)方法可以對參數指定的obj對象進行序列化,把得到的字節序列寫到一個目標輸出流中。
java.io.ObjectInputStream:表示對象輸入流,它的readObject()方法源輸入流中讀取字節序列,再把它們反序列化成為一個對象,並將其返回
需要注意的是,被序列化的對象需要實現java.io.Serializable接口
序列化的高階認識serialVersionUID的作用
在IDEA中通過如下設置可以生成serializeid
演示步驟
1.先將user對象序列化到文件中
2.然后修改user對象,增加serialVersionUID字段
3.然后通過反序列化來把對象提取出來
4.演示預期結果:提示無法反序列化
結論
Java的序列化機制是通過判斷類的serialVersionUID來驗證版本一致性的。在進行反序列化時,JVM會把傳來的字節流中的serialVersionUID與本地相應實體類的serialVersionUID進行比較,如果相同就認為是一致的,可以進行反序列化,否則就會出現序列化版本不一致的異常,即是InvalidCastException。
從結果可以看出,文件流中的class和classpath中的class,也就是修改過后的class,不兼容了,處於安全機制考慮,程序拋出了錯誤,並且拒絕載入。從錯誤結果來看,如果沒有為指定的class配置serialVersionUID,那么java編譯器會自動給這個class進行一個摘要算法,類似於指紋算法,只要這個文件有任何改動,得到的UID就會截然不同的,可以保證在這么多類中,這個編號是唯一的。所以,由於沒有顯指定serialVersionUID,編譯器又為我們生成
了一個UID,當然和前面保存在文件中的那個不會一樣了,於是就出現了2個序列化版本號不一致的錯誤。因此,只要我們自己指定了serialVersionUID,就可以在序列化后,去添加一個字段,或者方法,而不會影響到后期的還原,還原后的對象照樣可以使用,而且還多了方法或者屬性可以用。
tips:serialVersionUID有兩種顯示的生成方式:
一是默認的1L,比如:private static final long serialVersionUID = 1L;
二是根據類名、接口名、成員方法及屬性等來生成一個64位的哈希字段當實現java.io.Serializable接口的類沒有顯式地定義一個serialVersionUID變量時候,Java序列化機制會根據編譯的Class自動生成一個serialVersionUID作序列化版本比較用,這種情況下,如果Class文件(類名,方法明等)沒有發生變化(增加空格,換行,增加注釋等等),就算再編譯多次,serialVersionUID也不會變化的。
Transient關鍵字
Transient 關鍵字的作用是控制變量的序列化,在變量聲明前加上該關鍵字,可以阻止該變量被序列化到文件中,在被反序列化后,transient 變量的值被設為初始值,如int 型的是0,對象型的是null。
繞開transient機制的辦法
雖然name被transient修飾,但是通過我們寫的這兩個方法依然能夠使得name字段正確被序列化和反序列化
writeObject和readObject原理
writeObject和readObject是兩個私有的方法,他們是什么時候被調用的呢?從運行結果來看,它確實被調用。而且他們並不存在於Java.lang.Object,也沒有在Serializable中去聲明。我們唯一的猜想應該還是和ObjectInputStream和ObjectOutputStream有關系,所以基於這個入口去看看在哪個地方有調用
從源碼層面來分析可以看到,readObject是通過反射來調用的。其實我們可以在很多地方看到readObject和writeObject的使用,比如HashMap。
Java序列化的一些簡單總結
1.Java序列化只是針對對象的狀態進行保存,至於對象中的方法,序列化不關心
2.當一個父類實現了序列化,那么子類會自動實現序列化,不需要顯示實現序列化接口
3.當一個對象的實例變量引用了其他對象,序列化這個對象的時候會自動把引用的對象也進行序列化(實現深度克隆)
4.當某個字段被申明為transient后,默認的序列化機制會忽略這個字段5.被申明為transient的字段,如果需要序列化,可以添加兩個私有方法:writeObject和readObject
分布式架構下常見序列化技術
初步了解了Java序列化的知識以后,我們又得回到分布式架構中,了解序列化的發展過程
了解序列化的發展
隨着分布式架構、微服務架構的普及。服務與服務之間的通信成了最基本的需求。這個時候,我們不僅需要考慮通信的性能,也需要考慮到語言多元化問題所以,對於序列化來說,如何去提升序列化性能以及解決跨語言問題,就成了一個重點考慮的問題。
由於Java本身提供的序列化機制存在兩個問題
1.序列化的數據比較大,傳輸效率低
2.其他語言無法識別和對接以至於在后來的很長一段時間,基於XML格式編碼的對象序列化機制成為了主流,一方面解決了多語言兼容問題,另一方面比二進制的序列化方式更容易理解。以至於基於XML的SOAP協議及對應的WebService框架在很長一段時間內成為各個主流開發語言的必備的技術。再到后來,基於JSON的簡單文本格式編碼的HTTP REST接口又基本上取代了復雜的Web Service接口,成為分布式架構中遠程通信的首要選擇。但是JSON序列化存儲占用的空間大、性能低等問題,同時移動客戶端應用需要更高效的傳輸數據來提升用戶體驗。在這種情況下與語言無關並且高效的二進制編碼協議就成為了大家追求的熱點技術之一。首先誕生的一個開源的二進制序列化框架-MessagePack。它比google的Protocol Buffers出現得還要早。
簡單了解各種序列化技術
XML序列化框架介紹
XML序列化的好處在於可讀性好,方便閱讀和調試。但是序列化以后的字節碼文件比較大,而且效率不高,適用於對性能不高,而且QPS較低的企業級內部系統之間的數據交換的場景,同時XML又具有語言無關性,所以還可以用於異構系統之間的數據交換和協議。比如我們熟知的Webservice,就是采用XML格式對數據進行序列化的。XML序列化/反序列化的實現方式有很多,熟知的方式有XStream和Java自帶的XML序列化和反序列化兩種
JSON序列化框架
JSON(JavaScriptObjectNotation)是一種輕量級的數據交換格式,相對於XML來說,JSON的字節流更小,而且可讀性也非常好。現在JSON數據格式在企業運用是最普遍的JSON序列化常用的開源工具有很多
1.Jackson (https://github.com/FasterXML/jackson)
2.阿里開源的FastJson(https://github.com/alibaba/fastjson)
3.Google的GSON( https://github.com/google/gson )這幾種json序列化工具中,Jackson與fastjson要比GSON的性能要好,但是Jackson、GSON的穩定性要比Fastjson好。而fastjson的優勢在於提供的api非常容易使用
Hessian序列化框架
Hessian是一個支持跨語言傳輸的二進制序列化協議,相對於Java默認的序列化機制來說,Hessian具有更好的性能和易用性,而且支持多種不同的語言
實際上Dubbo采用的就是Hessian序列化來實現,只不過Dubbo對Hessian進行了重構,性能更高
Avro序列化
Avro是一個數據序列化系統,設計用於支持大批量數據交換的應用。它的主要特點有:支持二進制序列化方式,可以便捷,快速地處理大量數據;動態語言友好,Avro提供的機制使動態語言可以方便地處理Avro數據。
序列化技術的選型
技術層面
1.序列化空間開銷,也就是序列化產生的結果大小,這個影響到傳輸的性能
2.序列化過程中消耗的時長,序列化消耗時間過長影響到業務的響應時間
3.序列化協議是否支持跨平台,跨語言。因為現在的架構更加靈活,如果存在異構系統通信需求,那么這個是必須要考慮的
4.可擴展性/兼容性,在實際業務開發中,系統往往需要隨着需求的快速迭代來實現快速更新,這就要求我們采用的序列化協議基於良好的可擴展性/兼容性,比如在現有的序列化數據結構中新增一個業務字段,不會影響到現有的服務
5.技術的流行程度,越流行的技術意味着使用的公司多,那么很多坑都已經淌過並且得到了
解決,技術解決方案也相對成熟
6.學習難度和易用性
選型建議
1.對性能要求不高的場景,可以采用基於XML的SOAP協議
2.對性能和間接性有比較高要求的場景,那么Hessian、Protobuf、Thrift、Avro都可以。
3.基於前后端分離,或者獨立的對外的api服務,選用JSON是比較好的,對於調試、可讀性都很不錯
4.Avro設計理念偏於動態類型語言,那么這類的場景使用Avro是可以的
各個序列化技術的性能比較
這個地址有針對不同序列化技術進行性能比較:https://github.com/eishay/jvm-serializers/wiki
現在人工智能非常火爆,很多朋友都想學,但是一般的教程都是為博碩生准備的,太難看懂了。最近發現了一個非常適合小白入門的教程,不僅通俗易懂而且還很風趣幽默。所以忍不住分享一下給大家