Java基础巩固-关于Java序列化和反序列化

  1. 云栖社区>
  2. 博客>
  3. 正文

Java基础巩固-关于Java序列化和反序列化

garwer 2018-05-25 01:02:00 浏览855

题外话:
从事IT要学习的东西太多了,有时候会比较浮躁,因为要学的东西太多但又无从下手,甚至有很多基础都还没有深入学习,这个时候应当静下心来,正所谓不忘初心,方能始终,之前一直听说过序列化,但也没有去深入一点点的了解过,这个时候,就当好好巩固下了~

java序列化

常被称为持久化,将其写入磁盘中。
对于一个存在于jvm的对象来说,内部的状态保存在内存中,当jvm停止时这些状态就丢失了,但有些时候对象的内部是需要持久保存的,对象序列化机制(object serialization)是Java语言内建的一种对象持久化方式,可以很容易的在JVM中的活动对象和字节数组(流)之间进行转换,该机制中对象可以表示为字节序列,该字节序列包括该对象的数据,有关对象的类型的信息和存储在对象中数据的类型。

数据序列化就是将对象或者数据结构转化成特定的格式,使其可在网络中传输,或者可存储在内存或者文件中。反序列化则是相反的操作,将对象从序列化数据中还原出来。而对象序列化后的数据格式可以是二进制,可以是XML,也可以是JSON等任何格式。

【整个过程在jvm独立的,在一个平台上序列化的对象可以在另外的平台反序列化】

java类序列化的条件:

1.该类必须实现 java.io.Serializable接口。
2.该类的所有属性必须是可序列化的。如果有一个属性不是可序列化的,则该属性必须注明是短暂的。如果你想知道一个 Java 标准类是否是可序列化的,请查看该类的文档。检验一个类的实例是否能序列化十分简单, 只需要查看该类有没有实现 java.io.Serializable接口。

为什么序列化

1.将结构化的对象变为无结构的字节流,存储对象在存储介质中,方便下次使用可以快捷获取,便于数据传输。
2.序列化的过程通俗讲,就是一个“freeze”的过程,它将一个对象freeze住,然后进行存储,等到再次需要的时候,再将这个对象de-freeze就可以立即使用。

jdk内置序列化

java对序列化提供了很好的支持,当一个对象实现了Serilizable接口,这个对象就可以被序列化,我们不关心其内在的原理,只需要了解这个类实现了Serilizable接口,这个类的所有属性和方法都会自动序列化。可以说Serilizable只是一个标识,实际的序列化和反序列化工作是通过java.io.ObjectOuputStream和java.io.ObjectInputStream来完成的。ObjectOutputStream的writeObject方法可以把一个Java对象写入到流中,ObjectInputStream的readObject方法可以从流中读取一个Java对象。

transient关键字

在实际开发过程中可能遇到说一个对象中的属性有些需要序列化有些则不用,比如说一个用户有些敏感信息(密码,银行卡号),为了安全考虑不需要在网络操作中被传输。
这种时候使用transient可以使对应的属性不被写入磁盘持久化。换句话说,这个对象的生命周期仅存在调用者的内存中而不被持久化在硬盘中或者网络传输。

//使用例子
package serializable;
import java.io.*;

public class TransientTest implements Serializable{
    static class UserInfo implements Serializable {
        private String name; //此处加static反序列化后仍能取到是因为static修饰的变量存在jvm内存中
        private transient String psw;//transient 只能修饰变量(属性)
        public UserInfo(String name, String psw) {
            this.name = name;
            this.psw = psw;
        }

        public String toString() {
            return "name=" + name + ", psw=" + psw;
        }
    }

    public static void main(String[] args) {
        UserInfo userInfo = new UserInfo("张三", "123456");
        System.out.println(userInfo);
        try {
            // 序列化将对象属性写入到UserInfo.txt文件中,被设置为transient的属性没有被序列
            ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("UserInfo.txt"));
            o.writeObject(userInfo);
            o.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
        try {
            // 重新读取序列化内容
            ObjectInputStream in = new ObjectInputStream(new FileInputStream("UserInfo.txt"));
            UserInfo readUserInfo = (UserInfo) in.readObject();
            System.out.println(readUserInfo.toString()); //修饰transient关键字的属性打印为null
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
img_eb66e8be8f833af53bbb4664519b4420.png
测试

Externalizable接口

在java中,对象的序列化可以通过两种接口实现,除了Serilizable接口,还有就是Externalizable接口。
1.若实现Serializable,则所有序列化将会自动执行。
2.若实现Externalizable,序列化的过程需手动执行,需要在writeExternal方法中进行手工指定所要序列化的变量,与是否被transient修饰无关。

import java.io.*;
/**
 * Created by LJW on 2018/5/28.
 * Externalizable接口测试
 */
public class TestExternalizable implements Externalizable {
    private transient String content = "就算被transient修饰,但如果实现的是Externalizable接口,我还是可能被序列化";
    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
    }

    public static void main(String[] args) throws Exception {
        TestExternalizable et = new TestExternalizable();
        //将TestExternalizable序列化到test.txt文件中
        ObjectOutput out = new ObjectOutputStream(new FileOutputStream(
                new File("test.txt")));
        out.writeObject(et);
        //反序列化test.txt中的信息
        ObjectInput in = new ObjectInputStream(new FileInputStream(new File(
                "test.txt")));
        et = (TestExternalizable) in.readObject();
        System.out.println(et.content);//成功打印content内容而不是null,说明反序列化有取到被transient修饰的变量属性
        out.close();
        in.close();
    }
}

serialVersionUID

在查看jdk源码的时候,经常看到这种代码

private static final long serialVersionUID = 2877471301981509474L; //xxxL 

一个类如果使用了java.io.Serializable接口,在序列化到文件时会自动生成一个serialVersionUID,用于对类进行版本控制(通过判断实体类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地相应实体类的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致InvalidCalssException的异常)

如何生成

Intellij IDEA可以自动为serializable的类生成一个serialVersionUID。

File->Preferences->Inspections->Serializationissues,将其展开后将serialzable class without "serialVersionUID"打上勾;
之后双击下class类名 ALT+ENTER即可生成随机的serialVersionUID
img_d54155d66a9d6112c5509cc558a976eb.png

img_4a2072c99d58f9a639e2acd48eb6e4fd.png
生成serialVersionUID

总结

  1. Java序列化就是把对象转换成字节序列,而Java反序列化就是把字节序列还原成Java对象,在java中可以通过实现Serializable和Externalizable两种接口实现序列化。
  2. 采用Java序列化与反序列化技术,一是可以实现数据的持久化,在MVC模式中很有用;二是可以对象数据的远程通信,序列化用于通信,服务端把数据序列化发送到客户端。客户端收到数据反序列化对数据操作。
  3. 序列化的好处:通过序列化可以把数据永久保存在硬盘上(通常放在文件里)
  4. transient关键字只能修饰属性,被transient修饰的属性将不会被序列化(这边的前提是实现Serializable接口,还有需注意被static修饰的属性也无法被序列化,static修饰的变量存在jvm内存中,如果反序列化后得到static修饰的属性,是从jvm取而不是反序列化后得到)。
  5. serialVersionUID主要用于反序列化的时候验证版本的一致性,常在jdk,各种jar包中使用。