作為碼農你必須懂的序列化
一、基本概念
-
序列化:將物件寫入到IO流中
-
反序列化:從IO流中恢復物件
-
意義:序列化機制允許將實現序列化的Java物件轉換位位元組序列,這些位元組序列可以儲存在磁碟上,或通過網路傳輸,以達到以後恢復成原來的物件。序列化機制使得物件可以脫離程式的執行而獨立存在。
-
使用場景:所有可在網路上傳輸的物件都必須是可序列化的,比如RMI(remote method invoke,即遠端方法呼叫),傳入的引數或返回的物件都是可序列化的,否則會出錯;所有需要儲存到磁碟的java物件都必須是可序列化的
二、序列化實現的方式
JDK類庫中的序列化API,只有實現了Serializable或Externalizable介面的類物件才能被序列化,否則會出現java.io.NotSerializableException異常。
-
Serializable
@Getter @Setter @AllArgsConstructor @NoArgsConstructor public class Student implements Serializable { private static final long serialVersionUID = 1L; private String userName; private String password; private String year; private transient String addr; }
測試:
public class SerializableTest {
public static void main(String[] args) throws IOException
, ClassNotFoundException {
//序列化
FileOutputStream fos = new FileOutputStream("object.out");
ObjectOutputStream oos = new ObjectOutputStream(fos);
Student student1 = new Student();
student1.setUserName("lisi");
student1.setYear("2008");
student1.setAddr("beijing");
oos.writeObject(student1);
oos.flush();
oos.close();
//反序列化
FileInputStream fis = new FileInputStream("object.out");
ObjectInputStream ois = new ObjectInputStream(fis);
Student student2 = (Student) ois.readObject();
System.out.println(student2.getUserName()+ " " +
student2.getPassword() + " " + student2.getYear()
+ " " + student2.getAddr());
}
}
輸出:
lisi 123456 2008 null
我們會發現被transient修飾的欄位,即使設定了值也不會被序列化,反序列化後返回值是null.
serialVersionUID又是什麼作用呢?讓我們改掉serialVersionUID版本號為2L,執行下面這段程式碼:
//反序列化
FileInputStream fis = new FileInputStream("object.out");
ObjectInputStream ois = new ObjectInputStream(fis);
Student student2 = (Student) ois.readObject();
System.out.println(student2.getUserName()+ " " +
student2.getPassword() + " " + student2.getYear()
+ " " + student2.getAddr());
發現出現如下錯誤,大家細品serialVersionUID的作用吧。
Exception in thread "main" java.io.InvalidClassException:
com.manong.test.server.Student; local class incompatible: stream classdesc
serialVersionUID = 1, local class serialVersionUID = 2
at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:687)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1876)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1745)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2033)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1567)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:427)
at com.yeahmobi.easyjob.server.SerializableTest.main(SerializableTest.java:23)
另外我們把Student換成String或者Integer型別去序列化或者反序列化,一樣是可以的,原因我們檢視其String,Integer原始碼,他是實現了Serializable介面的
-
Externalizable:強制自定義序列化
通過實現Externalizable介面,必須實現writeExternal、readExternal方法
public class Person implements Externalizable {
private String name;
private int age;
//注意,必須加上pulic 無參構造器
public Person() {}
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
//將name反轉後寫入二進位制流
StringBuffer reverse = new StringBuffer(name).reverse();
out.writeObject(reverse);
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException,
ClassNotFoundException {
//將讀取的字串反轉後賦值給name例項變數
this.name = ((StringBuffer) in.readObject()).reverse().toString();
this.age = in.readInt();
}
public static void main(String[] args) throws IOException,
ClassNotFoundException {
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("Person.txt"));
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("Person.txt"))) {
oos.writeObject(
oos.writeObject(new Person("lisi", 23));
Person ep = (Person) ois.readObject();
System.out.println(ep.name + " " + ep.age);
}
}
執行結果:
brady 23
二、kafka的序列化和反序列化
kafka內部發送和接收訊息的時候,使用的是byte[]位元組陣列的方式(RPC底層也是用這種通訊格式)。但是我們在應用層其實可以使用更多的資料型別,比如int,short, long,String等,這歸功於kafka的序列化和反序列化機制。
我們來看一個自定義序列化元件的實現的例子,其實核心就是利用fastjson的toJSONBytes把物件轉化為byte陣列。
@Data
@ToString
public class Company {
private String name;
private String address;
}
public class CompanySerializer implements Serializer<Company> {
@Override
public void configure(Map<String, ?> configs, boolean isKey) {
}
@Override
public byte[] serialize(String s, Company company) {
return JSON.toJSONBytes(company);
}
@Override
public void close() {
}
}
public class CompanyDeserializer implements Deserializer<Company> {
@Override
public void configure(Map<String, ?> configs, boolean isKey) {
}
@Override
public Company deserialize(String s, byte[] bytes) {
return JSON.parseObject(bytes, Company.class);
}
@Override
public void close() {
}
}
設定如下引數即可:
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, CompanyDeserializer.class.getName());
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer .class.getName());
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, CompanySerializer.class.getName());