Java的序列化与反序列化

本文深入探讨Java对象序列化的机制,包括序列化ID的重要性、序列化存储规则、自定义序列化策略,以及如何处理敏感字段加密。通过实例演示序列化过程中变量的状态保存,探讨静态变量、父类对象和transient关键字的序列化行为。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

初遇

序列化是一种对象持久化的手段。普遍应用在网络传输、RMI等场景中。本文通过分析ArrayList的序列化来介绍Java序列化的相关内容。

Java对象的序列化

Java平台允许我们在内存中创建可复用的Java对象,但一般情况下,只有当JVM处于运行时,这些对象才可能存在,即,这些对象的生命周期不会比JVM的生命周期更长。但在现实应用中,就可能要求在JVM停止运行之后能够保存(持久化)指定的对象,并在将来重新读取被保存的对象。Java对象序列化就能够帮助我们实现该功能。

使用Java对象序列化,在保存对象时,会把其状态保存为一组字节,在未来,再将这些字节组装成对象。必须注意地是,对象序列化保存的是对象的”状态”,即它的成员变量。由此可知, 对象序列化不会关注类中的静态,注意保存的是对象的状态,是对象,不是类。

除了在持久化对象时会用到对象序列化之外,当使用RMI(远程方法调用),或在网络中传递对象时,都会用到对象序列化。

如何对Java对象进行序列化与反序列化

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

package com.controller;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;

import java.io.*;

/**
 * @program: demo
 * @description: 序列化测试
 * @author: lee
 * @create: 2018-08-24
 **/
public class TestUtils implements Serializable {
    private String name;
    private String age;
    private String sex;
    private static final long serialVersionUID = 1L;

    public String getName() {
        return name;
    }

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

    public String getAge() {
        return age;
    }

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

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "TestUtils{" +
                "name='" + name + '\'' +
                ", age='" + age + '\'' +
                ", sex='" + sex + '\'' +
                '}';
    }

    public static void main(String[] args) {
        TestUtils testUtils = new TestUtils();
        testUtils.setName("jianxin");
        testUtils.setAge("12");
        testUtils.setSex("男");
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
            oos.writeObject(testUtils);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(oos);
        }
        File file = new File("tempFile");
        ObjectInputStream objectInputStream = null;
        try {
            objectInputStream = new ObjectInputStream(new FileInputStream(file));
            TestUtils testUtils1 = (TestUtils) objectInputStream.readObject();
            System.out.println(testUtils1);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(objectInputStream);
            try {
                FileUtils.forceDelete(file);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

输出结果

TestUtils{name='jianxin', age='12', sex='男'}

序列化及反序列化相关知识

  • 在Java中,只要一个类实现了 java.io.Serializable 接口,那么它就可以被序列化。

  • 通过 ObjectOutputStream 和 ObjectInputStream 对对象进行序列化及反序列化

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

  • 序列化并不保存静态变量

  • 要想将父类对象也序列化,就需要让父类也实现 Serializable 接口。

  • Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。

  • 服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串等,希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全。

package com.inspur.cyjcys.ajjq.controller;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;

import java.io.*;

/**
 * @program: demo
 * @description: 序列化测试
 * @author: lee
 * @create: 2018-08-24
 **/
public class TestUtils implements Serializable {
    private String name;
    private String age;
    private transient String sex;
    private static final long serialVersionUID = 1L;

    public String getName() {
        return name;
    }

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

    public String getAge() {
        return age;
    }

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

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    @Override
    public String toString() {
        return "TestUtils{" +
                "name='" + name + '\'' +
                ", age='" + age + '\'' +
                ", sex='" + sex + '\'' +
                '}';
    }

    public static void main(String[] args) {
        TestUtils testUtils = new TestUtils();
        testUtils.setName("lilang");
        testUtils.setAge("12");
        testUtils.setSex("男");
        ObjectOutputStream oos = null;
        try {
            oos = new ObjectOutputStream(new FileOutputStream("tempFile"));
            oos.writeObject(testUtils);
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(oos);
        }
        File file = new File("tempFile");
        ObjectInputStream objectInputStream = null;
        try {
            objectInputStream = new ObjectInputStream(new FileInputStream(file));
            TestUtils testUtils1 = (TestUtils) objectInputStream.readObject();
            System.out.println(testUtils1);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(objectInputStream);
            try {
                FileUtils.forceDelete(file);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

输出结果

TestUtils{name='lilang', age='12', sex='null'}

ArrayList的序列化

如何自定义的序列化和反序列化策略

java.util.ArrayList 的源码

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
    private static final long serialVersionUID = 8683452581122892189L;
    transient Object[] elementData; // non-private to simplify nested class access
    private int size;
}

笔者省略了其他成员变量,从上面的代码中可以知道ArrayList实现了 java.io.Serializable 接口,那么我们就可以对它进行序列化及反序列化。因为elementData是 transient 的,所以我们认为这个成员变量不会被序列化而保留下来。我们写一个Demo,验证一下我们的想法:

package com.inspur.cyjcys.ajjq.controller;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

/**
 * @program: demo
 * @description: 序列化测试
 * @author: lee
 * @create: 2018-08-24
 **/
public class TestUtils implements Serializable {

    public static void main(String[] args) {
        List<String> list = new ArrayList<String>();
        list.add("1");
        list.add("2");
        list.add("3");
        ObjectOutputStream outputStream =null ;
        try {
            outputStream = new ObjectOutputStream(new FileOutputStream("stringfile"));
            outputStream.writeObject(list);
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            IOUtils.closeQuietly(outputStream);
        }

        File file = new File("stringfile");
        ObjectInputStream objectInputStream = null;
        try {
            objectInputStream = new ObjectInputStream(new FileInputStream(file));
            List<String> newStringList = (List<String>)objectInputStream.readObject();
            System.out.println(newStringList);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException ex){
            ex.printStackTrace();
        }finally {
            IOUtils.closeQuietly(objectInputStream);
        }
        if(file.exists()){
            try {
                FileUtils.forceDelete(file);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
}

输出结果

[1, 2, 3]

ArrayList底层是通过数组实现的。那么数组 elementData 其实就是用来保存列表中的元素的。通过该属性的声明方式我们知道,他是无法通过序列化持久化下来的。那么为什么结果却通过序列化和反序列化把List中的元素保留下来了呢?

writeObject和readObject方法

在ArrayList中定义了来个方法: writeObject 和 readObject 。

这里先给出结论:

在序列化过程中,如果被序列化的类中定义了writeObject 和 readObject 方法,虚拟机会试图调用对象类里的 writeObject 和 readObject 方法,进行用户自定义的序列化和反序列化。

如果没有这样的方法,则默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。

用户自定义的 writeObject 和 readObject 方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。

private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        elementData = EMPTY_ELEMENTDATA;

        // Read in size, and any hidden stuff
        s.defaultReadObject();

        // Read in capacity
        s.readInt(); // ignored

        if (size > 0) {
            // be like clone(), allocate array based upon size not capacity
            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{
        // Write out element count, and any hidden stuff
        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实际上是动态数组,每次在放满以后自动增长设定的长度值,如果数组自动增长长度设为100,而实际只放了一个元素,那就会序列化99个null元素。为了保证在序列化的时候不会将这么多null同时进行序列化,ArrayList把元素数组设置为transient。

前面说过,为了防止一个包含大量空对象的数组被序列化,为了优化存储,所以,ArrayList使用 transient 来声明 elementData 。

但是,作为一个集合,在序列化过程中还必须保证其中的元素可以被持久化下来,所以,通过重写 writeObject 和 readObject 方法的方式把其中的元素保留下来。

writeObject 方法把 elementData 数组中的元素遍历的保存到输出流(ObjectOutputStream)中。

readObject 方法从输入流(ObjectInputStream)中读出对象并保存赋值到 elementData 数组中。

如果一个类中包含writeObject 和 readObject 方法,那么这两个方法是怎么被调用的?

在使用ObjectOutputStream的writeObject方法和ObjectInputStream的readObject方法时,会通过反射的方式调用。

总结

  • 如果一个类想被序列化,需要实现Serializable接口。否则将抛出 NotSerializableException 异常,这是因为,在序列化操作过程中会对类型进行检查,要求被序列化的类必须属于Enum、Array和Serializable类型其中的任何一种。

  • 在变量声明前加上transient关键字,可以阻止该变量被序列化到文件中。

  • 在类中增加writeObject 和 readObject 方法可以实现自定义序列化策略

Java 序列化的高级认识

序列化 ID 问题

情境:两个客户端 A 和 B 试图通过网络传递对象数据,A 端将对象 C 序列化为二进制数据再传给 B,B 反序列化得到 C。

问题:C 对象的全类路径假设为 com.inout.Test,在 A 和 B 端都有这么一个类文件,功能代码完全一致。也都实现了 Serializable 接口,但是反序列化时总是提示不成功。

解决:虚拟机是否允许反序列化,不仅取决于类路径和功能代码是否一致,一个非常重要的一点是两个类的序列化 ID 是否一致(就是 private static final long serialVersionUID = 1L)。如下代码中,虽然两个类的功能代码完全一致,但是序列化 ID 不同,他们无法相互序列化和反序列化。

package com.inout; 
 
import java.io.Serializable; 
 
public class A implements Serializable { 
 
    private static final long serialVersionUID = 1L; 
 
    private String name; 
    
    public String getName() 
    { 
        return name; 
    } 
    
    public void setName(String name) 
    { 
        this.name = name; 
    } 
} 
 
package com.inout; 
 
import java.io.Serializable; 
 
public class A implements Serializable { 
 
    private static final long serialVersionUID = 2L; 
    
    private String name; 
    
    public String getName() 
    { 
        return name; 
    } 
    
    public void setName(String name) 
    { 
        this.name = name; 
    } 
}

特性使用案例

读者应该听过 Façade 模式,它是为应用程序提供统一的访问接口,案例程序中的 Client 客户端使用了该模式,案例程序结构图如图 所示。

Client 端通过 Façade Object 才可以与业务逻辑对象进行交互。而客户端的 Façade Object 不能直接由 Client 生成,而是需要 Server 端生成,然后序列化后通过网络将二进制对象数据传给 Client,Client 负责反序列化得到 Façade 对象。该模式可以使得 Client 端程序的使用需要服务器端的许可,同时 Client 端和服务器端的 Façade Object 类需要保持一致。当服务器端想要进行版本更新时,只要将服务器端的 Façade Object 类的序列化 ID 再次生成,当 Client 端反序列化 Façade Object 就会失败,也就是强制 Client 端从服务器端获取最新程序。

静态变量序列化

先看代码

package chaprer3;

import sun.misc.IOUtils;

import java.io.*;

/**
 * @program: demo
 * @description: 主线程
 * @author: lee
 * @create: 2018-08-13
 **/
public class MainRun implements Serializable {
    private static final long serialVersionUID = 1L;
    public static int staticVar = 5;

    public static void main(String[] args) throws InterruptedException {
        ObjectOutputStream outputStream = null;
        try {
            outputStream = new ObjectOutputStream(new FileOutputStream("result.obj"));
            outputStream.writeObject(new MainRun());
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        MainRun.staticVar = 10;
        try {
            ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("result.obj"));
            MainRun mainRun = (MainRun) objectInputStream.readObject();
            objectInputStream.close();
            System.out.println(mainRun.staticVar);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException ex) {
            ex.printStackTrace();
        }

    }
}    

输出结果

10

之所以打印 10 的原因在于序列化时,并不保存静态变量,这其实比较容易理解,序列化保存的是对象的状态,静态变量属于类的状态,因此 序列化并不保存静态变量。 看一下idea编译以后的代码吧

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package chaprer3;

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

public class MainRun implements Serializable {
    private static final long serialVersionUID = 1L;
    public static int staticVar = 5;

    public MainRun() {
    }

    public static void main(String[] args) throws InterruptedException {
        ObjectOutputStream outputStream = null;

        try {
            outputStream = new ObjectOutputStream(new FileOutputStream("result.obj"));
            outputStream.writeObject(new MainRun());
            outputStream.close();
        } catch (IOException var6) {
            var6.printStackTrace();
        }

        staticVar = 10;

        try {
            ObjectInputStream ex = new ObjectInputStream(new FileInputStream("result.obj"));
            MainRun mainRun = (MainRun)ex.readObject();
            ex.close();
            System.out.println(staticVar);
        } catch (IOException var4) {
            var4.printStackTrace();
        } catch (ClassNotFoundException var5) {
            var5.printStackTrace();
        }

    }
}

父类的序列化与 Transient 关键字

情境:一个子类实现了 Serializable 接口,它的父类都没有实现 Serializable 接口,序列化该子类对象,然后反序列化后输出父类定义的某变量的数值,该变量数值与序列化时的数值不同。

解决:要想将父类对象也序列化,就需要让父类也实现Serializable 接口。如果父类不实现的话的,就 需要有默认的无参的构造函数。在父类没有实现 Serializable 接口时,虚拟机是不会序列化父对象的,而一个 Java 对象的构造必须先有父对象,才有子对象,反序列化也不例外。所以反序列化时,为了构造父对象,只能调用父类的无参构造函数作为默认的父对象。因此当我们取父对象的变量值时,它的值是调用父类无参构造函数后的值。如果你考虑到这种序列化的情况,在父类无参构造函数中对变量进行初始化,否则的话,父类变量值都是默认声明的值,如 int 型的默认是 0,string 型的默认是 null。

Transient 关键字的作用是控制变量的序列化,在变量声明前加上该关键字,可以阻止该变量被序列化到文件中,在被反序列化后,transient 变量的值被设为初始值,如 int 型的是 0,对象型的是 null。

特性使用案例

我们熟悉使用 Transient 关键字可以使得字段不被序列化,那么还有别的方法吗?根据父类对象序列化的规则,我们可以将不需要被序列化的字段抽取出来放到父类中,子类实现 Serializable 接口,父类不实现,根据父类序列化规则,父类的字段数据将不被序列化,形成类图如图所示。

上图中可以看出,attr1、attr2、attr3、attr5 都不会被序列化,放在父类中的好处在于当有另外一个 Child 类时,attr1、attr2、attr3 依然不会被序列化,不用重复抒写 transient,代码简洁。

package chaprer3;

/**
 * @program: demo
 * @description: 父类
 * @author: lee
 * @create: 2018-08-24
 **/
public class ParentClass {
    private int c;

    @Override
    public String toString() {
        return "ParentClass{" +
                "c=" + c +
                '}';
    }

    public int getC() {
        return c;
    }

    public void setC(int c) {
        this.c = c;
    }
}

package chaprer3;

import sun.misc.IOUtils;

import java.io.*;

/**
 * @program: demo
 * @description: 主线程
 * @author: lee
 * @create: 2018-08-13
 **/
public class MainRun extends ParentClass implements Serializable {
    private static final long serialVersionUID = 1L;
    public static void main(String[] args) throws InterruptedException {
        ObjectOutputStream outputStream = null;
        try {
            outputStream = new ObjectOutputStream(new FileOutputStream("result.obj"));
            outputStream.writeObject(new MainRun());
            outputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("result.obj"));
            MainRun mainRun = (MainRun) objectInputStream.readObject();
            objectInputStream.close();
            ParentClass parentClass = mainRun;
            System.out.println(parentClass);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException ex) {
            ex.printStackTrace();
        }

    }
}    

输出结果

ParentClass{c=0}

如下父类的时候

/**
 * @program: demo
 * @description: 父类
 * @author: lee
 * @create: 2018-08-24
 **/
public class ParentClass {
    private int c = 5;

    @Override
    public String toString() {
        return "ParentClass{" +
                "c=" + c +
                '}';
    }

    public int getC() {
        return c;
    }

    public void setC(int c) {
        this.c = c;
    }
}

输出结果是

ParentClass{c=5}
对敏感字段加密

情境:服务器端给客户端发送序列化对象数据,对象中有一些数据是敏感的,比如密码字符串等,希望对该密码字段在序列化时,进行加密,而客户端如果拥有解密的密钥,只有在客户端进行反序列化时,才可以对密码进行读取,这样可以一定程度保证序列化对象的数据安全。

解决:在序列化过程中,虚拟机会试图调用对象类里的 writeObject 和 readObject 方法,进行用户自定义的序列化和反序列化,如果没有这样的方法,则默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法。用户自定义的 writeObject 和 readObject 方法可以允许用户控制序列化的过程,比如可以在序列化的过程中动态改变序列化的数值。基于这个原理,可以在实际应用中得到使用,用于敏感字段的加密工作,如下展示了这个过程。

    private static final long serialVersionUID = 1L;
 
   private String password = "pass";
 
   public String getPassword() {
       return password;
   }
 
   public void setPassword(String password) {
       this.password = password;
   }
 
   private void writeObject(ObjectOutputStream out) {
       try {
           PutField putFields = out.putFields();
           System.out.println("原密码:" + password);
           password = "encryption";//模拟加密
           putFields.put("password", password);
           System.out.println("加密后的密码" + password);
           out.writeFields();
       } catch (IOException e) {
           e.printStackTrace();
       }
   }
 
   private void readObject(ObjectInputStream in) {
       try {
           GetField readFields = in.readFields();
           Object object = readFields.get("password", "");
           System.out.println("要解密的字符串:" + object.toString());
           password = "pass";//模拟解密,需要获得本地的密钥
       } catch (IOException e) {
           e.printStackTrace();
       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       }
 
   }
 
   public static void main(String[] args) {
       try {
           ObjectOutputStream out = new ObjectOutputStream(
                   new FileOutputStream("result.obj"));
           out.writeObject(new Test());
           out.close();
 
           ObjectInputStream oin = new ObjectInputStream(new FileInputStream(
                   "result.obj"));
           Test t = (Test) oin.readObject();
           System.out.println("解密后的字符串:" + t.getPassword());
           oin.close();
       } catch (FileNotFoundException e) {
           e.printStackTrace();
       } catch (IOException e) {
           e.printStackTrace();
       } catch (ClassNotFoundException e) {
           e.printStackTrace();
       }
   }
序列化存储规则
package com.controller;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

/**
 * @program: demo
 * @description: 序列化测试
 * @author: lee
 * @create: 2018-08-24
 **/
public class TestUtils implements Serializable {
    public static void main(String[] args) {
        ObjectOutputStream out = null;
        TestUtils test = new TestUtils();
        try {
            out = new ObjectOutputStream(
                    new FileOutputStream("result.obj"));
            //试图将对象两次写入文件
            out.writeObject(test);
            out.flush();
            System.out.println(new File("result.obj").length());
            out.writeObject(test);
            out.close();
            System.out.println(new File("result.obj").length());
        } catch (IOException e) {
            e.printStackTrace();
        }

        ObjectInputStream oin = null;
        try {
            oin = new ObjectInputStream(new FileInputStream(
                    "result.obj"));
            //从文件依次读出两个文件
            TestUtils t1 = (TestUtils) oin.readObject();
            TestUtils t2 = (TestUtils) oin.readObject();
            oin.close();
            //判断两个引用是否指向同一个对象
            System.out.println(t1 == t2);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException ex) {
            ex.printStackTrace();
        }
    }
}

输出结果是

64
69
true

同一对象两次写入文件,打印出写入一次对象后的存储大小和写入两次后的存储大小,然后从文件中反序列化出两个对象,比较这两个对象是否为同一对象。一般的思维是,两次写入对象,文件大小会变为两倍的大小,反序列化时,由于从文件读取,生成了两个对象,判断相等时应该是输入 false 才对,但是最后结果输出如上所示。

解答:Java 序列化机制为了节省磁盘空间,具有特定的存储规则,当写入文件的为同一对象时,并不会再将对象的内容进行存储,而只是再次存储一份引用,上面增加的 5字节的存储空间就是新增引用和一些控制信息的空间。反序列化时,恢复引用关系,使得上述代码中指向的是同一个对象,二者相等,输出 true。该存储规则极大的节省了存储空间。

修改如下代码

package chaprer3;

import java.io.*;

/**
 * @program: demo
 * @description: 主线程
 * @author: lee
 * @create: 2018-08-13
 **/
public class MainRun  implements Serializable {
    private int anInt;

    public int getAnInt() {
        return anInt;
    }

    public void setAnInt(int anInt) {
        this.anInt = anInt;
    }

    public static void main(String[] args) {
        ObjectOutputStream out = null;
        MainRun test = new MainRun();
        try {
            out = new ObjectOutputStream(
                    new FileOutputStream("result.obj"));
            //试图将对象两次写入文件
            test.setAnInt(12);
            out.writeObject(test);
            out.flush();
            System.out.println(new File("result.obj").length());
            test.setAnInt(157);
            out.writeObject(test);
            out.close();
            System.out.println(new File("result.obj").length());
        } catch (IOException e) {
            e.printStackTrace();
        }

        ObjectInputStream oin = null;
        try {
            oin = new ObjectInputStream(new FileInputStream(
                    "result.obj"));
            //从文件依次读出两个文件
            MainRun t1 = (MainRun) oin.readObject();
            MainRun t2 = (MainRun) oin.readObject();
            oin.close();
            //判断两个引用是否指向同一个对象
            System.out.println(t1.getAnInt());
            System.out.println(t2.getAnInt());
            System.out.println(t1 == t2);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException ex) {
            ex.printStackTrace();
        }
    }
}    

输出结果是

49
54
12
12
true

原因就是第一次写入对象以后,第二次再试图写的时候,虚拟机根据引用关系知道已经有一个相同对象已经写入文件,因此只保存第二次写的引用,所以读取时,都是第一次保存的对象。读者在使用一个文件多次 writeObject 需要特别注意这个问题。

借鉴如下:

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

http://www.cnblogs.com/Qian123/articles/5665671.html

转载于:https://my.oschina.net/jiansin/blog/1933623

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值