java序列化

java对象序列化

什么是序列化

在java程序中,创建一个对象时,一般都是存活在内存中(交由GC自动管理)。但是只要程序终止,这个对象肯定就不存在了。

如果对象能够在保存起来,在程序下次运行的时候或者其它程序上使用,那么将会很方便。

Java可以将实现了Serializable接口的对象转化成一个字节序列,并能够在以后将这个字节序列完全恢复成原来的对象,这个过程就称为JAVA的序列化。

场景

JAVA序列化主要支持2种场景:

  1. JAVA的远程方法调用:Remote Method Invocation,简称RMI;它使存活于其它计算机上的对象使用起来就像是存活于本机上一样
  2. 当向远程对象发送消息时,需要通过对象序列化来传输参数和返回值

类似下图,常见的RPC方法调用示意图,都会有序列化这一步:

来吧,看一个例子

首先声明一个普通的Java类,实现了Serializable接口,没有什么特殊之处

public class Data implements Serializable {

    private int n;

    public Data(int n) {
        this.n = n;
    }

    @Override
    public String toString() {
        return "Data{" +
                "n=" + n +
                '}';
    }
}

写个测试用例,将对象保存到本地文件data.out中

public class Demo1 {

    @Test
    public void test1() throws IOException, ClassNotFoundException {
        // 创建一个对象
        Data data = new Data(1);
        // 序列化一个文件中
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("data.out"));
        out.writeObject(data);
        out.close();

        // 反序列化
        ObjectInputStream input = new ObjectInputStream(new FileInputStream("data.out"));
        Data data1 = (Data) input.readObject();
        // 打印对象信息
        System.out.println(data1);

    }
}

看下输出的结果:Data{n=1},说明了对象真的序列化成功,并成功反序列化了。

Serializable接口

Serializable是一个标识接口,只要实现了该接口的对象就可以进行序列化。如下为接口定义:
* @author  unascribed
 * @see java.io.ObjectOutputStream
 * @see java.io.ObjectInputStream
 * @see java.io.ObjectOutput
 * @see java.io.ObjectInput
 * @see java.io.Externalizable
 * @since   JDK1.1
 */
public interface Serializable {
}

如果一个类没有实现该接口,进行序列化时会怎么样呢?还是上面的例子,我们让Data类不实现Serializable接口。

public class Data {

    private int n;

    public Data(int n) {
        this.n = n;
    }

    @Override
    public String toString() {
        return "Data{" +
                "n=" + n +
                '}';
    }
}

重新运行Demo1,会发生什么结果呢?运行结果如下 ,报错序列化失败

java.io.NotSerializableException: com.example.serializa.Data

	at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
	at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
	at serializa.Demo1.test1(Demo1.java:16)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
serialVersionUID版本号
serialVersionUID字段,是一个很重要的点,在实现Serializable接口时,一般要求声明这个字段,那这个字段有什么用呢?先来做一个试验。首先让Data类生成一个随机的64位值
public class Data implements Serializable {

    // 版本号
    private static final long serialVersionUID = 8182156252372168014;
    private int n;

    public Data(int n) {
        this.n = n;
    }

    @Override
    public String toString() {
        return "Data{" +
                "n=" + n +
                '}';
    }
}

其次,进行序列化,将对象存入到data.out文件中

// 创建一个对象
        Data data = new Data(1);
        // 序列化一个文件中
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("data.out"));
        out.writeObject(data);
        out.close();

接着修改一下Data的serialVersionUID值,比如将8182156252372168014的值任意改动一下818215625237216804L。

最后再执行反序列化

@Test
    public void testSerialVersionUID() throws IOException, ClassNotFoundException {
        // 反序列化
        ObjectInputStream input = new ObjectInputStream(new FileInputStream("data.out"));
        Data data1 = (Data) input.readObject();
        // 打印对象信息
        System.out.println(data1);
    }

会看到控制台报以下错:版本号不一致,所以在实现Serializable接口时,最好声明一个serialVersionUID。因为 Java 的序列化机制是通过在运行时判断类的serialVersionUID来验证版本一致性的。在进行反序列化时,JVM 会把传来的字节流中的serialVersionUID与本地相应实体(类)的serialVersionUID进行比较,如果相同就认为是一致的,可以进行反序列化,否则就会出现序列化版本不一致的异常。这个可以在Serializable接口的JavaDoc上面看到!

java.io.InvalidClassException: com.example.serializa.Data; local class incompatible: stream classdesc serialVersionUID = 8182156252372168014, local class serialVersionUID = 818215625237216804

	at java.io.ObjectStreamClass.initNonProxy(ObjectStreamClass.java:616)
	at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1829)
	at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1713)
	at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:1986)
	at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1535)
	at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)
	at serializa.Demo1.testSerialVersionUID(Demo1.java:32)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:78)
	at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:57)
	at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
	at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
	at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
	at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
	at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
	at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
	at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
	at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
	at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:47)
	at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)

IntelliJ IDEA 中自动生成 serialVersionUID 的方法

在File-->Settings-->Editor-->Inspections中,勾上Serializable class without 'serialVersionUID',然后在类名上输入Alt+Enter,即可自动生成serialVersionUID

输入快捷键Alt+Enter

序列化控制

java的序列化会默认把对象的所有属性都序列化,只要它实现了Serializable接口。

那如果需要让某些属性不能被序列化,该怎么办呢?比如Login对象保存用户登录信息,用户登录后想把数据保存下来,但不包括password。这个时候就可以用transient关键字。

对Data改造一下,添加一个password字段,并声明为transient:

public class Data implements Serializable {

    private static final long serialVersionUID = -292017699019977661L;

    private int n;
    // 加上transient,该属性不会被序列化
    private transient String password;

    public Data(int n, String password) {
        this.n = n;
        this.password = password;
    }

    @Override
    public String toString() {
        return "Data{" +
                "n=" + n +
                ", password='" + password + '\'' +
                '}';
    }
}

执行一下test()1测试用例,看下输出结果:password为null

Data{n=1, password='null'}

static变量

那在这里有一个疑问,如果是static变量,会进行序列化么?还是Data例子,进行一下测试

public class Data implements Serializable {

    private static final long serialVersionUID = -292017699019977661L;

    private int n;
    // 加上transient,该属性不会被序列化
    private transient String password;
    // 静态变量
    private static String nickName ;

    public Data(int n, String password) {
        // 初始化静态变量
        nickName = "static-test";
        this.n = n;
        this.password = password;
    }

    @Override
    public String toString() {
        return "Data{" +
                "n=" + n +
                ", password='" + password + '\'' +
                ", nickName='" + nickName + "\'" +
                '}';
    }
}

还是Test1测试用例,执行一下

@Test
    public void test1() throws IOException, ClassNotFoundException {
        // 创建一个对象
        Data data = new Data(1, "test-password");

        // 序列化一个文件中
        ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream("data.out"));
        out.writeObject(data);
        out.close();

        // 反序列化
        ObjectInputStream input = new ObjectInputStream(new FileInputStream("data.out"));
        Data data1 = (Data) input.readObject();
        // 打印对象信息
        System.out.println(data1);
    }

看下运行结果:nickName被序列化了!?真是这样么

Data{n=1, password='null', nickName='static-test'}
再看一个例子

@Test
    public void testStatic() throws IOException, ClassNotFoundException {
        // 反序列化
        ObjectInputStream input = new ObjectInputStream(new FileInputStream("data.out"));
        Data data1 = (Data) input.readObject();
        // 打印对象信息
        System.out.println(data1);
    }

输出结果:nickName又没有被序列化,这是怎么回事呢?

Data{n=1, password='null', nickName='null'}
原因就是:

  • 同一个机器(而且是同一个进程),因为这个jvm已经把nickName加载进来了,所以你获取的是加载好的nickName,如果你是传到另一台机器或者你关掉程序重写写个程序读入data.out,此时因为别的机器或新的进程是重新加载nickName的,所以count信息就是初始时的信息,所以static变量是不能被序列化的
  • 静态变量是类级别的,序列化的是对象

常用的序列化框架

JDK自带的序列化

上面的例子都是jdk自带的序列化方式,JAVA语言本身提供,使用比较方便和简单。但是也有一定的缺点,不支持跨语言处理,性能相对不是很好,序列化以后产生的数据相对较大(转换为节点序列)

XML序列化框架 

XML使用广泛,而且标准成熟,比如很出名的WebService协议。XML序列化的好处在于可读性好,方便阅读和调试。但是序列化以后的字节码文件比较大,而且效率不高,适用于对性能不高,而且QPS较低的企业级内部系统之间的数据交换的场景,同时XML又具有语言无关性,所以还可以用于异构系统之间的数据交换和协议

JSON序列化框架 

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,相对于XML来说,JSON 的字节流更小,而且可读性也非常好。现在JSON数据格式在企业运用是最普遍的,JSON序列化常用的开源工具有很多。

1. Jackson (https://github.com/FasterXML/jackson) 
2. 阿里开源的 FastJson (https://github.com/alibaba/fastjon) 
3. Google的GSON (https://github.com/google/gson) 
这几种json序列化工具中,Jackson与fastjson要比GSON的性能要好,但是Jackson、GSON的稳定性要比Fastjson好,而fastjson的优势在于提供的api非常容易使用。

Hessian序列化框架 
Hessian是一个支持跨语言传输的二进制序列化协议,相对于Java默认的序列化机制来说,Hessian具有更好的性能和易用性,而且支持多种不同的语言。阿里很多的中间件就是用的Hessian进行序列化

最后转载一个各个框架的性能对比:https://www.cnblogs.com/lgjlife/p/10731099.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值