优化Java序列化– Java,XML,JSON,Kryo,POF
也許我很天真,但是我一直認為Java序列化肯定是將Java對象序列化為二進制形式的最快,最有效的方法。 畢竟Java是第7個主要發行版,所以這不是新技術,并且由于每個JDK似乎都比上一個快,因此我錯誤地認為序列化現在必須非??焖俸透咝?。 我認為,由于Java序列化是二進制的,并且依賴于語言,因此它必須比XML或JSON更快,更高效。 不幸的是,我錯了,如果您擔心性能,建議不要使用Java序列化。
現在,請不要誤會我的意思,我不是在嘗試破壞Java。 Java序列化有許多要求,主要的需求是能夠將任何東西(或至少任何實現Serializable東西) Serializable到任何其他JVM(甚至是不同的JVM版本/實現)中,甚至運行被序列化的類的不同版本(例如只要您設置了serialVersionUID )。 最主要的是,它確實有效,而且確實很棒。 性能不是主要要求,格式是標準的并且必須向后兼容,因此優化非常困難。 而且,對于許多類型的用例,Java序列化執行得很好。
在研究三層并發基準時,我開始了進入序列化過程的旅程。 我注意到Java序列化過程中花費了大量CPU時間,因此我決定進行調查。 我從序列化具有幾個字段的簡單Order對象開始。 我序列化了對象并輸出了字節。 盡管Order對象只有幾個字節的數據,但我并不是天真地認為它將序列化為幾個字節,但我對序列化足夠了解,因此至少需要寫出完整的類名,因此它知道它已序列化的內容,因此可以將其讀回。 因此,我期望大約50個字節。 結果超過了600個字節,那時候我意識到Java序列化并不像我想象的那么簡單。
Order對象的Java序列化字節
----sr--model.Order----h#-----J--idL--customert--Lmodel/Customer;L--descriptiont--Ljava/lang/String;L--orderLinest--Ljava/util/List;L--totalCostt--Ljava/math/BigDecimal;xp--------ppsr--java.util.ArrayListx-----a----I--sizexp----w-----sr--model.OrderLine--&-1-S----I--lineNumberL--costq-~--L--descriptionq-~--L--ordert--Lmodel/Order;xp----sr--java.math.BigDecimalT--W--(O---I--scaleL--intValt--Ljava/math/BigInteger;xr--java.lang.Number-----------xp----sr--java.math.BigInteger-----;-----I--bitCountI--bitLengthI--firstNonzeroByteNumI--lowestSetBitI--signum[--magnitudet--[Bxq-~----------------------ur--[B------T----xp----xxpq-~--xq-~--(注意“-”表示不可打印的字符)
您可能已經注意到,Java序列化不僅寫出要序列化的對象的完整類名,而且還寫出要序列化的類的整個類定義以及所有引用的類。 類定義可能非常大,并且似乎是主要的性能和效率問題,尤其是在編寫單個對象時。 如果要寫出大量相同類的對象,則類定義開銷通常不是大問題。 我注意到的另一件事是,如果您的對象具有對類的引用(例如元數據對象),則Java序列化將編寫整個類定義,而不僅僅是類名,因此使用Java序列化來編寫元數據非常昂貴。
可外部化
通過實現Externalizable接口可以優化Java序列化。 實現此接口可以避免寫出整個類定義,而只需編寫類名即可。 它要求您實現readExternal和writeExternal方法,因此需要您進行一些工作和維護,但是比僅實現Serializable更快,更高效。
關于Externalizable結果的一個有趣注釋是,對于少量對象,它的效率要高得多,但對于大量對象,實際上輸出的字節數要比Serializable多。 我假設Externalizable格式對重復對象的效率稍低。
可外部化的類
public class Order implements Externalizable {private long id;private String description;private BigDecimal totalCost = BigDecimal.valueOf(0);private List orderLines = new ArrayList();private Customer customer;public Order() {}public void readExternal(ObjectInput stream) throws IOException, ClassNotFoundException {this.id = stream.readLong();this.description = (String)stream.readObject();this.totalCost = (BigDecimal)stream.readObject();this.customer = (Customer)stream.readObject();this.orderLines = (List)stream.readObject();}public void writeExternal(ObjectOutput stream) throws IOException {stream.writeLong(this.id);stream.writeObject(this.description);stream.writeObject(this.totalCost);stream.writeObject(this.customer);stream.writeObject(this.orderLines);} }Order對象的可外部化的序列化字節
----sr--model.Order---*3--^---xpw---------psr--java.math.BigDecimalT--W--(O---I--scaleL--intValt--Ljava/math/BigInteger;xr--java.lang.Number-----------xp----sr--java.math.BigInteger-----;-----I--bitCountI--bitLengthI--firstNonzeroByteNumI--lowestSetBitI--signum[--magnitudet--[Bxq-~----------------------ur--[B------T----xp----xxpsr--java.util.ArrayListx-----a----I--sizexp----w-----sr--model.OrderLine-!!|---S---xpw-----pq-~--q-~--xxx其他序列化選項
我開始研究Java中還有哪些其他序列化選項。 我從EclipseLink MOXy開始,它支持通過JAXB API將對象序列化為XML或JSON。 我并不期望XML序列化能勝過Java序列化,因此在某些用例中確實感到驚訝。 我還找到了產品Kryo,這是一個用于優化序列化的開源項目。 我還研究了Oracle Coherence POF序列化格式。 每個產品都有優點和缺點,但我的主要重點是比較它們的性能和效率。
EclipseLink MOXy – XML和JSON
使用EclipseLink MOXy序列化為XML或JSON的主要優點是兩者都是標準的可移植格式。 您可以使用任何語言從任何客戶端訪問數據,因此與Java序列化一樣,不限于Java。 您還可以將數據與Web服務和REST服務集成。 兩種格式都基于文本,因此易于閱讀。 不需要編碼或特殊接口,只需元數據。 性能是完全可以接受的,并且對于小型數據集,其性能優于Java序列化。
缺點是文本格式的效率不如優化的二進制格式,并且JAXB需要元數據。 因此,您需要使用JAXB批注來批注您的類,或提供一個XML配置文件。 另外,默認情況下不處理循環引用,您需要使用@XmlIDREF來處理循環。
JAXB注釋的類
@XmlRootElement public class Order {@XmlID@XmlAttributeprivate long id;@XmlAttributeprivate String description;@XmlAttributeprivate BigDecimal totalCost = BigDecimal.valueOf(0);private List orderLines = new ArrayList();private Customer customer; }public class OrderLine {@XmlIDREFprivate Order order;@XmlAttributeprivate int lineNumber;@XmlAttributeprivate String description;@XmlAttributeprivate BigDecimal cost = BigDecimal.valueOf(0); }訂單對象的EclipseLink MOXy序列化XML
<order id="0" totalCost="0"><orderLines lineNumber="1" cost="0"><order>0</order></orderLines></order>訂單對象的EclipseLink MOXy序列化JSON
{"order":{"id":0,"totalCost":0,"orderLines":[{"lineNumber":1,"cost":0,"order":0}]}}ry
Kryo是一個快速,高效的Java序列化框架。 Kryo是根據New BSD許可提供的Google代碼上的開源項目。 這是一個很小的項目,只有3個成員,它于2009年首次發布,最后一次于2013年2月發布2.21版本,因此仍在積極開發中。
Kryo的工作方式類似于Java序列化,并且尊重瞬態字段,但不需要類可序列化。 我發現Kryo有一些限制,例如要求類具有默認構造函數,并且在序列化java.sql.Time,java.sql.Date和java.sql.Timestamp類時遇到了一些問題。
Order對象的Kryo序列化字節
------java-util-ArrayLis-----model-OrderLin----java-math-BigDecima---------model-Orde-----Oracle Coherence POF
Oracle Coherence產品提供了自己優化的二進制格式,稱為POF(便攜式對象格式)。 Oracle Coherence是一種內存數據網格解決方案(分布式緩存)。 一致性是一種商業產品,需要許可證。 EclipseLink通過使用Coherence作為EclipseLink共享緩存的Oracle TopLink Grid產品支持與Oracle Coherence的集成。
POF提供了序列化框架,并且可以獨立于Coherence使用(如果您已經獲得Coherence許可)。 POF要求您的類實現可PortableObject接口和讀/寫方法。 您還可以實現單獨的Serializer類,或在最新的Coherence版本中使用注釋。 POF要求為每個類提前分配一個常量ID,因此您需要某種方式確定此ID。 POF格式是一種二進制格式,非常緊湊,高效且快速,但是您需要做一些工作。
POF的總字節數對于單個Order / OrderLine對象為32字節,對于100 OrderLines為1593字節。 我不會給出結果,因為POF是一種商業許可產品的一部分,但是速度非???。
POF便攜式對象
public class Order implements PortableObject {private long id;private String description;private BigDecimal totalCost = BigDecimal.valueOf(0);private List orderLines = new ArrayList();private Customer customer;public Order() {}public void readExternal(PofReader in) throws IOException {this.id = in.readLong(0);this.description = in.readString(1);this.totalCost = in.readBigDecimal(2);this.customer = (Customer)in.readObject(3);this.orderLines = (List)in.readCollection(4, new ArrayList());}public void writeExternal(PofWriter out) throws IOException {out.writeLong(0, this.id);out.writeString(1, this.description);out.writeBigDecimal(2, this.totalCost);out.writeObject(3, this.customer);out.writeCollection(4, this.orderLines);} }Order對象的POF序列化字節
-----B--G---d-U------A--G-------結果
那么每種表現如何呢? 我做了一個簡單的基準比較不同的序列化機制。 我比較了兩個不同用例的序列化。 第一個是具有單個OrderLine對象的單個Order對象。 第二個是具有100個OrderLine對象的單個Order對象。 我比較了每秒的平均序列化操作,并測量了序列化數據的字節大小。 不同的對象模型,用例和環境將產生不同的結果,但這使您對不同的序列化器的性能差異有一個大致的了解。
結果表明,Java序列化對于少量對象來說很慢,但是對于大量對象來說很好。 相反,對于少量對象,XML和JSON的性能優于Java序列化,但是對于大量對象,Java序列化的速度更快。 Kryo和其他優化的二進制序列化程序在這兩種數據類型方面均優于Java序列化。
您可能想知道,為什么不到一毫秒的時間與性能有任何關系,這可能是相關的,您可能是對的。 通常,如果您寫出大量對象,然后Java序列化執行得很好,那么您只會遇到一個實際的性能問題,那么,對于少量對象而言,它的執行效果很差嗎? 對于單個操作,這可能是正確的,但是如果執行許多小的序列化操作,則成本是相關的。 為許多客戶端提供服務的典型服務器通常會發出許多小請求,因此盡管序列化的成本不足以使這些單個請求中的任何一個花費很長時間,但它將極大地影響服務器的可伸縮性。
用1條訂單行訂購
| Java可序列化 | 636 | 128,634 | 19,180 | 0% | 0% |
| Java可外部化 | 435 | 160,549 | 26,678 | 24% | 39% |
| EclipseLink MOXy XML | 101 | 348,056 | 47,334 | 170% | 146% |
| ry | 90 | 359,368 | 346,984 | 179% | 1709% |
訂購100條訂單行
| Java可序列化 | 2,715 | 16,470 | 10,215 | 0% | 0% |
| Java可外部化 | 2,811 | 16,206 | 11,483 | -1% | 12% |
| EclipseLink MOXy XML | 6,628 | 7,304 | 2,731 | -55% | -73% |
| ry | 1216 | 22862 | 31,499 | 38% | 208% |
EclipseLink JPA
在EclipseLink 2.6開發版本(在某種程度上為2.5)中,我們增加了在EclipseLink進行序列化的任何地方選擇序列化程序的功能。
這樣的地方之一是序列化@Lob映射。 現在,您可以使用@Convert批注指定序列化程序,例如@Convert(XML),@ Convert(JSON),@ Convert(Kryo)。 除了優化性能之外,這還提供了一種簡單的機制來將XML和JSON數據寫入數據庫。
同樣對于EclipseLink緩存協調,您可以使用“ eclipselink.cache.coordination.serializer”屬性選擇序列化器。
這篇文章中使用的基準測試的源代碼可以在這里找到,或者在這里下載。
翻譯自: https://www.javacodegeeks.com/2013/09/optimizing-java-serialization-java-vs-xml-vs-json-vs-kryo-vs-pof.html
總結
以上是生活随笔為你收集整理的优化Java序列化– Java,XML,JSON,Kryo,POF的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 电脑cpu速度锁住(电脑cpu被锁频率如
- 下一篇: NetBeans 7.4 Beta提示警