kingpong

博客园 首页 新随笔 联系 订阅 管理

首先我们说答案:实体类对象在保存在内存中的,而对于web应用程序而言,很多客户端会对服务器后台提交数据请求,如得到某种类型的商品,此时后台程序会从数据库中读取符合条件的记录,并它们打包成对象的集合,再转化为JSON,回传给前端渲染。类似请求会有很多,所以如果有这么多对象常驻内存,服务器的内存是承载不了的,此时web容器会将一个暂时没有使用的对象保存在硬盘中,这个过程我们就称之为序列化。等到需要使用时,再把再把保存在硬盘中的对象还原到内存中,这个过程,我们称之为反序列化。这种策略实质上与Windows中设置虚拟内存的做法是一样的,我们开玩笑就叫“内存不够外存凑”。而在Java中实现序列化接口是不需要实现任何方法的,因为JDK在这个接口中没定义任何方法,实现这个接口的意义只是告诉JDK当前这个类可以进行序列化。至于SerialversionUID,是用于序列化和反序列化过程中进行校验用的,一般用长整型的数据保存,用于判断当前类和反序列化后生成对象是否是同一个版本,如果还想不通我们可以举个例的,在经典的谍战片都有这么一个情节,某个情报人员从家里出门后,要在门上夹一根头发,然后出门,回来开门时再观察这根头发还在不在,如果不在了,说明家里可能来过不速之客。这个长整型的SerialversionUID与这根头发的意义是一样的,头发很细小,只要门有任何状态的改变,它就可以感知到,造成头发的掉落。由于序列化是把一个对象变成字节流,而反序列化则是将字节流程重新组织成一个类的对象,因此一个长整型的数,只有需要反序列的类有细微的变化,反序列后就会造成这个长整型数的改变,如何使用一个比较简单的int类型,有可能类发生改变,然后反序列化后这个数仍然没有改变,从而丧失校验的功能。

 

下面再开始制式化的解释与说明

一、什么是序列化和反序列化

把对象转换为字节序列的过程称为对象的序列化。
把字节序列恢复为对象的过程称为对象的反序列化。
  对象的序列化主要有两种用途:
  1) 把对象的字节序列永久地保存到硬盘上,通常存放在一个文件中;
  2) 在网络上传送对象的字节序列。

二、实体类为什么要序列化

客户端访问了某个能开启会话功能的资源, web服务器就会创建一个与该客户端对应的HttpSession对象,每个HttpSession对象都要站用一定的内存空间。如果在某一时间段内访问站点的用户很多,web服务器内存中就会积累大量的HttpSession对象,消耗大量的服务器内存,即使用户已经离开或者关闭了浏览器,web服务器仍要保留与之对应的HttpSession对象,在他们超时之前,一直占用web服务器内存资源。

web服务器通常将那些暂时不活动但未超时的HttpSession对象转移到文件系统或数据库中保存,服务器要使用他们时再将他们从文件系统或数据库中装载入内存,这种技术称为Session的持久化。

将HttpSession对象保存到文件系统或数据库中,需要采用序列化的方式将HttpSession对象中的每个属性对象保存到文件系统或数据库中;将HttpSession对象从文件系统或数据库中装载如内存时,需要采用反序列化的方式,恢复HttpSession对象中的每个属性对象。所以存储在HttpSession对象中的每个属性对象必须实现Serializable接口。

三、Mybatis为何要求持久层的javabean序列化?

 MyBatis使用SerializedCache序列化缓存来实现可读写缓存类,并通过序列化和反序列化来保证通过缓存获取数据时,得到的是一个新的实例。如果配置为只读缓存,MyBatis就会使用Map来存储缓存值,这种情况下,从缓存中获取的对象就是同一个实例。

MyBatis中配置缓存时,缓存元素<cache>有个readOnly属性,readOnly属性可以被设置为 true 或 false。只读缓存将对所有调用者返回同一个实例,因为对象没有进行序列化,所以速度最快。可写的缓存将通过序列化来返回一个缓存对象的拷贝。因为对象进行了序列化,会比较慢,但是得到的都是新的对象,线程安全。默认值是 false。即Mybatis的二级缓存默认是可写的,可写缓存会使用序列化。

序列化缓存
* 先将对象序列化成2进制,再缓存,好处是将对象压缩了,省内存
* 坏处是速度慢了(因为对象需要进行序列化)

总结:Mybatis通过序列化得到对象的新实例,保证多线程安全(因为是从缓存中取数据,速度还是比从数据库获取要快)。具体说就是对象序列化后存储到缓存中,从缓存中取数据时是通过反序列化得到新的实例。

四、为什么定义SerializableID

对于JVM来说,要进行持久化的类必须要有一个标记,只有持有这个标记JVM才允许类创建的对象可以通过其IO系统转换为字节数据,从而实现持久化,而这个标记就是Serializable接口。而在反序列化的过程中则需要使用serialVersionUID来确定由那个类来加载这个对象,所以我们在实现Serializable接口的时候,一般还会要去尽量显示地定义serialVersionUID.

如果我们在序列化中没有显示地声明serialVersionUID,则序列化运行时将会根据该类的各个方面计算该类默认的serialVersionUID值。但是,Java官方强烈建议所有要序列化的类都显示地声明serialVersionUID字段,因为如果高度依赖于JVM默认生成serialVersionUID,可能会导致其与编译器的实现细节耦合,这样可能会导致在反序列化的过程中发生意外的InvalidClassException异常。因此,为了保证跨不同Java编译器实现的serialVersionUID值的一致,实现Serializable接口的必须显示地声明serialVersionUID字段。我们前面做了了一个类比 ,某个情报人员从家里出门后,要在门上夹一根头发,然后出门,回来开门时再观察这根头发还在不在,如果不在了,说明家里可能来过不速之客。这个长整型的SerialversionUID与这根头发的意义是一样的,也就是说JDK对内存对象反序列化之后,需要利用这个长整型的数与现有的类的这个同名成员进行比对,看是否相同,如果不相同则会以抛异常的方式报警,提示反序列化失败,用于提示保存在原来序列化时的对象结构与当前类的结构不相同了(有可能是反序列之前类发生改变了)。那为什么需要使用一个长整型的数来作为serialVersionUID,一个普通的int不行吗,

此外serialVersionUID字段地声明要尽可能使用private关键字修饰,这是因为该字段的声明只适用于声明的类,该字段作为成员变量被子类继承是没有用处的!有个特殊的地方需要注意的是,数组类是不能显示地声明serialVersionUID的,因为它们始终具有默认计算的值,不过数组类反序列化过程中也是放弃了匹配serialVersionUID值的要求。

posted on 2022-07-20 18:43  WangdaFish  阅读(928)  评论(0编辑  收藏  举报