Java基础-数据序列化之序列化框架的使用

为什么要序列化?

现在开发过程中经常遇到多个进程多个服务间需要交互,或者不同语言的服务之间需要交互,这个时候,我们一般选择使用固定的协议,将数据传输过去,但是在很多语言,比如java等jvm语言中,传输的数据是特有的类对象,而类对象仅仅在当前jvm是有效的,传递给别的jvm或者传递给别的语言的时候,是无法直接识别类对象的,那么,我们需要多个服务之间交互或者不同语言交互,该怎么办?这个时候我们就需要通过固定的协议,传输固定的数据格式,而这个数据传输的协议称之为序列化,而定义了传输数据行为的框架组件也称之为序列化组件(框架)。
序列化框架中会包含两种操作,一种将对象或数据结构转换为特定格式(传输数据协议),我们称之为序列化;另一种是将特定格式转换为对象或数据结构的过程,我们称之为反序列化。

序列化的目的

  • 使数据可以在网络中传输,或者可存储在内存或文件中。
  • 使数据可以脱离应用程序而独立存在。

序列化使用场景

不同服务间的交互,服务可以同语言也可以不同语言。

Java语言内置序列化

序列化的实现可以分为文本序列化、语言内置序列化和跨语言序列化,本文主要讲述Java语言内置序列化。
每种语言基本都会有实现的序列化框架,此序列化的方式我们称之为语言内置序列化,其优点是语言提供了序列化的框架(序列化协议,序列化和反序列化操作),方便使用,缺点是交互的应用程序必须是同一种语言。
下面将讲述Java需要内置序列化框架的使用,Java序列化协议是字节序列(由jvm实现)。

序列化框架使用的核心

  • 序列化接口(Serializable和Externalizable)

    需要序列化的类对象需要继承此接口,只有当前接口修饰定义的类对象才可以按照指定的方式传输对象。

  • 序列化id-serialVersionUID

    用来作为传输/读取双端进程(javaBean)的版本是否一致的,防止我们因为版本不一致导致的序列化失败。
    编译器提供两种序列化id的方式,一种是生成默认的versionID,这个值为1L,还有一种方式是根据类名、接口名、成员方法及属性等来生成一个 64 位的哈希字段,只要我们类名、方法名、变量有修改,或者有空格、注释、换行等操作,计算出来的哈希字段都会不同,当然这里需要注意,每次我们有以上的操作的时候尽量都要重新生成一次serialVerionUID(编译器并不会给你自动修改)。
    默认固定值1L和64位哈希数值不需要自己计算,继承了序列化接口的类,如果没有序列化id字段,在类名下会显示黄色的波浪线,鼠标放置在类名上,选择需要的方式即可自动添加序列化id字段(如有变动,删除后再根据提示添加即可)。

  • 对象序列化和数据写入操作(输出流java.io.ObjectOutputStream)

    用于执行序列化操作,并将序列化后的数据写入文件或进行传输。

  • 数据读取和对象反序列化操作(输入流java.io.ObjectInputStream)

    用于执行反序列化操作,将文件或从网络接收到的数据反序列化为对象。

案例1-基础序列化和反序列化Serializable

下面以序列化对象存储入文件后,读取文件再使用对象为例,讲解序列化框架的使用,此使用同序列化数据后进行传输,因为无论是数据传输还是存取文件,都需要用到输入和输出流,只是获取输入输出流的参数不一致。

Person类实现
public class Person implements Serializable {
	//64位哈希序列化ID
	private static final long serialVersionUID = 8247451571741032209L;
	private String name;
	private int age;
	private transient boolean isMan; // true=男,false=女

	public Person(String name, int age, boolean isMan) {
		this.name = name;
		this.age = age;
		this.isMan = isMan;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}

	public boolean isMan() {
		return isMan;
	}

	public void setMan(boolean isMan) {
		this.isMan = isMan;
	}
	
	/*此方法只是用于打印,与序列化无关*/
    public String toString() {
        return "Person{" + "name=" + name  + ",age = " + age+",isMan = " + isMan + "}";
    }

}
序列化操作
public static void writeObject() {
		try {
			// 0. 创建一个ObjectOutputStream输出流
			ObjectOutputStream oos = new ObjectOutputStream(
					new FileOutputStream("E:/object.txt"));
			// 1. 将对象序列化到文件s
			Person person = new Person("lilu", 18, true);
			oos.writeObject(person);
			System.out.println("Person对象已写入object.txt文件");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
反序列化操作
public static void readObject() {
		try {
			// 0. 创建ObjectInputStream
			ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
					"E:/object.txt"));
			Person person1 = (Person) ois.readObject();
			System.out.println("读取到的Person对象数据如下:");
			System.out.println(person1);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
main函数调用
public static void main(String[] args) {
		writeObject();
		readObject();
	}
运行结果

在这里插入图片描述

Java序列化使用的总结

  • Java 序列化只是针对对象的属性(对象的类名,变量的类型、变量的数据)的传递,至于方法和序列化过程无关。
  • 类中的某个成员变量不需要序列化,使用transient声明该字段。
  • 类静态变量(static修饰的变量)不会被序列化。
  • 成员是引用的序列化,这个引用类型对应的类也必须是可序列化的。
  • 序列化具有传递性-当一个父类实现了序列化,那么子类会自动实现序列化,不需要显示实现序列化接口,反过来,子类实现序列化,而父类没有实现序列化则序列化会失败。 同一对象序列化多次,不会多次序列化同一对象,而是不管进行多少次序列化操作,只会得到同一个对象。

扩展

  • 自定义序列化,重写writeObject 和 readObject
  • 继承接口Externalizable
  • 序列化框架的源码详解
  • 跨语言序列化的实现

参考链接

java序列化,看这篇就够了!
详解JAVA序列化
菜鸟教程-Java序列化

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章