Java序列化详解

Java序列化详解

1.简介

Java的序列化是指将对象编码成一个字节流,这个字节流可以在网络中传输,同时序列化也是将Java对象持久化的一种方式。

Java的序列化机制是在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把字节流中的serialVersionUID和本地对应实体的serialVersionID进行比较,如果相同就认为是一致的。否则就会出现序列化版本不一致的异常。

2.方式

通过java.io.ObjectOutputStream类的writeObject()方法可以实现序列化;

通过java.io.ObjectInputStream类的readObject()方法实现反序列化。

public class SerialTest {
    public static void serialize(Object o, String path) throws IOException {
        File file = new File(path);
        OutputStream fileInputStream = new FileOutputStream(file);
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileInputStream);
        objectOutputStream.writeObject(o);
        objectOutputStream.close();
        fileInputStream.close();
    }

    public static Object reSerialize(String path) throws IOException, ClassNotFoundException {
        File file = new File(path);
        InputStream inputStream = new FileInputStream(file);
        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
        Object o = objectInputStream.readObject();
        return o;
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        User user = new User("张三", 18, 1);
        String path = SerialTest.class.getClassLoader().getResource("").getPath() + "user.txt";
        System.out.println("path = " + path);
        serialize(user, path);
        User reUser = (User) reSerialize(path);
        System.out.println("reUser = " + reUser); //reUser = User(name=张三, age=18, gender=1)

    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
class User implements Serializable{
    private String name;
    private int age;
    private int gender; //性别,0女,1男
}

3. Serializable 接口

被序列化的类必须实现SerializabelExternalizable接口,否则将抛出 NotSerializableException 异常。因为在序列化的过程中会对要序列化的实例进行类型检查,如果不满足序列化要求就抛异常。

3.1SerialVersionUID

serialVersionUID 是 Java 为每个序列化类产生的版本标识。它可以用来保证在反序列时,发送方发送的和接受方接收的是可兼容的对象。如果接收方接收的类的 serialVersionUID 与发送方发送的 serialVersionUID 不一致,会抛出 InvalidClassException

如果可序列化类没有显式声明 serialVersionUID,则序列化运行时将基于该类的各个方面计算该类的默认 serialVersionUID 值。尽管这样,还是建议在每一个序列化的类中显式指定 serialVersionUID 的值。因为不同的 jdk 编译很可能会生成不同的 serialVersionUID 默认值,从而导致在反序列化时抛出 InvalidClassExceptions 异常。

serialVersionUID 字段必须是 static final long 类型

显式地定义serialVersionUID有两种用途:

(1)在某些场合,希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有相同的serialVersionUID;在某些场合,不希望类的不同版本对序列化兼容,因此需要确保类的不同版本具有不同的serialVersionUID

(2)当你序列化了一个类实例后,希望更改一个字段或添加一个字段,不设置serialVersionUID,所做的任何更改都将导致无法反序化旧有实例,并在反序列化时抛出一个异常。如果你添加了serialVersionUID,在反序列旧有实例时,新添加或更改的字段值将设为初始化值(对象为null,基本类型为相应的初始默认值),字段被删除将不设置。

public class SerialTest2 {
    public static void serialize(Object o, String path) throws IOException {
        File file = new File(path);
        OutputStream fileInputStream = new FileOutputStream(file);
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileInputStream);
        objectOutputStream.writeObject(o);
        objectOutputStream.close();
        fileInputStream.close();
    }

    public static Object reSerialize(String path) throws IOException, ClassNotFoundException {
        File file = new File(path);
        InputStream inputStream = new FileInputStream(file);
        ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
        Object o = objectInputStream.readObject();
        return o;
    }

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        String path = SerialTest.class.getClassLoader().getResource("").getPath() + "user.txt";
//        User2 user2 = new User2("张三", 18, 1);
//        System.out.println("path = " + path);
//        serialize(user2, path);
        User2 reUser2 = (User2) reSerialize(path);
        System.out.println("reUser = " + reUser2);  //reUser = User2(name=张三, age=18, gender=1, email=null)

    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
@ToString
class User2 implements Serializable{
    private static final long serialVersionUID = 1L;
    private String name;
    private int age;
    private int gender; //性别,0女,1男
    private String email;
}

将不含email字段的user2字段序列化后,给User2类添加email字段后反序列化,得到的反序列化对象是reUser = User2(name=张三, age=18, gender=1, email=null)。如果没有显式指定serialVersionUID则会报InvalidClassExceptions异常。

3.2transient

在现实应用中,有些时候不能使用默认序列化机制。比如,希望在序列化过程中忽略掉敏感数据,或者简化序列化过程。下面将介绍若干影响序列化的方法。

当某个字段被声明为 transient 后,默认序列化机制就会忽略该字段的内容,该字段的内容在序列化后无法获得访问

class User implements Serializable{
    private String name;
    transient private int age;
    private int gender; //性别,0女,1男, 2无
}

//output: reUser = User(name=张三, age=0, gender=1)

如果我们示例1中的age字段使用transient修饰后,反序列化的对象为reUser = User(name=张三, age=0, gender=1),可以看到age字段没有被反序列化。

4.用途

对象的序列化主要有两种用途:

(1)把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
(2) 在网络上传送对象的字节序列。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值