java中序列化与反序列化的概念和使用方法介绍

发布时间:2021-07-29 16:36:16 作者:chen
来源:亿速云 阅读:158

这篇文章主要介绍“java中序列化与反序列化的概念和使用方法介绍”,在日常操作中,相信很多人在java中序列化与反序列化的概念和使用方法介绍问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”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();

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

基本实现代码

package com.my.test.clone;

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

public class TestStudent {

    public static void main(String[] args) {
        // TODO Auto-generated method stub
        
        Student stu1 = new Student("a", "a1");
        stu1.setAddress(new Address("ZZ"));

        ObjectOutputStream oos = null;
        ObjectInputStream ois = null;
        System.out.println(stu1);
        try {
            oos = new ObjectOutputStream(new FileOutputStream("D:/test/stu.txt"));
            oos.writeObject(stu1);
            oos.writeObject(stu1);
            
            ois = new ObjectInputStream(new FileInputStream("D:/test/stu.txt"));
            Student stu2 = (Student) ois.readObject();
            Student stu3 = (Student) ois.readObject();
            //Student stu4 = (Student) ois.readObject();
            System.out.println(stu2);
            System.out.println(stu3);
            //System.out.println(stu4);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

        try {
            if (oos != null) {
                oos.close();
            }
            if(ois!= null){
                ois.close();
            }
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

    }
}

知识点总结

1.java 序列化ID的作用

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

public class Student implements Serializable {
    
    
    /**
     * 
     */
    private static final long serialVersionUID = 6866904399011716299L;

    private String stuId;
    
    private transient String stuName;
    
    private Address address;
。。。。。。
}

序列化ID的作用: 

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

       序列化ID如何产生:

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

总结:

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

2.如何使某些属性不被序列化进去?

使用 transient 关键字

ublic class Student implements Serializable {
    
    
    /**
     * 
     */
    private static final long serialVersionUID = 6866904399011716299L;

    private String stuId;
    
    private transient String stuName;
    
    private Address address;
    
。。。。。。
}

Student [stuId=a, stuName=a1, address=Address [addr=ZZ]]
Student [stuId=a, stuName=null, address=Address [addr=ZZ]]

3.如何判断是否还有可读对象

oos = new ObjectOutputStream(new FileOutputStream(file));
            oos.writeObject(stu1);
            oos.writeObject(stu1);
            oos.writeObject(stu1);
            oos.writeObject(stu1);
            FileInputStream fis = new FileInputStream(file);
            ois = new ObjectInputStream(fis);
            while (fis.available()>0) {                
                System.out.println(ois.readObject());
            }

4.覆盖与不覆盖

oos = new ObjectOutputStream(new FileOutputStream("D:/test/stu.txt", true));

java.io.StreamCorruptedException: invalid type code: AC问题解决

问题描述:

每次向一个文件中序列化对象时 ,每次只想向文件末尾追加对象,而不是覆盖,可以使用FileInputStream(文件名,true);在读取数据的时候第一次会正常读取,不会报错,当读取第二次的时候,就会报出java.io.StreamCorruptedException: invalid type code: AC的错误。

问题分析:

由于用FileInputStream(文件名,true)向同一个文件中序列化对象,每次都会向文件中序列化一个header。在反序列化的时候每个 ObjectInputStream 对象只会读取一个header,那么当遇到第二个的时候就会报错,导致出现异常。

解决方案:

一共三种方法,推荐使用第二种;

一、运用集合:

在第一次序列化对象之前,把要序列化的对象添加到集合中,把这个集合序列化到文件中。然后每次序列化之前,除了把准备序列化的对象添加到集合中,再把已经序列化的集合反序列化回来添加到集合中,然后再把集合序列化到文件中。

二、重写ObjectOutputSream的writeStreamHeader()方法:

判断是不是第一次写入,若是则写入头部,若不是则不写入头部

/**
重写writeStreamHeader()方法
*/
class MyObjectOutputStream  extends ObjectOutputStream{

    public MyObjectOutputStream(OutputStream out) throws IOException {
        super(out);
    }

    public void writeStreamHeader() throws IOException{
        return;
    }
//序列化
    public static void set(File file,Person p) throws Exception{
        FileOutputStream fos = new FileOutputStream(file,true);
        /**
        判断是否是第一次写入
        */
        if(file.length()<1){
            ObjectOutputStream oos = new ObjectOutputStream(fos);
            oos.writeObject(p);
            oos.close();
        }else{
            MyObjectOutputStream mos = new MyObjectOutputStream(fos);
            mos.writeObject(p);
            mos.close();
        }
    }

    //反序列化
    public static List<Person> get(File file) throws Exception{
        List<Person> list = new ArrayList<Person>();
        FileInputStream fis = new FileInputStream(file);
        ObjectInputStream ois = new ObjectInputStream(fis);
        while(fis.available()>0){
            Person p = (Person) ois.readObject();
            list.add(p);
        }
        ois.close();
        return list;
    }
}

三:不重写ObjectOutputSream的writeStreamHeader()方法。在反序列化的while循环中,每次都创建一个新的ObjectInputStream用来读取header

public class SerializableDemo03{
    public static void main(String[] args) throws Exception {
        File file = new File(".\\c.txt");
        Person p = new Person("lisi",19);
        set(file,p);
        List<Person> list = get(file);
        for(Person per:list){
            System.out.println(per);
        }
    }

    public static void set(File file,Person p) throws Exception{
        FileOutputStream fos = new FileOutputStream(file,true);
        ObjectOutputStream oos = new ObjectOutputStream(fos);
        oos.writeObject(p);
        oos.close();
    }

    public static List<Person> get(File file) throws Exception{
        List<Person> list = new ArrayList<Person>();
        FileInputStream fis = new FileInputStream(file);
        ObjectInputStream ois = null;
        while(fis.available()>0){
            //每次都new一个新的ObjectInputStream
            ois = new ObjectInputStream(fis);
            Person p = (Person) ois.readObject();
            list.add(p);
        }
        ois.close();
        return list;
    }
}

5.序列化与克隆

解决多层克隆问题

如果引用类型里面还包含很多引用类型,或者内层引用类型的类里面又包含引用类型,使用clone方法就会很麻烦。这时我们可以用序列化的方式来实现对象的深克隆。

所有的引用类型都要实现序列化

public class Outer implements Serializable{
  private static final long serialVersionUID = 369285298572941L;  //最好是显式声明ID
  public Inner inner;
 //Discription:[深度复制方法,需要对象及对象所有的对象属性都实现序列化] 
  public Outer myclone() {
      Outer outer = null;
      try { // 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝
          ByteArrayOutputStream baos = new ByteArrayOutputStream();
          ObjectOutputStream oos = new ObjectOutputStream(baos);
          oos.writeObject(this);
      // 将流序列化成对象
          ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
          ObjectInputStream ois = new ObjectInputStream(bais);
          outer = (Outer) ois.readObject();
      } catch (IOException e) {
          e.printStackTrace();
      } catch (ClassNotFoundException e) {
          e.printStackTrace();
      }
      return outer;
  }
}

 6.java中序列化之子类继承父类序列化

父类实现了Serializable,子类不需要实现Serializable

  相关注意事项

    a)序列化时,只对对象的状态进行保存,而不管对象的方法;

    b)当一个父类实现序列化,子类自动实现序列化,不需要显式实现Serializable接口;

    c)当一个对象的实例变量引用其他对象,序列化该对象时也把引用对象进行序列化;

    d)并非所有的对象都可以序列化,至于为什么不可以,有很多原因了,比如:

        1.安全方面的原因,比如一个对象拥有private,public等field,对于一个要传输的对象,比如写到文件,或者进行rmi传输等等,在序列化进行传输的过程中,这个对象的private等域是不受保护的。transient标记的属性,也不会被实例化

       2. 资源分配方面的原因,比如socket,thread类,如果可以序列化,进行传输或者保存,也无法对他们进行重新的资源分配,而且,也是没有必要这样实现。

  例子:

 1,父类实现了Serializable,子类没有,

父类有int a = 1;int b = 2;int c = 3

子类有int d = 4;int e = 5

序列化子类的时候,d和e会不会被序列化?(答案:会)

2,反过来父类未实现Serializable,子类实现了,序列化子类实例的时候,父类的属性是直接被跳过不保存,还是能保存但不能还原?(答案:值不保存)

解:父类实现接口后,所有派生类的属性都会被序列化。子类实现接口的话,父类的属性值丢失。

java中序列化之子类继承父类序列化

当一个父类实现Serializable接口后,他的子类都将自动的实现序列化。

以下验证了这一点:

package Serial;
import java.io.Serializable; 
public class SuperC implements Serializable {//父类实现了序列化 
 int supervalue; 
 public SuperC(int supervalue) { 
  this.supervalue = supervalue; 
 } 
 public String toString() { 
  return "supervalue: "+supervalue; 
 } 
} 

public class SubC extends SuperC {//子类 
 int subvalue; 

 public SubC(int supervalue,int subvalue) { 
  super(supervalue); 
  this.subvalue=subvalue; 
 } 

 public String toString() { 
  return super.toString()+" sub: "+subvalue; 
 } 
} 

public class Test1 { 

 public static void main(String [] args){ 
  SubC subc=new SubC(100,200); 
  FileInputStream in=null; 
  FileOutputStream out=null; 
  ObjectInputStream oin=null; 
  ObjectOutputStream oout=null; 
  try { 
   out = new FileOutputStream("Test1.txt");//子类序列化 
   oout = new ObjectOutputStream(out); 
   oout.writeObject(subc); 
   oout.close(); 
   oout=null; 

   in = new FileInputStream("Test1.txt"); 
   oin = new ObjectInputStream(in); 
   SubC subc2=(SubC)oin.readObject();//子类反序列化 
   System.out.println(subc2); 
  } catch (Exception ex){ 
   ex.printStackTrace(); 
  } finally{ 
  …此处省略 
 } 
} 
}

运行结果如下:

supervalue: 100 sub: 200

  可见子类成功的序列化/反序列化了。

  怎管让子类实现序列化看起来是一件很简单的事情,但有的时候,往往我们不能够让父类实现Serializable接口,原因是有时候父类是抽象的(这并没有关系),并且父类不能够强制每个子类都拥有序列化的能力。换句话说父类设计的目的仅仅是为了被继承。

  要为一个没有实现Serializable接口的父类,编写一个能够序列化的子类是一件很麻烦的事情。java docs中提到:

“To allow subtypes of non-serializable classes to be serialized, the subtype may assume responsibility for saving and restoring
 the state of the supertype's public, protected, and (if accessible) package fields. The subtype may assume this responsibility
 only if the class it extends has an accessible no-arg constructor to initialize the class's state. It is an error to declare a
class Serializable if this is not the case. The error will be detected at runtime. ”

也就是说,要为一个没有实现Serializable接口的父类,编写一个能够序列化的子类要做两件事情:

  其一、父类要有一个无参的constructor;

  其二、子类要负责序列化(反序列化)父类的域。

  我们将SuperC的Serializable接口去掉,而给SubC加上Serializable接口。运行后产生错误:

java.lang.Error: Unresolved compilation problem:
Serializable cannot be resolved or is not a valid superinterface
at Serial.SubC.<init>(SubC.java:15)
at Serial.Test1.main(Test1.java:19)
Exception in thread "main"

  果真如docs中所说的一样,父类缺少无参构造函数是不行的。

  接下来,按照docs中的建议我们改写这个例子:

public abstract class SuperC { 
 int supervalue; 
 public SuperC(int supervalue) { 
  this.supervalue = supervalue; 
 }
 public SuperC(){}//增加一个无参的constructor 
  public String toString() { 
   return "supervalue: "+supervalue; 
  } 
 } 

 public class SubC extends SuperC implements Serializable { 
  int subvalue; 

  public SubC(int supervalue,int subvalue) { 
   super(supervalue); 
   this.subvalue=subvalue; 
  } 

  public String toString() { 
   return super.toString()+" sub: "+subvalue; 
  } 

  private void writeObject(java.io.ObjectOutputStream out) 
  throws IOException{ 
   out.defaultWriteObject();//先序列化对象 
   out.writeInt(supervalue);//再序列化父类的域 
  } 
  private void readObject(java.io.ObjectInputStream in) 
  throws IOException, ClassNotFoundException{ 
   in.defaultReadObject();//先反序列化对象 
   supervalue=in.readInt();//再反序列化父类的域 
  } 
}

运行结果证明了这种方法是正确的。在此处我们用到了writeObject/ readObject方法,这对方法如果存在的话,序列化时就会被调用,以代替默认的行为(以后还要探讨,先了解这么多)。我们在序列化时,首先调用了ObjectOutputStream的defaultWriteObject,它使用默认的序列化行为,然后序列化父类的域;反序列化的时候也一样。

  归纳一下:

  目的 行为

  为一个实现Serializable接口的父类,编写一个能够序列化的子类 子类将自动的实现序列化

  为一个没有实现Serializable接口的父类,编写一个能够序列化的子类 1, 父类要有一个无参的constructor;2, 子类要先序列化自身,然后子类要负责序列化父类的域

到此,关于“java中序列化与反序列化的概念和使用方法介绍”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!

推荐阅读:
  1. Java中如何实现序列化与反序列化
  2. java中如何实现对象的序列化和反序列化

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

java

上一篇:如何解决shiro定时监听器不生效的问题

下一篇:MapReduce中怎么实现自定义排序功能

相关阅读

您好,登录后才能下订单哦!

密码登录
登录注册
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》