内置序列化技术
本文是我們名為“ 高級Java ”的學院課程的一部分。
本課程旨在幫助您最有效地使用Java。 它討論了高級主題,包括對象創建,并發,序列化,反射等。 它將指導您完成Java掌握的過程! 在這里查看 !
目錄
1.簡介 2.可序列化的接口 3.可外部化的界面 4.有關可序列化接口的更多信息 5.可序列化和遠程方法調用(RMI) 6. JAXB 7. JSON-P 8.序列化成本 9.超越Java標準庫和規范 10.下一步是什么 11.下載源代碼1.簡介
本教程的這一部分將專門用于序列化 :將Java對象轉換為一種格式的過程,該格式可用于在同一(或其他)環境中進行存儲和以后的重構( http://en.wikipedia。 org / wiki / Serialization )。 序列化不僅允許將Java對象保存到持久性存儲中或從持久性存儲中加載Java對象,而且還是現代分布式系統通信中非常重要的組件。
序列化并不容易,但是有效的序列化則更加困難。 除了Java標準庫之外,還有許多可用的序列化技術和框架:其中一些使用緊湊的二進制表示形式,而另一些則將可讀性放在首位。 盡管我們將在此過程中提及許多替代方案,但我們的注意力將集中在Java標準庫(和最新規范)中的替代方案: Serializable , Externalizable ,用于XML綁定的Java體系結構( JAXB , JSR-222 )和用于Java的Java API。 JSON處理( JSON-P , JSR-353 )。
2.可序列化的接口
可以說,Java中將類標記為可用于序列化的最簡單方法是實現java.io.Serializable接口。 例如:
public class SerializableExample implements Serializable { }序列化運行時與每個可序列化的類關聯一個特殊的版本號,稱為序列號UID ,該序列號在反序列化 (與序列化相反的過程)中使用,以確保為序列化對象加載的類兼容。 如果兼容性受到損害,則將InvalidClassException 。
可序列化的類可以通過聲明名稱為serialVersionUID的字段為static , final且類型為long的字段來顯式引入其自己的串行版本UID 。 例如:
public class SerializableExample implements Serializable {private static final long serialVersionUID = 8894f47504319602864L; }但是,如果可序列化的類未明確聲明serialVersionUID字段,則序列化運行時將為該類生成一個默認的serialVersionUID字段。 值得一提的是,所有實現Serializable類都強烈建議顯式聲明serialVersionUID字段,因為默認的serialVersionUID生成嚴重依賴于內部類的詳細信息,并且可能會因Java編譯器實現及其版本而有所不同。 這樣,為了保證行為的一致性,可序列化的類必須始終聲明一個顯式的serialVersionUID字段。
一旦該類可序列化(實現Serializable并聲明serialVersionUID ),就可以使用例如ObjectOutputStream / ObjectInputStream進行存儲和檢索:
final Path storage = new File( "object.ser" ).toPath();try( final ObjectOutputStream out = new ObjectOutputStream( Files.newOutputStream( storage ) ) ) {out.writeObject( new SerializableExample() ); }存儲后,可以通過類似的方式進行檢索,例如:
try( final ObjectInputStream in = new ObjectInputStream( Files.newInputStream( storage ) ) ) {final SerializableExample instance = ( SerializableExample )in.readObject();// Some implementation here }如我們所見, Serializable接口沒有對應該序列化什么以及如何進行序列化提供很多控制( transient關鍵字將字段標記為不可序列化除外)。 而且,它限制了更改內部類表示形式的靈活性,因為它可能會破壞序列化/反序列化過程。 這就是為什么引入了另一個接口Externalizable原因。
3.可外部化的界面
與Serializable接口相反, Externalizable將類應如何序列化和反序列化的職責委托給該類。 它只有兩種方法,這是Java標準庫中的聲明:
public interface Externalizable extends java.io.Serializable {void writeExternal(ObjectOutput out) throws IOException;void readExternal(ObjectInput in) throws IOException, ClassNotFoundException; }反過來,每個實現Externalizable接口的類都應提供這兩種方法的實現。 讓我們看一個例子:
public class ExternalizableExample implements Externalizable {private String str;private int number;private SerializableExample obj;@Overridepublic void readExternal(final ObjectInput in) throws IOException, ClassNotFoundException {setStr(in.readUTF());setNumber(in.readInt());setObj(( SerializableExample )in.readObject());}@Overridepublic void writeExternal(final ObjectOutput out) throws IOException {out.writeUTF(getStr());out.writeInt(getNumber());out.writeObject(getObj());} }與實現Serializable的類相似,可以使用例如ObjectOutputStream / ObjectInputStream存儲和檢索實現Externalizable的類:
final Path storage = new File( "extobject.ser" ).toPath();final ExternalizableExample instance = new ExternalizableExample(); instance.setStr( "Sample String" ); instance.setNumber( 10 ); instance.setObj( new SerializableExample() );try( final ObjectOutputStream out = new ObjectOutputStream( Files.newOutputStream( storage ) ) ) {out.writeObject( instance ); }try( final ObjectInputStream in = new ObjectInputStream( Files.newInputStream( storage ) ) ) {final ExternalizableExample obj = ( ExternalizableExample )in.readObject();// Some implementation here }當使用Serializable接口的簡單方法無法正常工作時,使用Externalizable接口可以進行細粒度的序列化/反序列化自定義。
4.有關可序列化接口的更多信息
在上一節中,我們提到了Serializable接口并沒有對應該序列化什么以及如何序列化提供很多控制。 實際上,它并不是完全正確的(至少在使用ObjectOutputStream / ObjectInputStream時)。 任何可序列化的類都可以實現一些特殊方法,以控制默認的序列化和反序列化。
private void writeObject(ObjectOutputStream out) throws IOException;此方法負責為其特定類編寫對象的狀態,以便相應的readObject方法可以將其恢復(可以通過調用out.defaultWriteObject調用保存對象字段的默認機制)。
private void readObject(ObjectInputStream in) throws IOException,ClassNotFoundException;此方法負責從流中讀取并還原對象的狀態(可通過調用in.defaultReadObject調用還原對象字段的默認機制)。
private void readObjectNoData() throws ObjectStreamException;在序列化流未將給定類列為要反序列化的對象的超類的情況下,此方法負責初始化對象的狀態。
Object writeReplace() throws ObjectStreamException;當可序列化的類需要指定將對象寫入流時要使用的替代對象時,使用此方法。
Object readResolve() throws ObjectStreamException;最后,當從流中讀取可序列化的類的實例時,可序列化的類需要指定替換時,使用此方法。
一旦知道內在的實現細節和要使用的特殊方法,默認的序列化機制(使用Serializable接口)在Java中就會變得非常麻煩。 您正在編寫用于支持序列化的更多代碼,更有可能展示出更多的錯誤和漏洞。
但是,有一種方法可以通過使用名為Serialization Proxy的非常簡單的模式來降低這些風險,該模式基于利用writeReplace和readResolve方法。 這種模式的基本思想是引入專用的伴隨類進行序列化(通常作為private static內部類),以補充需要序列化的類。 讓我們看一下這個例子:
public class SerializationProxyExample implements Serializable {private static final long serialVersionUID = 6163321482548364831L;private String str;private int number; public SerializationProxyExample( final String str, final int number) {this.setStr(str);this.setNumber(number);}private void readObject(ObjectInputStream stream) throws InvalidObjectException {throw new InvalidObjectException( "Serialization Proxy is expected" );}private Object writeReplace() {return new SerializationProxy( this );}// Setters and getters here }對此類的實例進行序列化時,類SerializationProxyExample實現將提供替換對象( SerializationProxy類的實例)。 這意味著SerializationProxyExample類的實例將永遠不會直接序列化(和反序列化)。 它還說明了為什么以某種方式進行反序列化嘗試時, readObject方法會引發異常。 現在,讓我們看一下伴隨的SerializationProxy類:
private static class SerializationProxy implements Serializable {private static final long serialVersionUID = 8368440585226546959L;private String str;private int number;public SerializationProxy( final SerializationProxyExample instance ) {this.str = instance.getStr();this.number = instance.getNumber();}private Object readResolve() {return new SerializationProxyExample(str, number); // Uses public constructor} }在我們的略微簡化的情況下, SerializationProxy類只是復制了所有的領域SerializationProxyExample (但可能比被很多復雜)。 因此,當要反序列化此類的實例時, readResolve調用readResolve方法,并且SerializationProxy提供替換,這次的形式為SerializationProxyExample實例。 因此, SerializationProxy類可作為一個序列化代理SerializationProxyExample類。
5.可序列化和遠程方法調用(RMI)
相當長一段時間以來,Java遠程方法調用( RMI )是可用于在Java平臺上構建分布式應用程序的唯一機制。 RMI提供了所有繁重的工作,并且可以從同一主機或不同物理(或虛擬)主機上的其他JVM透明地調用遠程Java對象的方法。 RMI的基礎是對象序列化,該對象序列化用于編組(序列化)和解組(反序列化)方法參數。
如今, RMI仍在許多Java應用程序中使用,但由于它的復雜性和通信限制(大多數防火墻都阻止RMI端口),因此越來越少選擇RMI 。 要獲取有關RMI的更多詳細信息,請參考官方文檔 。
6. JAXB
用于XML綁定的Java體系結構,或者只是JAXB ,可能是Java開發人員可以使用的最古老的替代序列化機制。 在下面,它使用XML作為序列化格式,提供了廣泛的自定義選項,并包含許多注釋,這些注釋使JAXB非常吸引人并且易于使用(注釋在本教程的第5部分中介紹了如何以及何時使用Enums和注釋 )。
讓我們看一個用JAXB注釋注釋的普通舊Java類(POJO)的簡化示例:
import java.math.BigDecimal; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlRootElement;@XmlAccessorType( XmlAccessType.FIELD ) @XmlRootElement( name = "example" ) public class JaxbExample {@XmlElement(required = true) private String str;@XmlElement(required = true) private BigDecimal number;// Setters and getters here }要使用JAXB基礎結構將該類的實例序列化為XML格式,唯一需要的是編組器(或序列化器)的實例,例如:
final JAXBContext context = JAXBContext.newInstance( JaxbExample.class ); final Marshaller marshaller = context.createMarshaller();final JaxbExample example = new JaxbExample(); example.setStr( "Some string" ); example.setNumber( new BigDecimal( 12.33d, MathContext.DECIMAL64 ) );try( final StringWriter writer = new StringWriter() ) {marshaller.marshal( example, writer ); }這是上面示例中JaxbExample類實例的XML表示形式:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <example><str>Some string</str><number>12.33000000000000</number> </example>按照相同的原則,可以使用解組器(或反序列化器)的實例將類的實例從XML表示反序列化回Java對象,例如:
final JAXBContext context = JAXBContext.newInstance( JaxbExample.class );final String xml = "" +"<?xml version=\\"1.0\\" encoding=\\"UTF-8\\" standalone=\\"yes\\"?>" +"<example>" +" <str>Some string</str>" +" <number>12.33000000000000</number>" +"</example>";final Unmarshaller unmarshaller = context.createUnmarshaller(); try( final StringReader reader = new StringReader( xml ) ) {final JaxbExample example = ( JaxbExample )unmarshaller.unmarshal( reader );// Some implementaion here }正如我們所看到的, JAXB易于使用,并且XML格式在當今仍然很受歡迎。 但是,XML的基本陷阱之一是冗長:很多時候,必要的XML結構元素大大超過了有效的數據有效負載。
7. JSON-P
自2013年以來,借助新引入的JSON處理Java API( JSON-P ),Java開發人員可以將JSON用作序列化格式。
截至目前,JSON-P是不是Java標準庫的一部分,雖然有很多討論,包括原生JSON支持到在即將推出的Java 9釋放(語言http://openjdk.java.net/jeps/198 )。 盡管如此,它還是可以作為Java JSON處理參考實現 ( https://jsonp.java.net/ )的一部分獲得的。
與JAXB相比 ,無需為使該類適合于JSON序列化而添加任何類,例如:
public class JsonExample {private String str;private BigDecimal number;// Setters and getters here }序列化不像JAXB那樣透明,并且需要為要序列化為JSON的每個類編寫一些代碼,例如:
final JsonExample example = new JsonExample(); example.setStr( "Some string" ); example.setNumber( new BigDecimal( 12.33d, MathContext.DECIMAL64 ) );try( final StringWriter writer = new StringWriter() ) {Json.createWriter(writer).write( Json.createObjectBuilder().add("str", example.getStr() ).add("number", example.getNumber() ).build()); }這是上面示例中JsonExample類實例的JSON表示形式:
{"str":"Some string","number":12.33000000000000 }反序列化過程也是如此:
final String json = "{\\"str\\":\\"Some string\\",\\"number\\":12.33000000000000}"; try( final StringReader reader = new StringReader( json ) ) {final JsonObject obj = Json.createReader( reader ).readObject();final JsonExample example = new JsonExample();example.setStr( obj.getString( "str" ) );example.setNumber( obj.getJsonNumber( "number" ).bigDecimalValue() ); }可以說,目前Java中的JSON支持非常基本。 盡管如此,擁有一個很棒的東西,Java社區正在通過引入用于JSON綁定的Java API (JSON-B, JSR-367 )來努力豐富JSON支持。 使用此API,與JSON之間的Java對象序列化和反序列化應該像JAXB一樣透明。
8.序列化成本
重要的是要理解,盡管序列化/反序列化在Java中看起來很簡單,但它不是免費的,并且取決于數據模型和數據訪問模式可能會消耗大量的網絡帶寬,內存和CPU資源。 不僅如此,盡管如此,Java對可序列化類提供了某種版本支持(使用序列化UID,如我們在“可序列化接口 ”一節中所見),它確實使開發過程變得更加困難,因為開發人員需要自己弄清楚如何管理數據模型的演變。
另外要說明的是,Java序列化在JVM領域之外無法正常工作。 對于使用多種編程語言和運行時構建的現代分布式應用程序,這是一個重要的限制。
這就解釋了為什么許多替代的序列化框架和解決方案應運而生,并成為Java生態系統中非常流行的選擇。
9.超越Java標準庫和規范
在本節中,我們將從Fast-serialization項目( http://ruedigermoeller.github.io/fast-serialization/ )開始,探討無痛且有效的Java序列化的替代解決方案:快速替換Java序列化。 快速序列化的用法與Java標準庫提供的用法沒有太大區別,但聲稱它更快,更有效。
另一組框架對此問題有不同的看法。 它們基于結構化數據定義(或協議),并將數據序列化為緊湊的二進制表示形式(甚至可以從定義中生成相應的數據模型)。 除此之外,這些框架遠遠超出了Java平臺,可以用于跨語言/跨平臺序列化。 該領域中最知名的Java庫是Google協議緩沖區 ( https://developers.google.com/protocol-buffers/),Apache Avro ( http://avro.apache.org/ )和Apache Thrift ( https:/ /thrift.apache.org/ )。
10.下一步是什么
在本部分的教程中,我們討論了Java語言及其運行時提供的內置序列化技術。 我們已經看到了當今的串行化的重要性,當時幾乎所有正在構建的單個應用程序都是大型分布式系統的一部分,并且需要與其其余部分(或與其他外部系統)進行通信。 在本教程的下一部分中,我們將討論Java中的反射和動態語言支持。
11.下載源代碼
您可以在此處下載本課程的源代碼: advanced-java-part-10
翻譯自: https://www.javacodegeeks.com/2015/09/built-in-serialization-techniques.html
創作挑戰賽新人創作獎勵來咯,堅持創作打卡瓜分現金大獎總結
- 上一篇: limbo安卓镜像(limbo安卓)
- 下一篇: app mvc框架_Google App