[重学Java基础][Java IO流][Part.11] 序列化与序列化输入输出流

[重学Java基础][Java IO流][Part.11] 序列化与序列化输入输出流

序列化与反序列化

概述

序列化:指把堆内存中的 Java 对象数据,通过某种方式把对象存储到磁盘文件中或者传递给其他网络节点。这个过程称为序列化。通俗来说就是将数据结构或对象转换成二进制串的过程   反序列化:把磁盘文件中的对象数据或者把网络节点上的对象数据,恢复成Java对象模型的过程。也就是将在序列化过程中所生成的二进制串转换成数据结构或者对象的过程 Java 序列化有许多方案,除了JDK自带的外,有众所周知的JSON,谷歌出品性能强大的ProtocolBuffer/ProtocolStaff,脸书出品远程调用全家桶一部分的Apache Thrift,大数据领域Spark的好兄弟Apache Kyro等等。各有各的好处,

但JDK自带的序列化工具是我们必须掌握的。 Java自带的是ObjectOutputStream类的对象用于序列化对象。ObjectInputStream类的对象用于反序列化一个对象。ObjectOutputStream继承自OutputStream。ObjectInputStream继承自InputStream。类必须实现Serializable或Externalizable接口以便序列化或反序列化。

序列化标记

Serializable接口

public interface Serializable {
}

目标对象实现Serializable接口,则此对象即可以被序列化。
Serializable接口为空接口,并无实际方法需要继承类进行实现,是一个典型的标机接口(Marker interface pattern).
使用transient关键字修饰的类成员变量 在序列化时不会被写入到序列化目标中去。

Externalizable接口

public interface Externalizable extends Serializable {
void writeExternal(ObjectOutput var1) throws IOException;   
void readExternal(ObjectInput var1) throws IOException, ClassNotFoundException;
}

Externalizable接口继承自Serializable接口,并添加了两个方法 writeExternal(ObjectOutput out)和readExternal(ObjectInput in)
writeExternal(ObjectOutput out)方法指定要保存的属性信息,对象序列化时调用
readExternal(ObjectInput in)方法读取被保存的信息,对象反序列化时调用
相比Serializable接口,实现了Externalizable接口的类可以通过调用writeExternal和readExternal方法实现自定义的序列化和反序列化操作,
进行更细粒度的操作序列化内容

一般是这么使用的

serialVersionUID

凡是实现Serializable接口的类都有一个表示序列化版本标识符的静态变量:

public class Test1 implements Serializable {

private static final long serialVersionUID = 8103308523877435576L;

}

如果不显示的指定,serialVersionUID的取值是Java运行时环境根据类的内部细节自动生成的。如果对类的源代码作了修改,再重新编译,新生成的类文件的serialVersionUID的取值有可能也会发生变化。
类的serialVersionUID的默认值完全依赖于Java编译器的实现,对于同一个类,用不同的Java编译器编译,有可能会导致不同的serialVersionUID,也有可能相同。为了提高使用serialVersionUID的独立性和确定性,强烈建议在一个可序列化类中显示的定义serialVersionUID,为它赋予明确的值。显式地定义serialVersionUID有两种用途:

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

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

代码示例

Serializable序列化
序列化类User
	public class User implements Serializable {
	
	    private String name;   
		private int age;  
 		private SuperUser superUser;   

		public User(String name, int age) {
	        this.name = name;
	 		this.age = age;
			System.out.println("调用User构造函数");
	  }
	
	    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 SuperUser getSuperUser() {
	        return superUser;
	  }
	
	    public void setSuperUser(SuperUser superUser) {
	        this.superUser = superUser;
	  }
	
	    @Override
	  public String toString() {
	        return "tv.acgmoe.iostreamdemo.User{" +
	                "name='" + name + '\'' +
	                ", age=" + age +
	                ", superUser=" + superUser +
	                '}';
	  }
	}

User内部引用的对象

	public class SuperUser implements Serializable {
	private int Code;   
	private String clerk;   
public SuperUser(int code, String clerk) {
	        Code = code;
	 this.clerk = clerk;
System.out.println("调用SuperUser构造函数");
	  }
	
	    public int getCode() {
	        return Code;
	  }
	
	    public void setCode(int code) {
	        Code = code;
	  }
	
	    public String getClerk() {
	        return clerk;
	  }
	
	    public void setClerk(String clerk) {
	        this.clerk = clerk;
	  }
	
	    @Override
	  public String toString() {
	        return "SuperUser{" +
	                "Code=" + Code +
	                ", clerk='" + clerk + '\'' +
	                '}';
	  }
	}

主方法

	public static void main(String[] args) 
throws IOException, ClassNotFoundException {
	
	    ByteArrayOutputStream baos=new ByteArrayOutputStream();
	  	ObjectOutputStream ous=new ObjectOutputStream(baos);    
		User user=new User("user",30);
	  	SuperUser superUser=new SuperUser(95258,"Power"); 

   	  user.setSuperUser(superUser);
	  ous.writeObject(user);
	  ous.flush();
	  ous.close();    
ByteArrayInputStream bais=new ByteArrayInputStream(baos.toByteArray());
	  ObjectInputStream ois=new ObjectInputStream(bais);    
User inUser=(User)ois.readObject();    System.out.println(inUser.toString());   
}

运行结果

调用User构造函数
调用SuperUser构造函数
tv.acgmoe.iostreamdemo.User{name='user', age=30, superUser=SuperUser{Code=95258, clerk='Power'}}

反序列化时并不调用序列化对象的构造方法

Externalizable序列化

前面说过 Serializable默认的序列化机制非常简单,而且序列化后的对象不需要再次调用构造器重新生成,但是在实际中,我们可以自助控制对象的序列化,比如说一个对象被还原之后,其内部的某些子对象需要符合一些约束。 在这些情况下,我们可以考虑实现Externalizable接口从而代替Serializable接口来对序列化过程进行控制

Externalizable接口继承自Serializable接口,而且在其基础上增加了两个方法:writeExternal()和readExternal()。这两个方法会在序列化和反序列化还原的过程中被自动调用,以便执行一些特殊的操作。

public class TsetExtSer implements Externalizable {

    private static final long serialVersionUID = -4009470851722923167L;
    private int i ;

    private String str;

    public TsetExtSer() {
        //默认无参构造函数必须有,而且必须是public 否则使用Externalizable反序列化会抛异常
        System.out.println("调用 TsetExtSer 默认无参构造函数");
    }

    public TsetExtSer(String str, int i) {
        //str,i只是在带参数的构造函数中进行初始化。
        System.out.println("调用 TsetExtSer 带参数构造函数");
        this.str = str;
        this.i = i;
    }

    @Override
    public String toString() {
        return "TsetExtSer{" +
                "i=" + i +
                ", str='" + str + '\'' +
                '}';
    }

    @Override
    public void readExternal(ObjectInput in) throws IOException,
            ClassNotFoundException {
        System.out.println("调用readExternal()方法");
        //在反序列化时,需要从序列化数据源中获取字段值,否则只是调用默认构造函数,得不到str和i的值
        str = (String)in.readObject();
        i = in.readInt();
    }

    @Override
    public void writeExternal(ObjectOutput out) throws IOException {
        System.out.println("调用writeExternal()方法");
        //Externalizable对象默认不写入任何字段
        //所以必须把str和i写入到序列化目标中,否则在反序列化的时候,就不会得到这些值
        out.writeObject(str);
        out.writeInt(i);
    }

}

运行主函数

public static void main(String[] args) throws IOException, ClassNotFoundException {
        System.out.println("初始化测试数据");
        TsetExtSer tsetExtSer = new TsetExtSer("This String is " , 47);
        System.out.println(tsetExtSer);

        System.out.println("进行序列化,writeObject");
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(out);
        oos.writeObject(tsetExtSer);
        System.out.println("进行反序列化,readObject");
        ByteArrayInputStream in = new ByteArrayInputStream(out.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(in);
        TsetExtSer inTestStr = (TsetExtSer)ois.readObject();
        System.out.println(inTestStr);
    }

运行结果

初始化测试数据
调用 TsetExtSer 带参数构造函数
TsetExtSer{i=89757, str='测试字符串 '}
进行序列化,writeObject
调用writeExternal()方法
进行反序列化,readObject
调用 TsetExtSer 默认无参构造函数
调用readExternal()方法
TsetExtSer{i=89757, str='测试字符串 '}

可以看到 使用Externalizable进行反序列化时会调用序列化对象的无参构造函数

序列化时的静态变量


            //staticValue为5
            ObjectOutputStream oos = new ObjectOutputStream(
                    new FileOutputStream(new File("D://a.ser")));
            oos.writeObject(new Test());
            oos.close();

            //序列化后修改为10
            Test.staticValue = 10;

            ObjectInputStream oin = new ObjectInputStream(new FileInputStream(
                    "D://a.ser"));
            Test t = (Test) oin.readObject();
            oin.close();

            //再读取,通过t.staticValue打印新的值
            System.out.println(t.staticValue);

将对象序列化后,修改静态变量的数值,再将序列化对象读取出来,然后通过读取出来的对象获得静态变量的数值并打印出来。

10

最后的输出是 10,如果打印的 staticValue是从读取的对象里获得的,应该是保存时的状态才对。之所以打印 10 的原因在于序列化时,并不保存静态变量,这其实比较容易理解,序列化保存的是对象的状态,静态变量属于类的状态,因此 序列化并不保存静态变量

序列化输入输出流

ObjectInputStream

概述

源码分析

可以看到ObjectInputStream的方法时真的非常多 公有部分就有这么多了
但是仔细观察后 发现主要是各种不同的读取方法 对应了Java所有的数据类型

成员属性

	空对象标志
    private static final int NULL_HANDLE = -1;
	
    private static final Object unsharedMarker = new Object();
    private static final Map<String, Class<?>> primClasses=
		Map.of("boolean", boolean.class,
		  "byte", byte.class,
		  "char", char.class,
		  "short", short.class,
		  "int", int.class,
		  "long", long.class,
		  "float", float.class,
		  "double", double.class,
		  "void", void.class);
    private final ObjectInputStream.BlockDataInputStream bin;
    private final ObjectInputStream.ValidationList vlist;
    private long depth;
    private long totalObjectRefs;
    private boolean closed;
    private final ObjectInputStream.HandleTable handles;
    private int passHandle = -1;
    private boolean defaultDataEnd = false;
    private final boolean enableOverride;
    private boolean enableResolve;
    private SerialCallbackContext curContext;
    private ObjectInputFilter serialFilter;
    private static final Unsafe UNSAFE;

内部类

和其他的读写类不同 ObjectInputStream 有很多的内部类

成员方法

构造函数

ObjectOutputStream

概述

和ObjectInputStream类似 ObjectInputStream的方法时真的非常多
也主要是各种不同的写入方法 对应了Java所有的数据类型

源码分析

成员属性

内部类

和其他的读写类不同 ObjectOutputStream 有很多的内部类

    private static class Caches
    public static abstract class PutField
    private class PutFieldImpl extends PutField
    private static class BlockDataOutputStream extends 
	OutputStream implements DataOutput
    private static class HandleTable
    private static class ReplaceTable
    private static class DebugTraceInfoStack 

  • Cache
    private static class Caches {
        /** cache of subclass security audit results */
        static final ConcurrentMap<WeakClassKey,Boolean> subclassAudits =
            new ConcurrentHashMap<>();

        /** queue for WeakReferences to audited subclasses */
        static final ReferenceQueue<Class<?>> subclassAuditsQueue =
            new ReferenceQueue<>();
    }

内部包括两个静态不可变成员 subclassAudits 和 subclassAuditsQueue
subclassAudits 从名字上看是子类的统计信息 从注释可知subclassAudits 保存的是所有子类的代码执行安全性检测结果 是一个线程安全的散列表 键为WeakClassKey型的弱引用键,值为布尔型

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值