作为码农你必须懂的序列化

Posted -早起的码农

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了作为码农你必须懂的序列化相关的知识,希望对你有一定的参考价值。

一、基本概念

 

  • 序列化:将对象写入到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());

 

以上是关于作为码农你必须懂的序列化的主要内容,如果未能解决你的问题,请参考以下文章

作为码农你必须懂的序列化

2021你必须懂的RPC--分布式微服务和云计算应用的核心技术

个人感悟之CMS的精华和糟粕

后端工程师,必须搞懂的 RPC 框架丨极客时间

1024我们的码农节-向自己致敬!

imreconstruct 这个matlab的函数有人懂的吗