java序列化1

前言:

   如果仔细的分析网络编程的话,就会发现 这个里面就两件事情,一个是协议,即采用什么协议进行传输。另一个就是序列化,通过什么样的方式共享数据,我序列化的东西对方能够反序列化出来。既然序列化如此重要,那就闲话少说,进入正题,为了看得方便的,我回重点讲解 java序列化,然后在这个基础上讲解改进以及其他的算法等。

 

------------------------------------------------------------------------------------------------- Java序列化------------------------------------------------------------------------------------------------------------------首先看下序列化和反序列化的步骤

java序列化1_第1张图片

1、根据某种算法将对象转化为字节序列

2、将这个字节序列通过输出流写入到存储(文件,数据库,网络)中

3、字节序列通过存储进行保存和传输

4、当要用到某种对象的时候,首先通过输入流从存储中读出字节序列

5、将字节序列通过反序列化方法,还原为对象

并不是所有的类都适合于序列化,比如

1、带有安全信息的类,因为序列化之后的类不是安全的,很容易被破解,没有秘密

2、有些对类实例数量有限制的,比如有些是单例的类,因为序列化过后,理论上可以生成任意个实例。

有些类需要被序列化,有些类不需要,那么就需要有个标志标识类是否可以序列化的, 这个标识符就是SerializableExternalizable,实现了这两个接口中的任何一个都可以被序列化,需要说的是Externalizable继承自Serializable Externalizable可以定制自己的序列化和反序列化方法。

当然如果 自己写个工具进行java的序列化和反序列化,就不需要上面这些内容,比如我们下面会讲到的googl procbuf apache thrift,以及apache avro,有点跑题了。

顺着上面讲有个java命令行工具可以查看类是否可以被序列化            serialver

java序列化1_第2张图片

从上面可以看出 可以序列化的类里面有一个序列化uid,这个东东也很重要。这个我就不展开说了,不然东西就太多了,具体可以参见

1.一篇较好的关于serialVesionUid的说明: 
  http://www.mkyong.com/java-best-practices/understand-the-serialversionuid/ 
2.serialVesionUid
相关讨论 
  http://stackoverflow.com/questions/888335/why-generate-long-serialversionuid-instead-of-a-simple-1l 
3.compiler-generated ID
生成算法 
http://java.sun.com/javase/6/docs/platform/serialization/spec/class.html#4100

4. java序列化声明一个显式的UID      http://wu-yansheng.iteye.com/blog/909626

多说一句,这个uid 其实是为了一个问题而存在的,就是版本,要序列化类的版本,这个在讲解代码的时候会看到具体的应用。

好了,基本的东西就这么多,具体哪些该序列化,哪些不被序列化,子类和父类的关系等等,接下来讲解代码的时候会具体的看,这样就不会比较枯燥了

首先看下实现Serializable的序列化,问一个弱智的问题,为什么要有这个标示接口,没有不行么?当然你可以在概念上说出很多,那么我们从代码的角度看下如果不实现这个接口,会有什么结果。

首先一个java

public class Person implements Serializable {

    private static final long serialVersionUID = 1L;

    private String name;

    private int age;

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

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

    public String getName() {return name;  }

    public int getAge() {return age;}

}

序列化到本地的类

public class ToFile {

    public static void main(String[] args) throws Exception {

       Person myPerson = new Person();

       myPerson.setAge(10);myPerson.setName("wangshiji");

       FileOutputStream fos = new FileOutputStream("d:\\myPerson.txt");

       ObjectOutputStream oos = new ObjectOutputStream(fos);

       oos.writeObject(myPerson);

       oos.close();

    }

}

打开文件,用16进制编码得到如下数据,截图如下

 

 

为了更加好看,我在前面加入Ox标示,如下

0XAC 0XED 0X0 0X5 0X73 0X72 0X0 0X6 0X50 0X65 0X72 0X73 0X6F 0X6E 0X0 0X0 0X0 0X0 0X0 0X0 0X0 0X1 0X2 0X0 0X2 0X49 0X0 0X3 0X61 0X67 0X65 0X4C 0X0 0X4 0X6E 0X61 0X6D 0X65 0X74 0X0 0X12 0X4C 0X6A 0X61 0X76 0X61 0X2F 0X6C 0X61 0X6E 0X67 0X2F 0X53 0X74 0X72 0X69 0X6E 0X67 0X3B 0X78 0X70 0X0 0X0 0X0 0XA 0X74 0X0 0X9 0X77 0X61 0X6E 0X67 0X73 0X68 0X69 0X6A 0X69

我们来分析下这段代码:

  1. 0XAC,0XED,0X0,0X5  是头信息,0XAC,0XED是协议,0X0,0X5 是版本号,提前说下有些常量是在ObjectStreamConstants接口中定义的,比如0X70等。
  2. 0X73,0X72…后为当前序列化对象的类信息

       a)0X73 代表一个新对象,说明下面开始描述对象信息

       b)0X72 代表一个新类描述,说明下面开始描述类信息

       c) 0X0,0X6 代表类名的长度,代表后面的5个字节为类名称Person ,我偷懒,没有添加包

       d) 0x50ascii就是80 P 0x65ascii就是101 e 0x72(114 r) 0x73(115 s) 0x6F(111 o) 0x6E(110 n)   这几个字符就是Person

    3.0X0,0X0,0X0,0X0,0X0,0X0,0X0,0X1 8个字节代表我们的serialVersionUID的。

4.0X2标识为实现Serializable的,如果是0X3代表实现Externalizable

5.0X0 0X2标示几个属性,没错,我们就是两个属性

6. 0X49 标示类型 int,还有其他类型,0X0 0X3表示属性名的长度有3个字节,0X61 0X67 0X65 标示为age

    B: byte,C: char,D: double, F: float,I: int,J: long,L: class or interface,S: short,Z: boolean,[: array

70X4C值为L代表域类型,0X0 0X4 属性名有四个字节 0X6E 0X61 0X6D 0X65  标示name

8. 0X74 标示一个新字符串,0X0 0X12标示字符串的长度为jvm签名对象方法 0X4C 0X6A 0X61 0X76 0X61 0X2F 0X6C 0X61 0X6E 0X67 0X2F 0X53 0X74 0X72 0X69 0X6E 0X67 0X3B标示Ljava/lang/String;  注意着标点符号;也是前面方法的一部分

9. 0X78 代表对象块描述完毕,0X70代表没有父类引用,就是没有父类

10. 0X0 0X0 0X0 0XA 这个就是int类型的10了。

11. 0X74 标示一个新字符串,0X0 0X9字符串长度,0X77 0X61 0X6E 0X67 0X73 0X68 0X69 0X6A 0X69标示wangshiji

上面就是序列化内容。参考资料http://java.sun.com/j2se/1.4/pdf/serial-spec.pdf,http://www.javaworld.com/community/node/2915,http://longdick.iteye.com/blog/458557

下面从代码层面看下 上面这些内容是怎么来的

java序列化1_第3张图片

通过序列图我们可以看到首先判断下类型,然后在第7步将类型信息写入进去,在第8步将属性信息写入,在第8步的时候要注意,是首先判断原始类型(8种)的写入,然后其他类型(包含String)的写入都是重新调用writeObject0方法实现的,因此如果属性是一个类(单独写的pojo),类型信息就会在这个时候写入。在第7步写入类型信息的时候,如果父类不是Object,则会在这个时候讲父类的类型信息写入。

那我们再看下反序列化的代码,只有将两部分都看完,才会看到序列化的一些约束,以及一些异常才会做到心中有数

java序列化1_第4张图片

突然发现上面这些代码没有任何意义,自己看都可以看到的。那我们看下如果遇到下面这些问题需要在那个地方找吧

1  1、pojo中的方法会被序列化么?  从过程可以看到方法不会被写入的,仅仅是写入属性,这个很好理解。

2  2、Java为我们提供了transient这个关键字。如果一个变量被声明成transient,那么 在序列化的过程 中,这个变量是会被无视的。如果是你 是变量都没有序列化到文件中,还是无视了变量值呢?答案当然是变量不序列化到文件中,不要放多余的数据,在writeObject0 方法中ObjectStreamClass desc = ObjectStreamClass.lookup(cl, true); lookup的过程中计算的field的数量,将Modifier.STATIC | Modifier.TRANSIENT 类型的给过滤掉了,这个方法和做的动作匹配度不是很高,从这里可以看到夹带私货大家都在做。

3  3、和上面相反,如果一个类实现了Serializable接口,但是它的父类没有实现 ,这个类可不可以序列化?答案是肯定可以的,不然没有父类(也就是父类是Object)的就没法序列化了。但是有一个要求就是在不可序列化的父类中一定要有一个无参构造方法,看好了,是不可序列化的父类,序列化的时候没有问题,但是在反序列化的时候会报错,no valid constructor,但是这个报错也有问题,应该non- serializable superclass 没有,至于为什么一定要这个无参构造函数,可以自己思考下。

4  4、仔细查看代码就会发现 虽然Serializable接口是一个标示接口,但是这里面和其他的同样的接口还是有一些不同,如果这里面还是隐藏了两个方法private void writeObject() {}; private void readObject() {},并且最搞的是还是private方法,应该是刚开始实现的问题,然后这样的设计明显会被大家吐槽的,因此后来就加入了Externalizable,这个接口实现了Externalizable extends java.io.Serializable,并有自己的两个写和读方法,完全是为了弥补Serializable的缺陷,没有什么好讲的。

你可能感兴趣的:(java序列化)