Java 部分序列化_JAVA序列化

二、java序列化机制

从上面的图中我们也已经看到了,java序列化主要有两个接口,这两个接口的实现方式,我都会给出,但是重点在于serialize接口的实现方式。在这一部分中,先给出序列化基本的代码实现,在下一部分当中再来看序列化有哪些需要注意的问题。OK,现在开始代码实现java的序列化机制。

1、使用Serializable接口实现序列化(重点,要牢记,第三部分会多次使用)

首先我们定义一个对象类User

public class User implementsSerializable {//序列化ID

private static final long serialVersionUID = 1L;private intage;privateString name;//getter和setter方法、//toString方法

}

接下来,在Test类中去实现序列化和反序列化。

public classTest {public static void main(String[] args) throwsException, IOException {//SerializeUser();

DeSerializeUser();

}//序列化方法

private static void SerializeUser() throwsFileNotFoundException, IOException {

User user= newUser();

user.setName("Java的架构师技术栈");

user.setAge(24);//序列化对象到文件中

ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("G://Test/template"));

oos.writeObject(user);

oos.close();

System.out.println("序列化对象成功");

}//反序列化方法

private static void DeSerializeUser() throwsFileNotFoundException, IOException{

File file= new File("G://Test/template");

ObjectInputStream ois= new ObjectInputStream(newFileInputStream(file));

User newUser=(User)ois.readObject();

System.out.println("反序列化对象成功"+newUser.toString());

}

}

当我们运行序列化方法时候,就可以看到,我们把数据存在了G://Test/template。

同时当我们运行反序列化方法的时候,就可以看到,反序列化成功,结果就不贴出来了,比较简单。

2、使用Externalizable接口实现序列化

首先,定义一个User1类

public class User1 implementsExternalizable{private intage;privateString name;//getter、setter//toString方法

publicUser1() {}

@Overridepublic void writeExternal(ObjectOutput out) throwsIOException {

}

@Overridepublic void readExternal(ObjectInput in) throwsIOException, ClassNotFoundException {

}

}

然后就是Test1类,和上面的Test一样,就不再贴出来了。在这里主要看Externalizable和Serializable接口的区别。下面对其进行归纳一下。

(1)Externalizable继承自Serializable接口

(2)需要我们重写writeExternal()与readExternal()方法

(3)实现Externalizable接口的类必须要提供一个public的无参的构造器。

因此,我们可以对writeExternal()与readExternal()方法重新更改一下;

@Overridepublic void writeExternal(ObjectOutput out) throwsIOException {

out.writeObject(name);

out.writeInt(age);

}

@Overridepublic void readExternal(ObjectInput in) throwsIOException, ClassNotFoundException {

name=(String)in.readObject();

age=in.readInt();

}

到这,java序列化机制的基本使用就讲完了,从上面可以看出,使用起来还是非常简单的。不过,仅仅会基本的使用还不行,想要面试的时候更加的装13,还需要进一步深化。因此下面一部分主要就是对序列化机制的深入分析。还需要说一点,对于Serializable接口实现的序列化方式一定要牢记,因为下面要多次使用

三、深入分析java序列化机制

1、serialVersionUID的作用

​ 一句话:其目的是序列化对象版本控制,有关各版本反序列化时是否兼容。如果在新版本中这个值修改了,新版本就不兼容旧版本,反序列化时会抛出InvalidClassException异常。如果修改较小,比如仅仅是增加了一个属性,我们希望向下兼容,老版本的数据都能保留,那就不用修改;如果我们删除了一个属性,或者更改了类的继承关系,必然不兼容旧数据,这时就应该手动更新版本号,即SerialVersionUid。

serialVersionUID有两种显示的生成方式:

一是默认的1L,比如:private static final long serialVersionUID = 1L;

二是根据类名、接口名、成员方法及属性等来生成一个64位的哈希字段,比如:

private static final long serialVersionUID = xxxxL;

现在我们去验证一下序列化版本不一致的情况:

首先我们在User里面设置一下serialVersionUID=123456L。

public class User implementsSerializable {//序列化ID

private static final long serialVersionUID = 123456L;

。。。。。

}

然后再Test里面开始序列化。

接着我们更改serialVersionUID = 123456789L。然后再反序列化,就可以看到如下的错误了。同时也验证了序列化和反序列化需要版本一致的问题。

84c4a4b5e4484ff9790402781a5450e4.png

2、静态变量的序列化

首先需要说一下,静态变量不会被序列化。因为静态变量在全局区,本来流里面就没有写入静态变量,我打印静态变量当然会去全局区查找,当我write read 同时使用时,内存中的静态变量变了,所以打印出来的也变了。眼见为实,代码验证一下;

下面这个例子,主要是在对静态变量序列化之后,然后更改静态变量age的值,再重新反序列化输出一下。结果会出现两种情况:

(1)反序列输出的静态变量值没有变化:说明静态变量被序列化了。

(2)反序列输出的静态变量值变化了:说明静态变量没有被序列化了。

第一步:把User里面的年龄属性改成static静态类型

第二步:现在改一下Test类

//序列化

private static void SerializeUser() throwsFileNotFoundException, IOException, ClassNotFoundException {

User user= newUser();

user.setName("Java的架构师技术栈");//初始化之前静态变量age年龄是24.

user.setAge(24);

ObjectOutputStream oos=

new ObjectOutputStream(new FileOutputStream("G://Test/template"));

oos.writeObject(user);

oos.close();//现在把年龄改成18

user.setAge(18);

ObjectInputStream oin=

new ObjectInputStream(new FileInputStream( "G://Test/template"));

User modifyUser=(User) oin.readObject();

oin.close();//再读取,通过t.staticVar打印新的值

System.out.println("静态变量age:"+modifyUser.getAge());

}

然后看一下输出结果

c880f2f8a469b1f02c3d6b04ecac05e4.png

3、Transient 关键字作用

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

这个很好理解也很简单。下面代码来看一下他的作用

第一步:把User里面的年龄属性改成Transient 修饰。private transient int age;

第二步:Test不变,进行序列化和反序列化。

第三步:看结果。反序列化之后的输出age应该为0;

676f39c29d7670795b05568e37c873f5.png

4、使用序列化实现深度克隆

对象的克隆也叫作对象的拷贝,拷贝有浅拷贝和深拷贝之分。

浅拷贝:使用一个已知实例对新创建实例的成员变量逐个赋值,这个方式被称为浅拷贝。

深拷贝:当一个类的拷贝构造方法,不仅要复制对象的所有非引用成员变量值,还要为引用类型的成员变量创建新的实例,并且初始化为形式参数实例值。这个方式称为深拷贝。

浅拷贝存在对象属性拷贝不彻底问题。因此在这里我们的侧重点不是克隆问题,我们的关注点更在于深拷贝。现在抛弃之前的User。我们重新定义一个类Person。

第一步:定义一个CloneUtils

public classCloneUtils {public static T clone(T obj){

T cloneObj= null;try{//写入字节流

ByteArrayOutputStream out = newByteArrayOutputStream();

ObjectOutputStream obs= newObjectOutputStream(out);

obs.writeObject(obj);

obs.close();//分配内存,写入原始对象,生成新对象

ByteArrayInputStream ios = newByteArrayInputStream(out.toByteArray());

ObjectInputStream ois= newObjectInputStream(ios);//返回生成的新对象

cloneObj =(T) ois.readObject();

ois.close();

}catch(Exception e) {

e.printStackTrace();

}returncloneObj;

}

}

第二步:定义一个person类

public class Person implementsSerializable {private static final long serialVersionUID = 123456L;privateString name;//带参构造器//getter、setter//toString方法

}

第三步:在Test2类中实现深度克隆

public classTest2 {public static voidmain(String[] args) {

Person person1= new Person("张三");

Person person2=CloneUtils.clone(person1);

person2.setName("李四");

Person person3=CloneUtils.clone(person1);

person3.setName("王五");

System.out.println("person1 "+person1.getName());

System.out.println("person1 "+person2.getName());

System.out.println("person1 "+person3.getName());

}

}//相应的输出是:张三、李四、王五

四、序列化的其他问题

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

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

子类序列化时:

如果父类没有实现Serializable接口,没有提供默认构造函数,那么子类的序列化会出错;

如果父类没有实现Serializable接口,提供了默认的构造函数,那么子类可以序列化,父类的成员变量不会被序列化。如果父类实现了Serializable接口,则父类和子类都可以序列化。

五、使用效率更高的序列化框架—Protostuff

其实java的原生序列化方式(通过实现Serialiable接口),效率并不是最高的。

github上有一个分析序列化效率的项目:https://github.com/eishay/jvm-serializers/wiki

9be338199b7ae8216d24e96fe7e30a2c.png

其中看的出来性能最优的为google开发的colfer ,但是由于colfer的使用难度太大,而更多的都是使用protostuff序列化框架。适用改框架要引入两个库(core与runtime)。

如果用Maven,则添加依赖:

io.protostuff

protostuff-core

1.5.9

io.protostuff

protostuff-core

1.5.9

修改Main代码

importcom.dyuproject.protostuff.LinkedBuffer;importcom.dyuproject.protostuff.ProtobufIOUtil;importcom.dyuproject.protostuff.ProtostuffIOUtil;importcom.dyuproject.protostuff.Schema;importcom.dyuproject.protostuff.runtime.RuntimeSchema;public classMain {public static voidmain(String[] args) {

User user= newUser();

user.setName("旭旭宝宝");

user.setAge(33);

Schema schema = RuntimeSchema.getSchema(User.class);//保存对象,序列化,转化二进制数据

LinkedBuffer buffer = LinkedBuffer.allocate(512);final byte[] protostuff;try{

protostuff=ProtobufIOUtil.toByteArray(user, schema, buffer);

}finally{

buffer.clear();

}//读取对象,反序列化

User userObject =schema.newMessage();

ProtostuffIOUtil.mergeFrom(protostuff, userObject, schema);

System.out.println(userObject);

}

}

User类,并未实现Serializable接口

public classUser {privateString name;private intage;publicString getName() {returnname;

}public voidsetName(String name) {this.name =name;

}public intgetAge() {returnage;

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

}

@OverridepublicString toString() {return "User [name=" + name + ", age=" + age + "]";

}

}

测试结果:User  [name = 旭旭宝宝,age = 33]

若要要整合Redis使用,也可以写成一个工具类:

importjava.util.Map;importjava.util.concurrent.ConcurrentHashMap;importcom.dyuproject.protostuff.LinkedBuffer;importcom.dyuproject.protostuff.ProtobufIOUtil;importcom.dyuproject.protostuff.Schema;importcom.dyuproject.protostuff.runtime.RuntimeSchema;public classSerializeUtil {private static Map, Schema>> cachedSchema = new ConcurrentHashMap<>();

@SuppressWarnings("unchecked")public static byte[] serializer(T obj) {

Class clazz = (Class) obj.getClass();

Schema schema =getSchema(clazz);return ProtobufIOUtil.toByteArray(obj, schema, LinkedBuffer.allocate(256));

}public static T deSerializer(byte[] bytes, Classclazz) {

T message;try{

message=clazz.newInstance();

}catch (InstantiationException |IllegalAccessException e) {throw newRuntimeException(e);

}

Schema schema =getSchema(clazz);

ProtobufIOUtil.mergeFrom(bytes, message, schema);returnmessage;

}

@SuppressWarnings("unchecked")public static Schema getSchema(Classclazz) {

Schema schema = (Schema) cachedSchema.get(clazz);if (schema == null) {

schema=RuntimeSchema.createFrom(clazz);if (schema != null) {

cachedSchema.put(clazz, schema);

}

}returnschema;

}

}

这样即使我们的User类就不用再实现Serialiable接口了,同样可以进行序列化,效率也更高。

对于Redis的工具类封装,参照:https://blog.csdn.net/qq_35890572/article/details/81664911

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值