精华内容
下载资源
问答
  • 主要介绍了Java序列化和反序列化的相关资料,帮助大家更好的理解学习Java,感兴趣的朋友可以了解下
  •  Java中的序列化(serialization)机制能够将一个实例对象的状态信息写入到一个字节流中,使其可以通过socket进行传输、或者持久化存储到数据库或文件系统中;然后在需要的时候,可以根据字节流中的信息来重构一个...
  • 主要介绍了Java 序列化和反序列化实例详解的相关资料,需要的朋友可以参考下
  • java序列化和反序列化

    千次阅读 2018-07-20 18:26:41
    一:概念 序列化:将对象转化为二进制数据(字节序列)的过程成为序列化;... serialVersionUID:需要序列化的对象的成员属性,表示该对象的序列化版本id,反序列化的接收对象的serialVersionUID必须保持和序列化对...

    一:概念

    1. 序列化:将对象转化为二进制数据(字节序列)的过程成为序列化;
    2. 反序列化:将二进制数据(字节序列)恢复为对象的过程称为反序列化;
    3. transient:修饰需要序列化的对象的某些属性,使得被修饰的属性不会被序列化和反序列化;
    4. serialVersionUID:需要序列化的对象的成员属性,表示该对象的序列化版本id,反序列化的接收对象的serialVersionUID必须保持和序列化对象的serialVersionUID一致,这样才能正常反序列化;但是这点我的实践验证中好像没得到验证,经过大量询问和查阅资料,好像说这点已经被废弃了,即序列化和反序列化已经不关心该值。如果有答案的读者可以帮我答疑解惑下哈

    :应用场景

    1. 将对象保存到文件中,即存到电脑硬盘中,这时就要使用到序列化;
    2. mybatis查询缓存启用二级缓存的时候,所映射的pojo对象需要实现序列化,为了将缓存数据取出来进行反序列化;因为mybatis二级缓存的介质多种多样,不一定存在内存中;
    3. 通过网络传输对象的时候,就一定要用到序列化,将对象转成二进制的字节序列才能在网络上传输;而这种应用场景就是典型的分布式框架当中;分布式框架就是两台不同的物理机子之间的接口调用,数据在网络上从服务提供传输到服务消费者者;被笔者这边也是以此为应用场景进行序列化和反序列化的解释;

    :详细案例

          本次案例当中,我的应用场景就是基于dubbo+zookeeper为框架的分布式框架(不理解该框架的读者可以自己百度了解下或者略过该框架),该框架就是两个服务(可以理解为两个不同的项目之间的通信,而这两个项目可以在不同的电脑上运行)之间的调用,需要在网络上将数据转为二进制数据在网络上传输,如果没转为二进制的字节数据,则无法实现传输,如下图,是两个不同的服务:

    服务提供者                                                                                图一:服务提供者

                                                                                      图二:服务消费者

    可以看到消费者:framework项目调用提供者项目course-south项目的接口,返回的数据对象如下图:

    t                                                                                           图三:数据对象

         该数据对象正确实现了序列化接口Serializable,所以可以正常调用,调用结果如下:

                                                                                         图四:服务端输出

                                                                                          图五:消费端接收数据

         说明返回的对象实现了序列化接口,数据传输没问题;如果没实现序列化接口呢?如下图:

                                                                                              图六:异常

           可以看到,当我没有实现序列化接口的时候,服务端查询的数据无法通过网络传输到消费者,所以,证明分布式框架正是序列化和反序列化的一个重要应用场景;

           java关键字中有一个和序列化及反序列化关系密切,那就是transient

           如果我们的返回的dto有些字段不想进行在网络上进行传输,那这时候就需要将该字段加上transient修饰,所以transient的作用就是所修饰的属性不会进行序列化和反序列化,可能细心的读者就可以发现,在图四和图五当中,图四的输出结果当中的id是有值的,而图五的接收结构的id是没值的;这就是因为我们在返回的对象的id属性上使用了transient修饰,所以提供者在序列化的时候忽略了id的序列化,而消费者在进行反序列化的时候,得到的id值为null,返回数据的对象为图三;

    展开全文
  • 把对象转换为字节序列的过程称为对象的序列化,把字节序列恢复为对象的过程称为对象的序列化。接下来通过本文给大家介绍Java序列化和反序列化及主要的两种用途,感兴趣的的友参考下吧
  • 对象序列化(serialization)和反序列化(deserialization)是将对象转化为便于传输的格式进行发送接收的两个操作。 哪些东西可以是字节?图片可以是字节,文件可以是字节,一个字符串也可以是字节,嗯,宇宙间的...

    对象序列化(serialization)反序列化(deserialization)是将对象转化为便于传输的格式进行发送和接收的两个操作。

    哪些东西可以是字节?图片可以是字节,文件可以是字节,一个字符串也可以是字节,嗯,宇宙间的一切事物都可以用字节表示。当然,对象也可以是字节。java的序列化就是将对象转化为字节流,以便在进程或网络之间进行传输,而在接收方,需要以相同的方式对字节流进行反序列化,得到传输的对象。

    1,实现Serializable接口

    package com.java;
     
    import java.io.Serializable;
    import java.util.ArrayList;
    import java.util.List;
     
    public class Player implements Serializable{
     
    	public Player(long playerId,  int age, String name) {
    		this.playerId = playerId;
    		this.age = age;
    		this.name = name;
    	}
    	
    	private long playerId;
    	
    	private int age;
    	
    	private String name;
    	
    	private List<Integer> skills = new ArrayList<>();
    
        get() /set()方法
    }

    2.对象序列化,Java中通过对象流 ObjectOutputStream 进行序列化。

    public static byte[] serializable(Object out) throws Exception {
    		//用于序列化后存储对象
    		ByteArrayOutputStream byteArrayOutputStream = null;
    		//java序列化API
    		ObjectOutputStream objectOutputStream = null;
    		try {
    			byteArrayOutputStream = new ByteArrayOutputStream();
    			objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
    			//将out对象进行序列化
    			objectOutputStream.writeObject(out);
    			//测试验证输入(获取字节数组)
    			byte[] bs = byteArrayOutputStream.toByteArray();
    			//将数组转化为字符串输入
    			System.out.println(Arrays.toString(bs));
    			return bs;
    			
    		} catch (IOException e) {
    			e.printStackTrace();
    		}finally {
    			//关闭最外层的流(内部流会自动关闭)
    			objectOutputStream.close();
    		}
    		return null;
    	}

    3.反序列化为对象,Java中通过对象流 ObjectInputStream 进行序列化。

    public static player deserializable(byte[] bs) throws Exception {
    		//创建存放二进制数据的API
    		ByteArrayInputStream byteArrayInputStream = null;
    		//创建反序列化对象
    		ObjectInputStream objectInputStream = null;
    		try {
    			byteArrayInputStream = new ByteArrayInputStream(bs);
    			objectInputStream = new ObjectInputStream(byteArrayInputStream);
    			
    			//校验测试
    			Player player = (Player) objectInputStream.readObject();
    			System.out.println(player.toString());
    		} catch (IOException e) {
                player = null;
    			e.printStackTrace();
    		}finally {
    			objectInputStream.close();
    		}
            return player;
    	}
    

    本以为这样就可以了,但使用过程中会有一些问题出现,总结一些遇到的问题吧

    测试:

    public class TempTest {
        public static void main(String[] args) {
            CarData date= new CarData();
            Object value = new Object("a","123");
            system.out.println(Arrays.toString(serializable(value)));
            system.out.println(deserialzable(data.getString().getBytes()));
    
    }

    4.遇到的问题及解决方式;

    (1)打印字符串乱码

    其中String和byte[]转换:

      String string = "hello world";
      //Convert to byte[]
      byte[] bytes = string.getBytes();
             
      //Convert back to String
      String s = new String(bytes);

    输入一些数据测试,出现乱码打印结果:

    �� sr java.util.ArrayListx����a� I sizexp   w   sr com.alibaba.fastjson.JSONObject        L mapt Ljava/util/Map;xpsr java.util.HashMap���`� F 
    loadFactorI     thresholdxp?@           t at 123xx

    解决方法:

    原因:string利用 byte[] bytes = string.getBytes(); String s = new String(bytes);方法和bute[]互转时会出现丢失两字节识别码,所以发生乱码
    // 使用Base64解码
      System.out.println(deserializable(Base64.getDecoder().decode(value.getstring())));
    //value 是一个自定义复杂类型数据,通过getstring()内部方法得到string类型数据 
    打印结果:
    rO0ABXNyABNqYXZhLnV0aWwuQXJyYXlMaXN0eIHSHZnHYZ0DAAFJAARzaXpleHAAAAABdwQAAAABc3IAH2NvbS5hbGliYWJhLmZhc3Rqc29uLkpTT05PYmplY3QAAAAAAAAAAQIAAUwAA21hcHQAD0xqYXZhL3V0aWwvTWFwO3hwc3IAEWphdmEudXRpbC5IYXNoTWFwBQfawcMWYNEDAAJGAApsb2FkRmFjdG9ySQAJdGhyZXNob2xkeHA/QAAAAAAADHcIAAAAEAAAAAF0AAFhdAADMTIzeHg=
    [{"a":"123"}]

    (2)java.util.Base64报“java.lang.IllegalArgumentException: Illegal base64 character d”的问题

    原因:Base64是一种字符串编码格式,采用了A-Z,a-z,0-9,“+”和“/”这64个字符来编码原始字符(还有垫字符“=”)。一个字符本身是一个字节,也就是8位,而base64编码后的一个字符只能表示6位的信息。也就是原始字符串中的3字节的信息编码会变成4字节的信息。Base64的主要作用是满足MIME的传输需求。 
    在Java8中Base64编码已经成为Java类库的标准,且内置了Base64编码的编码器和解码器。
    
    Base64.getDecoder().decode() 修改为 Base64.getMimeDecoder().decode()
    

    (3)java.io.EOFException报空异常

    deSerializable()方法中的将构造ObjectInputStream 对象,放在捕获异常try{}catch{}外面

    (4)反序列化过程引发了 java.io.EOFException异常

    原因:可能是实现Serializable接口的时候重新实现了tostring()方法,这个格式写的不对,导致异常,最简单的方法就是用Serializable自己的toString()方法.

     

    以上,仅供个人学习记录.

     
    展开全文
  • java序列化和反序列化以及序列化ID的作用分析

    万次阅读 多人点赞 2017-08-07 20:26:07
    java序列化和反序列化以及序列化ID的作用分析

     java序列化和反序列化

    一、概念

           java对象序列化的意思就是将对象的状态转化成字节流,以后可以通过这些值再生成相同状态的对象。对象序列化是对象持久化的一种实现方法,它是将对象的属性和方法转化为一种序列化的形式用于存储和传输。反序列化就是根据这些保存的信息重建对象的过程。

           序列化:将java对象转化为字节序列的过程。

           反序列化:将字节序列转化为java对象的过程。


    二、为什么要序列化和反序列化

           我们知道,当两个进程进行远程通信时,可以相互发送各种类型的数据,包括文本、图片、音频、视频等, 而这些数据都会以二进制序列的形式在网络上传送。那么当两个Java进程进行通信时,能否实现进程间的对象传送呢?答案是可以的。如何做到呢?这就需要Java序列化与反序列化了。换句话说,一方面,发送方需要把这个Java对象转换为字节序列,然后在网络上传送;另一方面,接收方需要从字节序列中恢复出Java对象。当我们明晰了为什么需要Java序列化和反序列化后,我们很自然地会想Java序列化的好处。其好处一是实现了数据的持久化,通过序列化可以把数据永久地保存到硬盘上(通常存放在文件里),二是,利用序列化实现远程通信,即在网络上传送对象的字节序列。


    三、涉及到的javaAPI 

              java.io.ObjectOutputStream表示对象输出流,它的writeObject(Object obj)方法可以对参数指定的obj对象进行序列化,把得到的字节序列写到一个目标输出流中。

              java.io.ObjectInputStream表示对象输入流,它的readObject()方法源输入流中读取字节序列,再把它们反序列化成为一个对象,并将其返回。

             只有实现了Serializable或Externalizable接口的类的对象才能被序列化,否则抛出异常。


    四、序列化和反序列化的步骤

             序列化:

               步骤一:创建一个对象输出流,它可以包装一个其它类型的目标输出流,如文件输出流:

                              ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(“目标地址路径”));

             步骤二:通过对象输出流的writeObject()方法写对象:

                              out.writeObject("Hello");

                              out.writeObject(new Date());

             反序列化:        

              步骤一:创建一个对象输入流,它可以包装一个其它类型输入流,如文件输入流:

                              ObjectInputStream in = new ObjectInputStream(new fileInputStream(“目标地址路径”));

             步骤二:通过对象输出流的readObject()方法读取对象:

                            String obj1 = (String)in.readObject();

                            Date obj2 =  (Date)in.readObject();

            说明:为了正确读取数据,完成反序列化,必须保证向对象输出流写对象的顺序与从对象输入流中读对象的顺序一致。

    五、举个例子

           我们首先写个Person实现Serializable接口:

    [java]  view plain  copy
     print ?
    1. import java.io.Serializable;  
    2.   
    3. /** 
    4.  *  
    5.  * 测试序列化和反序列化 
    6.  * @author crazyandcoder 
    7.  * @date [2015-8-5 上午11:14:32] 
    8.  */  
    9. public class Person implements Serializable  {  
    10.       
    11.     private int age;  
    12.     private String name;  
    13.     //序列化ID  
    14.     private static final long serialVersionUID = -5809782578272943999L;  
    15.       
    16.     public Person() {}  
    17.       
    18.     public int getAge() {  
    19.         return age;  
    20.     }  
    21.     public void setAge(int age) {  
    22.         this.age = age;  
    23.     }  
    24.     public String getName() {  
    25.         return name;  
    26.     }  
    27.     public void setName(String name) {  
    28.         this.name = name;  
    29.     }  
    30.   
    31. }  

            

             其次,我们在main()里面写个方法,执行序列化过程:

    [java]  view plain  copy
     print ?
    1. import java.io.FileNotFoundException;  
    2. import java.io.FileOutputStream;  
    3. import java.io.IOException;  
    4. import java.io.ObjectOutputStream;  
    5. /** 
    6.  *  
    7.  * 测试序列化和反序列化 
    8.  * @author crazyandcoder 
    9.  * @date [2015-8-5 上午11:16:14] 
    10.  */  
    11. public class ObjSerializeAndDeserializeTest {   
    12.     public static void main(String[] args) {  
    13.           
    14.         //将Person对象序列化  
    15.         SerializePerson();  
    16.   
    17.     }  
    18.       
    19.     /** 
    20.      *  
    21.      * @author crazyandcoder 
    22.      * @Title: 序列化Person对象,将其存储到 E:/hello.txt文件中 
    23.      * @param   
    24.      * @return void  
    25.      * @throws  
    26.      * @date [2015-8-5 上午11:21:27] 
    27.      */  
    28.     private static void SerializePerson() {  
    29.         Person person =new Person();  
    30.         person.setAge(30);  
    31.         person.setName("SerializePerson");  
    32.         ObjectOutputStream outputStream = null;  
    33.         try {  
    34.             outputStream=new ObjectOutputStream(new FileOutputStream("E:/hello.txt"));  
    35.             outputStream.writeObject(person);  
    36.             System.out.println("序列化成功。");  
    37.         } catch (FileNotFoundException e) {  
    38.             e.printStackTrace();              
    39.         } catch (IOException e) {  
    40.             e.printStackTrace();          
    41.         } finally {  
    42.             try {  
    43.                 outputStream.close();  
    44.             } catch (IOException e) {  
    45.                 e.printStackTrace();  
    46.             }  
    47.         }         
    48.     }  
    49. }  
               代码很简单,首先创建一个对象输出流ObjectOutputStream,它可以包装一个其它类型的目标输出流,如文件输出流FileOutputStream,并指定存储的位置为“E:/hello.txt”,然后通过对象输出流的writeObject()方法写对象便执行了序列化过程。运行看一下效果,正确的话便会在控制台打印“”,并且在本地E盘下会创建一个Hello.txt文件。



            

            我们查看一下hello.txt文件中的内容,里面是一串字节序列,打开该文件的时候不要用自带的记事本打开,因为涉及到字符编码的问题,所以显示的话是一串乱码,建议用SublimeText打开。


             

            我们再写个方法来反序列化该字节成Person对象,并打印出里面的值。

    [java]  view plain  copy
     print ?
    1. import java.io.FileInputStream;  
    2. import java.io.FileNotFoundException;  
    3. import java.io.FileOutputStream;  
    4. import java.io.IOException;  
    5. import java.io.ObjectInputStream;  
    6. import java.io.ObjectOutputStream;  
    7. /** 
    8.  *  
    9.  * 测试序列化和反序列化 
    10.  * @author crazyandcoder 
    11.  * @date [2015-8-5 上午11:16:14] 
    12.  */  
    13. public class ObjSerializeAndDeserializeTest {  
    14.   
    15.        
    16.     public static void main(String[] args) {  
    17.           
    18.         //反序列化生成Person对象  
    19.         Person person=DeserializePerson();  
    20.         System.out.println("name :"+person.getName());  
    21.         System.out.println("age  :"+person.getAge());  
    22.           
    23.   
    24.     }  
    25.       
    26.     /** 
    27.      * 执行反序列化过程生产Person对象 
    28.      * @author crazyandcoder 
    29.      * @Title: DeserializePerson  
    30.      * @param @return  
    31.      * @return Person  
    32.      * @throws  
    33.      * @date [2015-8-5 下午1:30:12] 
    34.      */  
    35.     private static Person DeserializePerson() {  
    36.           
    37.         Person person=null;  
    38.         ObjectInputStream inputStream=null;  
    39.         try {  
    40.             inputStream=new ObjectInputStream(new FileInputStream("E:/hello.txt"));  
    41.             try {  
    42.                 person=(Person)inputStream.readObject();  
    43.                 System.out.println("执行反序列化过程成功。");  
    44.             } catch (ClassNotFoundException e) {  
    45.                 e.printStackTrace();  
    46.             }  
    47.         } catch (FileNotFoundException e) {  
    48.             e.printStackTrace();  
    49.         } catch (IOException e) {  
    50.             e.printStackTrace();  
    51.         } finally {  
    52.             try {  
    53.                 inputStream.close();  
    54.             } catch (IOException e) {  
    55.                 e.printStackTrace();  
    56.             }  
    57.         }  
    58.         return person;  
    59.     }  
    60. }  

             

            执行反序列化的代码也是很简单的,首先创建一个输入流对象ObjectInputStream,然后从指定的目录下“E:/hello.txt”获取它的字节序列,然后通过输入流对象的readObject()方法将其获得的对象强制转化为Person对象,这就完成了反序列化工作,正确的反序列化成功的情况下控制台打印输出为:



    Java 序列化ID的作用


           有关序列化和反序列化的概念,可以查看前一篇java序列化和反序列化使用总结的讲解,这一篇主要说明一下序列化过程中出现的问题即java序列化和反序列化中ID的作用。

            在前一篇的介绍中,我们在代码里会发现有这样一个变量:serialVersionUID,那么这个变量serialVersionUID到底具有什么作用呢?能不能去掉呢?


    [java]  view plain  copy
     print ?
    1. public class Person implements Serializable  {  
    2.       
    3.     private int age;  
    4.     private String sex;  
    5.     private String name;  
    6.     private String hobby;  
    7.     //序列化ID  
    8.     private static final long serialVersionUID = -5809782578272943999L;  
    9.         ............  
    10.   
    11. }  
           

           序列化ID的作用:  

           其实,这个序列化ID起着关键的作用,它决定着是否能够成功反序列化!简单来说,java的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM会把传来的字节流中的serialVersionUID与本地实体类中的serialVersionUID进行比较,如果相同则认为是一致的,便可以进行反序列化,否则就会报序列化版本不一致的异常。等会我们可以通过代码验证一下。

           序列化ID如何产生:

           当我们一个实体类中没有显示的定义一个名为“serialVersionUID”、类型为long的变量时,Java序列化机制会根据编译时的class自动生成一个serialVersionUID作为序列化版本比较,这种情况下,只有同一次编译生成的class才会生成相同的serialVersionUID。譬如,当我们编写一个类时,随着时间的推移,我们因为需求改动,需要在本地类中添加其他的字段,这个时候再反序列化时便会出现serialVersionUID不一致,导致反序列化失败。那么如何解决呢?便是在本地类中添加一个“serialVersionUID”变量,值保持不变,便可以进行序列化和反序列化。


           验证“serialVersionUID”不一致导致反序列化失败

    [java]  view plain  copy
     print ?
    1. import java.io.Serializable;  
    2.   
    3. /** 
    4.  *  
    5.  * 测试序列化和反序列化 
    6.  * @author crazyandcoder 
    7.  * @date [2015-8-5 上午11:14:32] 
    8.  */  
    9. public class Person implements Serializable  {  
    10.       
    11.     private int age;  
    12. //  private String sex;  
    13. //  private String name;  
    14. //  private String hobby;  
    15.     //序列化ID  
    16. //  private static final long serialVersionUID = -5809782578272943999L;  
    17.       
    18. //  public String getHobby() {  
    19. //      return hobby;  
    20. //  }  
    21. //  
    22. //  public void setHobby(String hobby) {  
    23. //      this.hobby = hobby;  
    24. //  }  
    25.   
    26.     public Person() {}  
    27.       
    28.     public int getAge() {  
    29.         return age;  
    30.     }  
    31.       
    32.     public void setAge(int age) {  
    33.         this.age = age;  
    34.     }  
    35.       
    36. //  public String getSex() {  
    37. //      return sex;  
    38. //  }  
    39. //  
    40. //  public void setSex(String sex) {  
    41. //      this.sex = sex;  
    42. //  }  
    43.       
    44. //  public String getName() {  
    45. //      return name;  
    46. //  }  
    47. //  public void setName(String name) {  
    48. //      this.name = name;  
    49. //  }  
    50.   
    51. }  
            复用前篇使用到的代码,首先,我们生成一个本地Person类,里面添加一个字段age,然后将其序列化存于本地E:/hello.txt中,

    [java]  view plain  copy
     print ?
    1. import java.io.FileNotFoundException;  
    2. import java.io.FileOutputStream;  
    3. import java.io.IOException;  
    4. import java.io.ObjectOutputStream;  
    5.   
    6.   
    7. /** 
    8.  *  
    9.  * 测试序列化和反序列化 
    10.  * @author crazyandcoder 
    11.  * @date [2015-8-5 上午11:16:14] 
    12.  */  
    13. public class ObjSerializeAndDeserializeTest {  
    14.   
    15.        
    16.     public static void main(String[] args) {  
    17.           
    18.         //将Person对象序列化  
    19.         SerializePerson();  
    20.     }  
    21.       
    22.       
    23.     /** 
    24.      *  
    25.      * @author crazyandcoder 
    26.      * @Title: 序列化Person对象,将其存储到 E:/hello.txt文件中 
    27.      * @param   
    28.      * @return void  
    29.      * @throws  
    30.      * @date [2015-8-5 上午11:21:27] 
    31.      */  
    32.     private static void SerializePerson() {  
    33.         Person person =new Person();  
    34.         person.setAge(30);  
    35.         ObjectOutputStream outputStream = null;  
    36.         try {  
    37.             outputStream=new ObjectOutputStream(new FileOutputStream("E:/hello.txt"));  
    38.             outputStream.writeObject(person);  
    39.             System.out.println("序列化成功。");  
    40.         } catch (FileNotFoundException e) {  
    41.             e.printStackTrace();  
    42.                   
    43.         } catch (IOException e) {  
    44.             e.printStackTrace();  
    45.                   
    46.         } finally {  
    47.             try {  
    48.                 outputStream.close();  
    49.             } catch (IOException e) {  
    50.                 e.printStackTrace();  
    51.             }  
    52.         }  
    53.           
    54.     }  
    55. }  
            

            运行一下,会在控制台中打印“序列化成功。”,然后我们在Person类中再添加一个字段,name,然后直接从E:/hello.txt中反序列化,再运行一下,看看会出现什么问题。

    [java]  view plain  copy
     print ?
    1. import java.io.FileInputStream;  
    2. import java.io.FileNotFoundException;  
    3. import java.io.FileOutputStream;  
    4. import java.io.IOException;  
    5. import java.io.ObjectInputStream;  
    6. import java.io.ObjectOutputStream;  
    7.   
    8. /** 
    9.  *  
    10.  * 测试序列化和反序列化 
    11.  *  
    12.  * @author crazyandcoder 
    13.  * @date [2015-8-5 上午11:16:14] 
    14.  */  
    15. public class ObjSerializeAndDeserializeTest {  
    16.   
    17.     public static void main(String[] args) {  
    18.   
    19.         // 反序列化生成Person对象  
    20.         Person person = DeserializePerson();  
    21.         System.out.println("name :" + person.getName());  
    22.         System.out.println("age  :" + person.getAge());  
    23.     }  
    24.   
    25.     /** 
    26.      * 执行反序列化过程生产Person对象 
    27.      *  
    28.      * @author crazyandcoder 
    29.      * @Title: DeserializePerson 
    30.      * @param @return 
    31.      * @return Person 
    32.      * @throws 
    33.      * @date [2015-8-5 下午1:30:12] 
    34.      */  
    35.     private static Person DeserializePerson() {  
    36.   
    37.         Person person = null;  
    38.         ObjectInputStream inputStream = null;  
    39.         try {  
    40.             inputStream = new ObjectInputStream(new FileInputStream("E:/hello.txt"));  
    41.             try {  
    42.                 person = (Person) inputStream.readObject();  
    43.                 System.out.println("执行反序列化过程成功。");  
    44.             } catch (ClassNotFoundException e) {  
    45.                 e.printStackTrace();  
    46.             }  
    47.         } catch (FileNotFoundException e) {  
    48.             e.printStackTrace();  
    49.         } catch (IOException e) {  
    50.             e.printStackTrace();  
    51.         } finally {  
    52.             try {  
    53.                 inputStream.close();  
    54.             } catch (IOException e) {  
    55.                 e.printStackTrace();  
    56.             }  
    57.         }  
    58.         return person;  
    59.     }  
    60. }  

           运行一下,不出意外,报了一个异常。



          

          从上面两张图便可以看出两次的序列化ID是不一样的,导致反序列化失败。


    总结:

           虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID = 1L)。



    展开全文
  • java序列化和反序列化,面试必备

    千次阅读 多人点赞 2020-04-26 17:04:19
    意义:序列化机制允许将实现序列化Java对象转换为字节序列,并将字节序列保存在磁盘中,或通过网络传输,以达到以后恢复成原来的对象。序列化机制使地对象可以脱离程序的运行而独立存在。 使用场景:所有在网络上...

    最近阅读Serializable接口和Externalizable接口的源码,并结合了一些资料,对面试过程中与序列化相关的内容做了一些总结。
    一、序列化、反序列化、使用场景、意义。
    序列化:将对象写入IO流中;
    反序列化:从IO流中恢复对象
    意义:序列化机制允许将实现序列化的Java对象转换为字节序列,并将字节序列保存在磁盘中,或通过网络传输,以达到以后恢复成原来的对象。序列化机制使地对象可以脱离程序的运行而独立存在。
    使用场景所有在网络上传输的对象都必须是可序列化的。如:RMI (远程方法调用),传入的参数或返回的对象都是可序列化的,否则会出错。所有必须保存到磁盘的java对象都必须是可序列化的程序创建的JavaBean最好都实现Serializable接口
    二、实现序列化的方式
    实现序列化有两种方式:实现Serializable接口或Externalizable接口,通常情况下,实现Serializable接口即可。两种接口的对比如下:

    实现Serializable接口:
    1) 系统自动存储必要的信息;
    2) Java内建支持,易于实现,只需要实现接口接口,不需要任何代码支持;
    3) 性能略差;
    
    实现Externalizable接口:
    1) 自己决定要序列化哪些属性;
    2) 必须实现该接口内的两个方法:
    void writeExternal(ObjectOutput out) throws IOException;
    void readExternal(ObjectInput in) throws IOException, ClassNotFoundException;
    3) 性能略好;
    

    三、使用Serializable接口实现序列化。
    Serializable接口是一个标记接口,不用实现任何方法,一旦某个类实现了该方法,则该类的对象是可序列化的。
    1、通过以下步骤实现序列化:
    1)创建一个ObjectOutputStream输出流;
    2)调用OjectOutputSteam对象的writeObject ()输出可序列化对象。

    public class Person implements Serializable {
    	private String name;
    	private String age;
    
    	public Person() {
    		System.out.println("调用Person的无参构造函数");
    	}
    
    	public Person(String name, String age) {
    		this.name = name;
    		this.age = age;
    		System.out.println("调用Person的有参构造函数");
    	}
    
    	@Override
    	public String toString() {
    		// TODO 自动生成的方法存根
    		return "Person{'name' :" + name + ",'age' :" + age + "}";
    	}
    }
    
    public class WriteObject {
    	public static void main(String[] args) {
    		try {
    			ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Person.txt"));
    			Person p = new Person("baby", "12");
    			oos.writeObject(p);
    		} catch (Exception e) {
    			// TODO: handle exception
    		}
    	}
    }
    

    输出的序列化文件如下:

    aced 0005 7372 0017 7365 7269 616c 697a
    6162 6c65 5465 7374 2e50 6572 736f 6e4e
    aff9 165f 38dd f602 0002 4c00 0361 6765
    7400 124c 6a61 7661 2f6c 616e 672f 5374
    7269 6e67 3b4c 0004 6e61 6d65 7100 7e00
    0178 7074 0002 3132 7400 0462 6162 79
    

    2、通过以下步骤实现反序列化:
    1)创建一个ObjectInputStream输入流;
    2)调用ObjectInputStream对象的readObject ()得到序列化对象。

    public class WriteObject {
    	public static void main(String[] args) {
    		try {
    			ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Person.txt"));
    			Person p = (Person) ois.readObject();
    			System.out.println(p.toString());
    		} catch (Exception e) {
    			// TODO: handle exception
    		}
    	}
    }
    

    输出结果如下:

    Person{'name' :baby,'age' :12}
    

    通过输出结果,我们知道反序列化没有调用类的构造方法,而是由JVM自己生成对象。
    3、当类的成员是引用数据类型时
    若一个类的成员不是基本数据类型,也不是String类型的时候,则该成员必须是可序列化的,否则会导致该类无法完成序列化。如下例子所示:

    // 去掉Person类实现的序列化接口
    public class Teacher implements Serializable {
    	private String name;
    	private Person person;
    
    	public Teacher(String name, Person person) {
    		this.name = name;
    		this.person = person;
    	}
    
    	public static void main(String[] args) throws Exception {
    		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Teacher.txt"));
    		Person p = new Person("baby", "16");
    		Teacher t = new Teacher("mom", p);
    		oos.writeObject(t);
    	}
    }
    

    执行时会抛出下面的异常,异常指出,因为Person类不可序列化,导致Teacher类无法完成序列化操作。
    在这里插入图片描述
    4、序列化过程中存在的问题。
    1)同一对象,会被序列化多次吗?
    依次将p、t1、t2、t1序列化到文件SerializableMore中。

    public class WriteMore {
    	public static void main(String[] args) throws Exception {
    		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("SerializableMore.txt"));
    		Person p = new Person("baby", "16");
    		Teacher t1 = new Teacher("mom", p);
    		Teacher t2 = new Teacher("dad", p);
    
    		oos.writeObject(p);
    		oos.writeObject(t1);
    		oos.writeObject(t2);
    		oos.writeObject(t1);
    	}
    }
    

    接下来将反序列化文件SerializableMore。

    public class ReadMore {
    	public static void main(String[] args) throws Exception {
    		ObjectInputStream ois = new ObjectInputStream(new FileInputStream("SerializableMore.txt"));
    		
    		// 注意:反序列化的顺序和序列化时的顺序一致
    		Person p = (Person) ois.readObject();
    		Teacher t1 = (Teacher) ois.readObject();
    		Teacher t2 = (Teacher) ois.readObject();
    		Teacher t3 = (Teacher) ois.readObject();
    
    		System.out.println("t1 == t2 ---------------------------->" + (t1 == t2));
    		System.out.println("t1.getPerson() == p ----------------->" + (t1.getPerson() == p));
    		System.out.println("t2.getPerson() == p ----------------->" + (t2.getPerson() == p));
    		System.out.println("t2 == t3 ---------------------------->" + (t2 == t3));
    		System.out.println("t1.getPerson() == t2.getPerson() ---->" + (t1.getPerson() == t2.getPerson()));
    	}
    }
    

    输出结果如下所示:

    t1 == t2 ---------------------------->false
    t1.getPerson() == p ----------------->true
    t2.getPerson() == p ----------------->true
    t2 == t3 ---------------------------->false
    t1.getPerson() == t2.getPerson() ---->true
    

    可以看到:针对同一对象进行多次序列化,Java并不会序列化多次,而是沿用第一次序列化获得的序列化编码
    2)由于Java序列化算法不会重复序列化同一个对象,只会记录已序列化对象的序列化编号。而当一个可变的对象中的内容发生改变时,此时进行序列化,却不会重新将此对象转换为字节序列,而是保存序列化编号。如下所示。

    public class WirteOnChange {
    	public static void main(String[] args) throws Exception {
    		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("WriteOnchange.txt"));
    		ObjectInputStream ois = new ObjectInputStream(new FileInputStream("WriteOnchange.txt"));
    
    		Person person = new Person("索隆", "20");
    		System.out.println("修改前:" + person.toString());
    		oos.writeObject(person);
    
    		person.setName("香吉士");
    		System.out.println("修改后:" + person.toString());
    		oos.writeObject(person);
    		
    		Person p1 = (Person) ois.readObject();
    		Person p2 = (Person) ois.readObject();
    		
    		System.out.println(p1 == p2);
    		System.out.println(p1.getName().equals(p2.getName()));
    	}
    }
    

    输出结果如下:

    修改前:Person{'name' :索隆,'age' :20}
    修改后:Person{'name' :香吉士,'age' :20}
    true
    true
    

    5、Java序列化算法

    1)所有保存到磁盘的对象都有一个序列化编号;
    2)当试图序列化一个对象时,会先检查该对象是否已经序列化过,只有该对象未被JVM序列化过,才会将该对象序列化为字节序列输出;
    3)如果此对象已经被序列化过,则直接输出序列化编码号即可。
    

    如下图所示:
    在这里插入图片描述
    6、可选的自定义序列化
    1)使用transient关键字指定不进行序列化的字段。
    使用transient修饰的属性,java序列化时会忽略该属性。而当反序列化时,被transient修饰的属性则赋予默认值对于引用类型则为nullboolean类型为false,基本类型为0

    public class Teacher implements Serializable {
    	private String name;
    	private transient String age;
    	private transient int height;
    	private Person person;
    	
    	public Teacher(String name, String age, int height, Person person) {
    		this.name = name;
    		this.age = age;
    		this.height = height;
    		this.person = person;
    	}
    	// ...省略getter、setter方法
    
    public class WirteOnChange {
    	public static void main(String[] args) throws Exception {
    		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("teacher.txt"));
    		ObjectInputStream ois = new ObjectInputStream(new FileInputStream("teacher.txt"));
    
    		Person person = new Person("索隆", "20");
    		Teacher teacher = new Teacher("鹰眼", "30", 190, person);
    		
    		System.out.println("序列化之前:" + teacher.toString());
    		oos.writeObject(teacher);
    
    		Teacher t1 = (Teacher) ois.readObject();
    
    		System.out.println("序列化之后:" + t1.toString());
    	}
    }
    

    输出结果如下所示:

    序列化之前:Teacher{"name" : "鹰眼"; "age" : "30"; "height" : 190; "person" : Person{'name' :索隆,'age' :20}
    序列化之后:Teacher{"name" : "鹰眼"; "age" : "null"; "height" : 0; "person" : Person{'name' :索隆,'age' :20}
    
    

    2)通过下面的方法可以实现自定义序列化,可以控制序列化的方式或对序列化数据进行编码加密等。

    private void writeObject(java.io.ObjectOutputStream out) throws IOException
    private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException;
    private void readObjectNoData() throws ObjectStreamException;
    

    通过重写writeObject()readObject()方法,可选择哪些属性要序列化。如果writeObject使用了某种规则进行序列化,则readObject要使用相反的规则进行反序列化,以便能正确反序列化对象。

    // 对字符串name进行反转加密
    public class Person implements Serializable {
    	private String name;
    	private String age;
    	private int height;
    	
    	// 省略构造函数和getter、setter方法
    
    	private void WriteObject(ObjectOutputStream oos) throws IOException {
    		oos.writeObject(new StringBuilder(this.name).reverse()); // 利用StringBuilder实现字符串反转
    		oos.writeInt(height);
    	}
    
    	private void ReadObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
    		this.name = ((StringBuilder) ois.readObject()).reverse().toString();
    		this.height = ois.readInt();
    	}
    

    当序列化流不完整时,readObjectNoData()方法可以正确地初始化反序列化的对象。例如,使用不同类接收反序列化对象,或者序列化流被篡改,系统都会调用readObjectNoData()来初始化反序列化对象。
    3)彻底的自定义序列化
    以下两个方法会在序列化前或反序列化后自动调用,可以实现更加彻底的自定义序列化。

    ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;
    ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;
    

    writeReplace()方法:在序列化前,会先调用该方法,再调用writeObject方法。此方法可以使用任意对象代替目标序列化对象。

    public class Person implements Serializable {
    	private String name;
    	private String age;
    	
    	// 省略构造方法、getter和setter方法
    
    	private Object writeReplace() throws ObjectStreamException {
    		ArrayList<String> list = new ArrayList<>();
    		list.add(this.name);
    		list.add(this.age);
    		return list;
    	}
    	
    	public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
    		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt"));
    		ObjectInputStream ios = new ObjectInputStream(new FileInputStream("person.txt"));
    
    		Person person = new Person("罗宾", "18");
    		oos.writeObject(person);
    
    		ArrayList<String> list = (ArrayList) ios.readObject();
    		System.out.println(list);
    	}
    

    输出结果如下:

    [罗宾, 18]
    

    readResolve()方法:替代反序列化输出的对象,反序列化出来的对象会被立即丢弃,此方法在readObject()后调用。

    public class Person implements Serializable {
    	private String name;
    	private String age;
    	private int height;
    	
    	// 省略构造方法、getter和setter方法
    	
    	private Object readResolve() throws ObjectStreamException {
    		return new Person("娜美", "23");
    	}
    	
    	public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
    		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("person.txt"));
    		ObjectInputStream ios = new ObjectInputStream(new FileInputStream("person.txt"));
    
    		Person person = new Person("罗宾", "18");
    		oos.writeObject(person);
    
    		Person p1 = (Person) ios.readObject();
    		System.out.println(p1);
    	}
    

    输出结果如下:

    Person{'name' :娜美,'age' :23}
    

    四、使用Externalizable接口实现序列化。
    Externalizable接口不同于Serializable接口,该接口需要强制重写两个方法。

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

    测试程序如下:

    public class PersonExternal implements Externalizable {
    	private String name;
    	private int age;
    
    	// 必须提供无参构造函数
    	public PersonExternal() {
    		System.out.println("调用无参构造方法!!");
    	}
    
    	public PersonExternal(String name, int age) {
    		this.name = name;
    		this.age = age;
    	}
    
    	@Override
    	public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
    		// 将读取的字符串反转后赋值给name实例变量
    		this.name = ((StringBuilder) in.readObject()).reverse().toString();
    		System.out.println("将name按相同的规则反序列化输出:" + name);
    		this.age = in.readInt();
    	}
    
    	@Override
    	public void writeExternal(ObjectOutput out) throws IOException {
    		// 将name反转后写入二进制流
    		StringBuilder reverse = new StringBuilder(name).reverse();
    		System.out.println("将name反转并序列化写入二进制流:" + reverse.toString());
    		out.writeObject(reverse);
    		out.writeInt(age);
    	}
    
    	@Override
    	public String toString() {
    		// TODO 自动生成的方法存根
    		return "Person{'name' :" + name + ", 'age' :" + age + "}";
    	}
    
    	public static void main(String[] args) throws Exception {
    		ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("PersonExternal.txt"));
    		ObjectInputStream ois = new ObjectInputStream(new FileInputStream("PersonExternal.txt"));
    
    		System.out.println("序列化ing");
    		oos.writeObject(new PersonExternal("cindy", 23));
    
    		System.out.println("反序列化ing");
    		PersonExternal pe = (PersonExternal) ois.readObject();
    
    		System.out.println(pe.toString());
    	}
    
    }
    

    输出结果:

    序列化ing
    将name反转并序列化写入二进制流:ydarb
    反序列化ing
    调用无参构造方法!!
    将name按相同的规则反序列化输出:brady
    Person{'name' :brady, 'age' :23}
    

    可以看到的是,实现Externalizable接口必须提供public的无参构造器,因为在反序列化的时候需要通过反射创建对象。
    五、序列化版本号serialVersionUID 。
    介绍了那么多关于序列化的内容,我们知道,反序列必须要有class文件,但随着项目的升级,class文件也会随之升级。那么,序列化怎么保证升级前后的兼容性呢?
    Java序列化提供了一个serializableVersionUID的序列化版本号,只要版本号相同,即使更改了序列化属性,对象也可以被正确地反序列化回来。

    public class Person implements Serializable {
    	// 序列化版本号
    	private static final long serialVersionUID = 1227593270102525184L;
    	
    	private String name;
    	private String age;
    	private int height;
    

    但是,如果反序列化使用的class的版本号与序列化时使用的不一致,反序列化会报InvalidClassException异常。
    在这里插入图片描述序列化版本号可自由指定,如果不指定,JVM会根据类信息自己计算一个版本号,这样随着class的升级,就无法正确反序列化;
    不指定版本号另一个明显隐患是,不利于jvm间的移植,可能class文件没有更改,但不同jvm可能计算的规则不一样,这样也会导致无法反序列化。
    接下来列出几种序列化的情况:

    a) 只是修改了方法,反序列化不影响,则无需修改版本号
    b) 只是修改了静态变量,瞬态变量(transient修饰的变量),反序列化不受影响,无需修改版本号
    c) 修改了非瞬态变量,则可能导致反序列化失败。如果新类中实例变量的类型与序列化时类的类型不一致,则会反序列化失败,这时候需要更改serialVersionUID。如果只是新增了实例变量,则反序列化回来新增的是默认值;
    

    六、总结 。

    a) 对象的类名、实例变量(包括基本类型,数组,对其他对象的引用)都会被序列化;方法、类变量、transient实例变量都不会被序列化。
    b) 所有需要网络传输的对象都需要实现序列化接口,通过建议所有的javaBean都实现Serializable接口。
    c) 如果想让某个变量不被序列化,使用transient修饰。
    d) 序列化对象的引用类型成员变量,也必须是可序列化的,否则,会报错。
    e) 反序列化时必须有序列化对象的class文件。
    f) 当通过文件、网络来读取序列化后的对象时,必须按照实际写入的顺序读取。
    g) 单例类序列化,需要重写readResolve()方法;否则会破坏单例原则。
    i) 同一对象序列化多次,只有第一次序列化为二进制流,以后都只是保存序列化编号,不会重复序列化。
    j) 建议所有可序列化的类加上serialVersionUID 版本号,方便项目升级。
    k) 数组不能显式地声明serialVersionUID,因为它们始终都有默认的计算值,但是对于数组类,无需匹配serialVersionUID。
    l) 可以通过序列化和反序列化的方式实现对象的深复制。
    
    展开全文
  • 面试题 - Java序列化和反序列化

    千次阅读 2020-09-15 18:03:22
    Java 对象序列化为二进制文件的 Java 序列化技术是 Java 系列技术中一个较为重要的技术点,在大部分情况下,开发人员只需要了解被序列化的类需要实现 Serializable 接口,使用 ObjectInputStream ...
  • (1)Java序列化是指把Java对象转换为字节序列的过程,而Java序列化是指把字节序列恢复为Java对象的过程; (2)**序列化:**对象序列化的最主要的用处就是在传递保存对象的时候,保证对象的完整性和可传递性。...
  • java 常用序列化和反序列化框架使用demo ,java 常用序列化和反序列化框架使用demo
  • 主要介绍了java 对象的序列化和反序列化的相关资料,需要的朋友可以参考下
  • Java 序列化反序列化

    千次阅读 2020-09-05 13:04:20
    介绍 Java序列化反序列化
  • 本篇文章是对Java中对象的序列化反序列化进行了详细的分析介绍,需要的朋友参考下
  • java 序列化和反序列化详解

    千次阅读 2016-10-29 09:17:36
    Java 序列化就是指将对象转换为字节序列的过程,而反序列化则是只将字节序列转换成目标对象的过程。 我们都知道,在进行浏览器访问的时候,我们看到的文本、图片、音频、视频等都是通过二进制序列进行传输的,那么...
  • 于是,我们需要对自己写的类进行序列化的操作机会变少了。 但,若我们对序列化有了了解,总是对于理解一些问题有帮助的。(其中序列化三个字可以换成任何技术) 今天要说的就是下面这个东西 static final long ...
  • 1、序列化 //序列化为byte[] public static byte[] serialize(Object object) { ObjectOutputStream oos = null; ByteArrayOutputStream bos = null; try { bos = new ByteArrayOutputStream(); ...
  • 遇到这个 Java Serializable 序列化这个接口,我们可能会有如下的问题a,什么叫序列化和反序列化 b,作用。为啥要实现这个 Serializable 接口,也就是为啥要序列化 c,serialVersionUID 这个的值到底是在怎么设置的...
  • java序列化(Serializable)的作用和反序列化.doc 有详细的讲解哦。 在什么地方用的到都有说明的.
  • java序列化和反序列化使用总结

    千次阅读 2015-08-05 13:42:32
    java序列化和反序列化使用总结 一、概念  java对象序列化的意思就是将对象的状态转化成字节流,以后可以通过这些值再生成相同状态的对象。对象序列化是对象持久化的一种实现方法,它是将对象的属性方法转化为一种...
  • java反序列化工具

    2018-11-21 09:20:05
    java反序列化工具,覆盖jboss、weblogic、websphere。
  • Java序列化和反序列化为什么要实现Serializable接口

    千次阅读 多人点赞 2019-08-27 11:49:37
    最近公司的在做服务化, 需要把所有model包里的类都实现...(1) 序列化和反序列化是什么? (2) 实现序列化和反序列化为什么要实现Serializable接口? (3) 实现Serializable接口就算了, 为什么还要显示指定serialVers...
  • 这里说到两个概念,序列化和反序列化序列化:简单理解就是把程序里面生成的对象以文件的形式保存到本地硬盘中,序列化写入文件的IO是ObjectOutputStream流。 反序列化:就是把序列化的对象文件导入到程序中,并...
  • 在web项目开发的时候,经常用到序列化和反序列化用来传递大流量的数据,类只有实现Serializable借口才能被序列化,下来是java序列化和反序列化演示
  • 序列化反序列化是指Java对象与字节序列的相互转换,一般在保存或传输字节序列的时候会用到,下面有两个Java实现序列化反序列化的简单示例,不过还是先来看看序列和反序列化的具体概念:
  • Java 序列化的几种方式 和反序列化

    千次阅读 2019-04-16 17:19:04
    对象 :反序列化 序列化的用途:把对象的字节序列保存在磁盘上,通常存放在一个文件中;在网络上传送对象的字节序列。 存储在物理磁盘上的:Web服务器中的Session对象。当有 10万用户并发访问,就有可能出现10万个...
  • Java反序列化工具.zip

    2019-05-31 16:45:28
    java反序列化工具;weblogic反序列化工具;jboss反序列化工具。
  • Java 序列化和反序列化(一)Serializable 使用场景 1. 最简单的使用:Serializable 接口 2. 序列化 ID 的问题 3. ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 497,380
精华内容 198,952
关键字:

java序列化和反序列化

java 订阅