Java序列化

序列化

概念(Java 提供了一种对象序列化的机制。)

用一个字节序列可以表示一个对象,该字节序列包含该 对象的数据 、 对象的类型和 对象中存储的属性 等信息。字节序列写出到文件之后,相当于文件中持久保存了一个对象的信息。 反之,该字节序列还可以从文件中读取回来,重构对象,对它进行反序列化。 对象的数据 、 对象的类型 和 对象中存储的数据 信息,都可以用来在内存中创建对象。

简单说序列化是指把一个Java对象变成二进制内容,本质上就是一个byte[]数组。

为什么要把Java对象序列化呢?

因为序列化后可以把byte[]保存到文件中,或者把byte[]通过网络传输到远程,这样,就相当于把Java对象存储到文件或者通过网络传输出去了。有序列化,就有反序列化,即把一个二进制内容(也就是byte[]数组)变回Java对象。有了反序列化,保存到文件中的byte[]数组又可以“变回”Java对象,或者从网络上读取byte[]并把它“变回”Java对象。

Serializable

序列化需要类实现java.io.Serializable接口,也就是说不实现此接口的类将不能序列化或反序列化。Serializable类的所有子类都是可序列化的。序列化接口没有方法或字段,仅用于标识可串行化的语义。我们把这样的空接口称为“标记接口”(Marker Interface)实现了标记接口的类仅仅是给自身贴了个“标记”,并没有增加任何方法。

public interface Serializable {
}

ObjectOutputStream

java.io.ObjectOutputStream类,将Java对象的原始数据类型写出到流,实现对象的持久存储。

public class ObjectOutputStream extends OutputStream implements ObjectOutput, ObjectStreamConstants

构造方法

ObjectOutputStream提供了一个public的构造方法:

//创建一个指定OutputStream的ObjectOutputStream
public ObjectOutputStream(OutputStream out);

序列化操作

序列化为byte[]

package com.itlaobing.demo;
​
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.Arrays;
​
public class ObjectOutputStreamTest {
​
    public static void main(String[] args) throws IOException {
        try(ByteArrayOutputStream bos = new ByteArrayOutputStream();ObjectOutputStream oos = new ObjectOutputStream(bos)){
            //写内容
             // 写入int:
            oos.writeInt(12345);
            // 写入String:
            oos.writeUTF("云创动力");
            // 写入Object:
            oos.writeObject(Double.valueOf(123.456));
            System.out.println(Arrays.toString(bos.toByteArray()));
            System.out.println(bos.toString());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
​
}

ObjectOutputStream既可以写入基本类型,如intboolean,也可以写入String(以UTF-8编码),还可以写入实现了Serializable接口的Object

因为写入Object时需要大量的类型信息,所以写入的内容很大

对象序列化

创建一个Student类,并实现Serializable接口

package com.itlaobing.demo.seri;
​
import java.io.Serializable;
​
public class Student implements Serializable{
    
    private String name;
    
    private double score;
    
    private String gender;
​
    public Student(String name, double score, String gender) {
        super();
        this.name = name;
        this.score = score;
        this.gender = gender;
    }
​
//省略getter/setter方法
}
​

例子:

package com.itlaobing.demo.seri;
​
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
​
public class ObjectOutputStreamTest2 {
​
    public static void main(String[] args) throws FileNotFoundException, IOException {
        
        try(FileOutputStream fos = new FileOutputStream("d:\\temp\\student.txt");ObjectOutputStream oos = new ObjectOutputStream(fos);){
            Student stu = new Student("尼古拉斯", 99.99, "男");
            oos.writeObject(stu);
        }
    }
​
}

思考:如果我Student类中的某个属性不想被序列化怎么办呢?

transient关键字,transient瞬态,修饰的成员,不会被序列化。

Student类修改为:

package com.itlaobing.demo.seri;
​
import java.io.Serializable;
​
public class Student implements Serializable{
    
    private String name;
    
    private double score;
    
    private transient String gender;
​
    public Student(String name, double score, String gender) {
        super();
        this.name = name;
        this.score = score;
        this.gender = gender;
    }
​
    //省略getter/setter
​
    @Override
    public String toString() {
        return "Student [name=" + name + ", score=" + score + ", gender=" + gender + "]";
    }
}
​

序列化:

package com.itlaobing.demo.seri;
​
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
​
public class ObjectOutputStreamTest3 {
​
    public static void main(String[] args) throws IOException {
        Student stu = new Student("尼古拉斯", 99.99, "男");
        System.out.println("序列化前: " + stu);
        try(FileOutputStream fos = new FileOutputStream("d:\\temp\\student2.txt");ObjectOutputStream oos = new ObjectOutputStream(fos);){
            oos.writeObject(stu);
        }
    }
​
}

ObjectInputStream

java.io.ObjectOutputStream相反,java.io.ObjectInputStream负责从一个字节流读取Java对象。将之前使用ObjectOutputStream序列化的原始数据恢复为对象.

public class ObjectInputStream
    extends InputStream implements ObjectInput, ObjectStreamConstants

构造方法

ObjectInputStream有一个public修饰的构造方法

//创建一个指定InputStream的ObjectInputStream
public ObjectInputStream(InputStream in);

反序列化

如果能找到一个对象的class文件,我们可以进行反序列化操作,调用 ObjectInputStream 读取对象的方法。

D:\\temp\\student.txt反序列化成对象。

import java.io.FileInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;

public class ObjectInputStreamTest {

	public static void main(String[] args) throws ClassNotFoundException, IOException {
		try(FileInputStream in = new FileInputStream("D:\\temp\\student2.txt");
				ObjectInputStream ois = new ObjectInputStream(in)){
			Object obj = ois.readObject();
			System.out.println(obj.getClass());//class com.itlaobing.demo.Student
			if(obj instanceof Student) {
				Student stu = (Student) obj;
				System.out.println(stu);
			}
		}		
	}
}

反序列化过程中,将字节序列转换成了对象,这个对象的创建没有经过构造方法。这个对象中的域(属性)的值是固定的,如果是transient声明的域(属性)值是其类型的默认值。

对于JVM可以反序列化对象,它必须是能够找到class文件的类。如果找不到该类的class文件,则抛出一个ClassNotFoundException 异常。

另外,当JVM反序列化对象时,能找到class文件,但是class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出一个 InvalidClassException异常。

为了避免这种class定义变动导致的不兼容,Java的序列化允许class定义一个特殊的serialVersionUID静态常量,用于标识Java类的序列化“版本”,通常可以由IDE自动生成。如果增加或修改了字段,可以改变serialVersionUID的值,这样就能自动阻止不匹配的class版本:

// 加入序列版本号
private static final long serialVersionUID = 1L;

这个时候序列化完成后,修改类后,可以反序列化。

要特别注意反序列化的几个重要特点:

反序列化时,由JVM直接构造出Java对象,不调用构造方法,构造方法内部的代码,在反序列化时根本不可能执行。

安全性

因为Java的序列化机制可以导致一个实例能直接从byte[]数组创建,而不经过构造方法,因此,它存在一定的安全隐患。一个精心构造的byte[]数组被反序列化后可以执行特定的Java代码,从而导致严重的安全漏洞。

实际上,Java本身提供的基于对象的序列化和反序列化机制既存在安全性问题,也存在兼容性问题。更好的序列化方法是通过JSON这样的通用数据结构来实现,只输出基本类型(包括String)的内容,而不存储任何与代码相关的信息。

json

json 对象:[包惠对象内容,对象的每个内容使用key : vlaue形式表示,多个属性使用,分割。对象的“key~是字符串类型的,~value~可以是字符串、数值、布尔、json对象、json数组

json数组:[]“包裹数组内容,数组的每个内容使用一,分割,数组的元素可以是字符串、数值、布尔、json对象、json数组.

java 中常见的解析 json 的第三方库(jar包):

  • GSON

  • Fastjson

  • Jackson2

  • hutool-json

注意: 13

  • 此时,我们深克隆实现就有三种了:

  • 自定义 clone 实现 [麻烦死了]

    • 将引用数据类型的字段也克隆一份

  • 使用序列化实现+I

    • 将对象序列化为字节流,再反序列化为对象

  • 通过JSON,使用第三方库实现 [推荐]

    • 使用第一方库实现深克隆,比如: GSON、Fast]son、Jackson 2、hutool-json

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值