• Java基础(十一)--Serializable和Externalizable接口实现序列化


      序列化在日常开发中经常用到,特别是涉及到网络传输的时候,例如调用第三方接口,通过一个约定好的实体进行传输,这时你必须实现序列

    化,这些都是大家都了解的内容,所以文章也会讲一下序列化的高级内容。

    序列化与反序列化简单认知:

      我们知道,对象在不具有可达性的时候,会被GC,这些对象都是保存在堆中,而现实中,我们可能需要将对象进行持久化,并且在需要的时候

    进行读取转换,这就是序列化的工作。

    1、序列化: 

      将一个对象转换成字节流或者说是字节数组,并且可以存储或传输的形式的过程。

      存储:可以把一个对象存储到文件、数据库等

      网络传输:可以转化成字节或XML进行网络传输

    2、反序列化:

      和序列化是一个相反的过程,在需要的时候,把字节数组转化成对象。

    序列化广泛应用于远程调用等所有涉及网络传输的地方

    序列化相关接口:

      Serializable、Externalizable、ObjectOutput、ObjectInput、ObjectOutputStream、ObjectInputStream

    Serializable接口:

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @ToString
    public class Student implements Serializable {
    
        private int id;
        private String name;
        private int sex;
        private transient String addr;
    } 
    public static void main(String[] args) throws Exception{
    	Student student = new Student(1001, "sam", 1, "SH");
    	File file = new File("D:\a.txt");
    	FileOutputStream fileOutputStream = new FileOutputStream(file);
    	ObjectOutputStream outputStream = new ObjectOutputStream(fileOutputStream);
    	outputStream.writeObject(student);
    	outputStream.close();
    
    	ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(file));
    	Student student1 = (Student)inputStream.readObject();
    	System.out.println(student1.toString());
    	inputStream.close();
    }
    

    结果:

    Student(id=1001, name=sam, sex=1, addr=null)
    

      我们通过Binary Viewer查看这个二进制文件a.txt,下面二进制内容解释参考自:https://www.cnblogs.com/xrq730/p/4821958.html

    第一部分:序列化文件头

      1、AC ED:STREAM_MAGIC序列化协议

      2、00 05:STREAM_VERSION序列化协议版本

      3、73:TC_OBJECT声明这是一个新的对象

    第二部分:序列化的类的描述,在这里是Student类

      1、72:TC_CLASSDESC声明这里开始一个新的class

      2、00 18:十进制的24,表示class名字的长度是24个字节

      3、63 6F 6D ... 6E 74 20:表示的是“com.it.exception.Student”这一串字符,可以数一下确实是24个字节

      4、00 00 00 00 00 00 00 01:SerialVersion,序列化ID,1

      5、02:标记号,声明该对象支持序列化

      6、00 03:该类所包含的域的个数为3个

    第三部分:是对象中各个属性项的描述

      1、49:int类型

      2、00 02:十进制的2,表示字段长度

      3、69 64:表示字段id

      4、49:int类型

    省略了sex、name属性,可以自行查看

      5、74:TC_STRING,代表一个new String,用String来引用对象

    第四部分:该对象父类的信息,如果没有父类就没有这部分。有父类和第2部分差不多

      1、00 12:十进制的18,表示父类的长度

      2、4C 6A 61 ... 6E 67 3B:“L/java/lang/String;”表示的是父类属性

      3、78:TC_ENDBLOCKDATA,对象块结束的标志

      4、70:TC_NULL,说明没有其他超类的标志

    第五部分:输出对象的属性项的实际值,如果属性项是一个对象,这里还将序列化这个对象,规则和第2部分一样

      1、00 03:十进制的3,属性的长度

      2、73 61 6D:字符串"sam",name的属性值

    以上是二进制文件的解析,可以得出结论:

      1、序列化之后保存的是对象的信息

      2、被声明为transient的属性不会被序列化,这就是transient关键字的作用,addr字段并没有保存

    所以,我们得出结论,static字段也不会被序列化,因为static变量属于类的

    Externalizable接口:

    public interface Externalizable extends java.io.Serializable {
    
        void writeExternal(ObjectOutput out) throws IOException;
            restored cannot be found.
    
        void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
    }
    

    使用样例:

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @ToString
    public class Student implements Externalizable{
    
        private static final long serialVersionUID = 1L;
    
        private int id;
        private String name;
        private int sex;
    
        @Override
        public void writeExternal(ObjectOutput out) throws IOException {
            out.writeObject(id);
            out.writeObject(name);
            out.writeObject(sex);
        }
    
        @Override
        public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
            id = (Integer)in.readObject();
            name = (String)in.readObject();
            sex = (Integer)in.readObject();
        }
    }
    public class Test {
    
        public static void main(String[] args) throws Exception{
            Student student = new Student(1001, "sam", 1);
            File file = new File("D:\a.txt");
            FileOutputStream fileOutputStream = new FileOutputStream(file);
            ObjectOutputStream outputStream = new ObjectOutputStream(fileOutputStream);
            outputStream.writeObject(student);
            outputStream.close();
    
            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream(file));
            Student student1 = (Student)inputStream.readObject();
            System.out.println(student1.toString());
            inputStream.close();
        }
    }
    

    结果:

    Student(id=1001, name=sam, sex=1)
    

    从结果看Externalizable接口同样可以实现序列化和反序列化,但是有些地方不太一样,需要注意

      1、相关类必须有默认构造器,否则会抛出异常,是因为在读取对象时,会调用被序列化类的无参构造器去创建一个新的对象,然后再将被

    保存对象的字段的值分别填充到新对象中。

      2、需要重写writeExternal和readExternal方法,去控制序列化,并且写入字段顺序和读取顺序要保持一致,写入和读取支持多种类型,不必

    一定使用object,这样不用类型转换

    自定义序列化:

      我们使用序列化的时候,一般情况都是使用默认的方式,而如果在一些特殊场景下我们需要进行特殊处理,例如字段加密,因为序列化是不安

    全的。

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @ToString
    public class Student implements Serializable{
    
        private static final long serialVersionUID = 1L;
    
        private int id;
        private String name;
        private int sex;
    
        private void writeObject(ObjectOutputStream outputStream) throws Exception {
            outputStream.defaultWriteObject();
            outputStream.writeBoolean(true);
        }
    
        private void readObject(ObjectInputStream inputStream) throws Exception {
            inputStream.defaultReadObject();
            boolean flag = inputStream.readBoolean();
            System.out.println("flag: " + flag);
        }
    }
    

    测试代码不变

    flag: true
    Student(id=1001, name=sam, sex=1)
    

    从代码上看和Externalizable接口几乎一样的,通过writeObject()和readObject()实现自定义的过程

    原因:

      虚拟机会首先试图调用对象里的writeObject()和readObject(),进行用户自定义的序列化和反序列化。如果没有这样的方法,那么默认调用的

    是ObjectOutputStream的defaultWriteObject以及ObjectInputStream的defaultReadObject方法

    我们在查看jdk集合的源码中可以看到,ArrayList、HashMap等在实现序列化的时候,都是自定义writeObject()和readObject()的

    PS:虚拟机通过反射来调用writeObject()和readObject()

    ArrayList序列化:

    public class ArrayList<E> extends AbstractList<E>
    		implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
    
    	transient Object[] elementData; //通过数组保存集合数据
    
    	private void readObject(java.io.ObjectInputStream s)
    			throws java.io.IOException, ClassNotFoundException {
    		elementData = EMPTY_ELEMENTDATA;
    
    		s.defaultReadObject();
    
    		s.readInt(); // ignored
    
    		if (size > 0) {
    			ensureCapacityInternal(size);
    
    			Object[] a = elementData;
    			// Read in all elements in the proper order.
    			for (int i=0; i<size; i++) {
    				a[i] = s.readObject();
    			}
    		}
    	}
    	private void writeObject(java.io.ObjectOutputStream s)
    			throws java.io.IOException{
    		int expectedModCount = modCount;
    		s.defaultWriteObject();
    
    		// Write out size as capacity for behavioural compatibility with clone()
    		s.writeInt(size);
    
    		// Write out all elements in the proper order.
    		for (int i=0; i<size; i++) {
    			s.writeObject(elementData[i]);
    		}
    
    		if (modCount != expectedModCount) {
    			throw new ConcurrentModificationException();
    		}
    	}
    }

    ArrayList这样实现的目的:

      ArrayList是动态数组,数组中的数据在达到阀值就会扩容,如果数组扩容后长度设为100,而里面只存放了10条数据,那就会序列化90个

    null元素。为了保证在序列化的时候不会将这么多null同时进行序列化,ArrayList把元素数组设置为transient,然后通过遍历数组讲数据进行

    序列化和反序列化

    总结:

      1、在Java中,只要一个类实现了Serializable接口,那么它就可以被序列化

      2、通过ObjectOutputStreamObjectInputStream对对象进行序列化及反序列化

      3、当父类继承Serializable接口,所有子类都可以被序列化

      4、子类实现了Serializable接口,如果想要父类的属性也能实现序列化,必须父类也实现Serializable 接口,否则父类中的属性不能序列

    化(不报错,数据丢失),但是在子类中属性仍能正确序列化

      5、如果序列化的属性是对象,则这个对象也必须实现Serializable接口,否则会报错  

      6、序列化并不保存静态变量

      7、反序列化能否成功,要求:①.类路径相同,②.序列化ID保持一致(serialVersionUID),否则无法成功

      8、反序列化时,如果对象的属性有修改或删减,则修改的部分属性会丢失,但不会报错

      5、序列化数据如果比较敏感,可以采用加密的方式,增加一定安全性

     

    内容参考:

      https://www.cnblogs.com/xrq730/p/4821958.html

      http://www.importnew.com/18024.html

      https://www.ibm.com/developerworks/cn/java/j-lo-serial/

  • 相关阅读:
    高性能的序列化与反序列化:kryo的简单使用
    C/C++读写csv文件(用getline探测逗号分隔符)
    VS2012中使用CEGUI项目发布到XP平台的问题(核心方法就一句话。“你项目使用的所有外部依赖库都用/MT编译。”)
    C/C++使用libcurl库发送http请求(get和post可以用于请求html信息,也可以请求xml和json等串)
    C/C++使用openssl进行摘要和加密解密(md5, sha256, des, rsa)
    DOM解析xml实现读、写、增、删、改
    Go and JSON
    HTTP2.0
    全球化与本地化
    远程调试 Azure 上的 Website
  • 原文地址:https://www.cnblogs.com/huigelaile/p/11031341.html
Copyright © 2020-2023  润新知