1. 程式人生 > 其它 >作為碼農你必須懂的序列化

作為碼農你必須懂的序列化

技術標籤:大資料處理javajava

一、基本概念

  • 序列化:將物件寫入到IO流中

  • 反序列化:從IO流中恢復物件

  • 意義:序列化機制允許將實現序列化的Java物件轉換位位元組序列,這些位元組序列可以儲存在磁碟上,或通過網路傳輸,以達到以後恢復成原來的物件。序列化機制使得物件可以脫離程式的執行而獨立存在。

  • 使用場景:所有可在網路上傳輸的物件都必須是可序列化的,比如RMI(remote method invoke,即遠端方法呼叫),傳入的引數或返回的物件都是可序列化的,否則會出錯;所有需要儲存到磁碟的java物件都必須是可序列化的

二、序列化實現的方式

JDK類庫中的序列化API,只有實現了Serializable或Externalizable介面的類物件才能被序列化,否則會出現java.io.NotSerializableException異常。

實現Externalizable介面的類完全由自身來控制序列化的行為,而僅實現Serializable介面的類可以採用預設的序列化方式

  • 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.setPassword("123456");
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());