java 系列化详解,java 序列化详解(二)

接着前面的文章说,当任何一个类继承Serializable 这个接口时,Eclipse经常会有黄色惊叹号提示。

提示内容如下:

The serializable class Person does not declare a static final serialVersionUID field of type long

点开以后有2个选择 一个是

Adds a default serial version ID to the selected type.

Use this option to add a user-defined ID in combination with

custom serialization code if the type did undergo structural

changes since its first release.

还有一个是

Adds a generated serial version ID to the selected type.

Use this option to add a compiler-generated ID if the type did

not undergo structural changes since its first release.

那么这个serialVersionUID  是干嘛的?

简单提一下 ,可以把这个

/**

*

*/

private static final long serialVersionUID = 1L;

Eclipse帮我们生成的这个语句解释一下。

实际上,java序列化呢,主要的应用场景就是 rmi。远程接口调用。

我举个例子,前面我们写的那个例子。把她序列化和反序列化 这个过程分开

放在2个工程里面,(要注意虽然是2个工程但是包要一样),

运行以后程序是正常的,但是你要注意 如果此时你2个工程的person类的

serialVersionUID  这个值如果不一样。你就会发现在反序列化的时候失败了。

只有当这个值相等的时候 序列化和反序列化才会成功。

其实这个值主要就是用来强制客户端更新接口用的。(RMI里经常使用)

比如客户端有个类A,服务器端有个类也是A。这个时候2个端用rmi进行通信,

假设服务器端这个类A 修改了某些内容比如增加或删除了一个字段,这个时候你怎么通知

客户端呢,你就把serialVersionUID  这个值修改成和客户端不一样的,这样在序列化或者反序列化的

时候就会出错,如此一来 客户端就知道,噢,序列化出错要更新接口了。

此外,前面的那个文章也说明了,序列化的过程默认调用是 ObjectOutputStream 的 defaultWriteObject 方法以及 ObjectInputStream 的 defaultReadObject 方法

当然我们也可以手动写方法来调用,writeObject 和 readObject 方法可以允许用户控制序列化的过程,手动控制除了能更好的控制序列化所消耗的事件以外,

还有一个优点是可以加密,比如我们要序列化一个 用户的用户名和密码,你默认序列化的话,是有可能被抓取的,但是如果你手动序列化在里面对密码进行

加密,然后在反序列化的时候解密,就非常安全了。

再看一段代码。

1 packagecom.burning.test;2

3 importjava.io.Serializable;4

5 public class Person implementsSerializable {6

7 public static final int STATIC_VALUE =100;8

9

10 @Override11 publicString toString() {12 return "Person [name=" + name + ", age=" + age + "]";13 }14

15 publicString getName() {16 returnname;17 }18

19 public voidsetName(String name) {20 this.name =name;21 }22

23 public intgetAge() {24 returnage;25 }26

27 public void setAge(intage) {28 this.age =age;29 }30

31 privateString name;32

33 private intage;34

35 }

然后看看main

1 packagecom.burning.test;2

3 importjava.io.File;4 importjava.io.FileInputStream;5 importjava.io.FileNotFoundException;6 importjava.io.FileOutputStream;7 importjava.io.IOException;8 importjava.io.ObjectInputStream;9 importjava.io.ObjectOutputStream;10

11 public classTestMain {12

13 public static voidmain(String[] args) {14 //TODO Auto-generated method stub

15 File file = new File("person2.out");16 ObjectInputStream oin = null;17 ObjectOutputStream oout = null;18 try{19 oout = new ObjectOutputStream(newFileOutputStream(file));20 Person person = newPerson();21 oout.writeObject(person);22 oout.flush();23

24 System.out.println("第1次写完以后" +file.length());25 oout.writeObject(person);26 System.out.println("第2次写完以后" +file.length());27 oout.close();28 oin = new ObjectInputStream(newFileInputStream(file));29 Object obj =oin.readObject();30 Object obj2 =oin.readObject();31 System.out.println(obj ==obj2);32

33 } catch(FileNotFoundException e) {34 //TODO Auto-generated catch block

35 e.printStackTrace();36 } catch(IOException e) {37 //TODO Auto-generated catch block

38 e.printStackTrace();39 } catch(ClassNotFoundException e) {40 //TODO Auto-generated catch block

41 e.printStackTrace();42 } finally{43 try{44 oout.close();45 } catch(IOException e) {46 //TODO Auto-generated catch block

47 e.printStackTrace();48 }49 try{50 oin.close();51 } catch(IOException e) {52 //TODO Auto-generated catch block

53 e.printStackTrace();54 }55

56 }57

58 }59 }

运行一下程序。结果为

第1次写完以后83

第2次写完以后88

true。

这个地方运行结果一目了然,我们发现,Java 序列化机制为了节省磁盘空间,具有特定的存储规则,当写入文件的为同一对象时,并不会再将对象的内容进行存储,而只是再次存储一份引用,

上面增加的 5 字节的存储空间就是新增引用和一些控制信息的空间。反序列化时,恢复引用关系,使得清单 3 中的 t1 和 t2 指向唯一的对象,二者相等,输出 true。该存储规则极大的节省了存储空间。

然后我们再修改一下主类

packagecom.burning.test;importjava.io.File;importjava.io.FileInputStream;importjava.io.FileNotFoundException;importjava.io.FileOutputStream;importjava.io.IOException;importjava.io.ObjectInputStream;importjava.io.ObjectOutputStream;public classTestMain {public static voidmain(String[] args) {//TODO Auto-generated method stub

File file = new File("person2.out");

ObjectInputStream oin= null;

ObjectOutputStream oout= null;try{

oout= new ObjectOutputStream(newFileOutputStream(file));

Person person= newPerson();

person.setAge(10);

oout.writeObject(person);

oout.flush();

System.out.println("第1次写完以后" +file.length());

person.setAge(20);

oout.writeObject(person);

System.out.println("第2次写完以后" +file.length());

oout.close();

oin= new ObjectInputStream(newFileInputStream(file));

Object obj=oin.readObject();

Object obj2=oin.readObject();

System.out.println(obj.toString());

System.out.println(obj2.toString());

}catch(FileNotFoundException e) {//TODO Auto-generated catch block

e.printStackTrace();

}catch(IOException e) {//TODO Auto-generated catch block

e.printStackTrace();

}catch(ClassNotFoundException e) {//TODO Auto-generated catch block

e.printStackTrace();

}finally{try{

oout.close();

}catch(IOException e) {//TODO Auto-generated catch block

e.printStackTrace();

}try{

oin.close();

}catch(IOException e) {//TODO Auto-generated catch block

e.printStackTrace();

}

}

}

}

看一下运行结果

第1次写完以后83

第2次写完以后88

Person [name=null, age=10]

Person [name=null, age=10]

我们就会发现结果是这样的,因为第一次对象保存完毕以后  你虽然修改了这个对象的值,但是你在第二次序列化对象的时候 因为这2个对象引用相等 所以不会更改值,只会保存一部分引用

所以会得出一个比较奇怪的结果~这个地方要好好理解下

再次修改main 看看运行结果

packagecom.burning.test;importjava.io.File;importjava.io.FileInputStream;importjava.io.FileNotFoundException;importjava.io.FileOutputStream;importjava.io.IOException;importjava.io.ObjectInputStream;importjava.io.ObjectOutputStream;public classTestMain {public static voidmain(String[] args) {//TODO Auto-generated method stub

File file = new File("person4.out");

ObjectInputStream oin= null;

ObjectOutputStream oout= null;try{

oout= new ObjectOutputStream(newFileOutputStream(file));

Person person= newPerson();

person.setAge(10);

oout.writeObject(person);

oout.flush();

System.out.println("第1次写完以后" +file.length());

Person person2= newPerson();

person2.setAge(20);

oout.writeObject(person2);

System.out.println("第2次写完以后" +file.length());

oout.close();

oin= new ObjectInputStream(newFileInputStream(file));

Object obj=oin.readObject();

Object obj2=oin.readObject();

System.out.println(obj.toString());

System.out.println(obj2.toString());

}catch(FileNotFoundException e) {//TODO Auto-generated catch block

e.printStackTrace();

}catch(IOException e) {//TODO Auto-generated catch block

e.printStackTrace();

}catch(ClassNotFoundException e) {//TODO Auto-generated catch block

e.printStackTrace();

}finally{try{

oout.close();

}catch(IOException e) {//TODO Auto-generated catch block

e.printStackTrace();

}try{

oin.close();

}catch(IOException e) {//TODO Auto-generated catch block

e.printStackTrace();

}

}

}

}

这个结果 就是

第1次写完以后83

第2次写完以后94

Person [name=null, age=10]

Person [name=null, age=20]

看完这个结果应该比较好理解了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Expect 是一个可以自动化交互式应用程序的工具,通过 Expect 脚本可以模拟用户与应用程序之间的交互,从而实现自动化操作。Expect 最初是为 Unix/Linux 系统设计的,但是现在已经有了 Windows 版本。 Expect 的基本用法是:在脚本中定义一系列期望的输入/输出序列,然后执行应用程序,并将输入/输出重定向到脚本中,最后根据期望的输入/输出序列进行匹配,从而实现自动化操作。 下面是一个简单的 Expect 脚本示例: ``` #!/usr/bin/expect set username "my_username" set password "my_password" set hostname "my_hostname" spawn ssh $username@$hostname expect { "password:" { send "$password\r" exp_continue } "$username@*" { interact } } ``` 这个脚本用来自动化登录远程服务器,其中: - `spawn` 命令用来执行 `ssh` 命令,并将输入/输出重定向到脚本中。 - `expect` 命令用来匹配期望的输入/输出序列,其中 `{}` 中的内容表示一个期望的输入/输出序列,每个序列中包含一个模式和一个动作。如果输入/输出的内容匹配了某个模式,就执行相应的动作。具体来说: - 如果匹配到了 "password:",就发送密码并继续等待下一个输入/输出。 - 如果匹配到了 "$username@*",就进入交互模式,允许用户与远程服务器交互。 除了上面的示例,Expect 还有很多其他的用法,比如自动化 telnet、ftp、scp 等协议的操作,以及自动化交互式应用程序的测试等。总的来说,Expect 是一个非常强大的自动化工具,可以帮助我们实现很多自动化操作。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值