1、java序列化
序列化介绍:
序列化:将java对象写入字节流;反序列化:从字节流中恢复对象
意义:序列化机制允许将实现序列化的java对象装换位字节序列,这些字节序列可以保存在磁盘,数据库上,或者通过网络传输以达到以后恢复成原来的java对象。序列化机制可以使对象脱离程序的运行而独立存在(脱离JVM的生命周期)
使用场景:
- 永久性保存对象。保存对象的字节序列到本地文件的或者数据库中。
- 通过序列化,以字节流的形式使对象在网络中记性传递与接收。建议创建的每一个javaBean类都实现Seriallizeable接口
- 通过序列化在进程之间传递对象
java实现序列化的方式
1、Seriallizable
seriallizable是一个标记接口,不用于实现任何方法。一旦实现了此接口,该类的对象就是可序列化的对象
a、序列化步骤:
- 创建一个ObjectOutputStream输出流
- 调用ObjectOutputStream对象的writeObject输出可序列化对象
@SuppressWarnings("unchecked")
public class Main {
public static void main(String[] args) throws IOException {
Test t=new Test();
t.setAge(10);
t.setName("cyc");
ObjectOutputStream oo=new ObjectOutputStream(new FileOutputStream("D:/test.txt"));
oo.writeObject(t);
oo.close();
}
}
class Test implements Serializable{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Test [name=" + name + ", age=" + age + "]";
}
b、反序列化步骤[反序列化并不会调用构造方法。反序列的对象是由JVM自己生成的对象,不通过构造方法生成]:
- 创建一个ObjectInputStream输入流
- 调用ObjectInputStrean对象的readObject得到序列化的对象
@SuppressWarnings("unchecked")
public class Main {
public static void main(String[] args) throws IOException, ClassNotFoundException {
ObjectInputStream oi=new ObjectInputStream(new FileInputStream("D:/test.txt"));
Test t=(Test) oi.readObject();
System.out.println(t.toString());
oi.close();
}
}
class Test implements Serializable{
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Test [name=" + name + ", age=" + age + "]";
}
}
c、成员是引用的序列化:如果一个可序列化的类的成员既不是基本类型也不是String类型,那这个引用类型必须是可以序列化的(实现序列化接口),否则会导致此类不能序列化。
d、同一对象实例化多次的机制:Java序列化同一对象,并不会将此对象序列化多次得到多个对象[只会序列化一次]。在java序列化时,所有保存到磁盘的对象都有一个系列化编号;当程序试图序列化一个对象的时候,会先检查此对象是否已经被序列化过,只有此对象没有被序列化(在一个JVM内),才会将此对象序列化为字节序列输出。
//序列化对象
public class WriteTeacher {
public static void main(String[] args) throws Exception {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("teacher.txt"))) {
Person person = new Person("路飞", 20);
Teacher t1 = new Teacher("雷利", person);
Teacher t2 = new Teacher("红发香克斯", person);
//依次将4个对象写入输入流
oos.writeObject(t1);
oos.writeObject(t2);
oos.writeObject(person);
oos.writeObject(t2);
}
}
}
//反序列化对象
public class ReadTeacher {
public static void main(String[] args) {
try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream("teacher.txt"))) {
Teacher t1 = (Teacher) ois.readObject();
Teacher t2 = (Teacher) ois.readObject();
Person p = (Person) ois.readObject();
Teacher t3 = (Teacher) ois.readObject();
System.out.println(t1 == t2);
System.out.println(t1.getPerson() == p);
System.out.println(t2.getPerson() == p);
System.out.println(t2 == t3);
System.out.println(t1.getPerson() == t2.getPerson());
} catch (Exception e) {
e.printStackTrace();
}
}
}
//输出结果
//false
//true
//true
//true
//true
e、序列化算法的潜在问题:由于java序利化算法不会重复序列化同一个对象,只会记录已序列化对象的编号。如果序列化一个可变对象(对象内的内容可更改)后,更改了对象内容,再次序列化,并不会再次将此对象转换为字节序列,而只是保存序列化编号。
f、 自定义序列化:
- 使用transient关键字,transient修饰的变量不会进行序列化。使用transient修饰的属性,java序列化时,会忽略掉此字段,所以反序列化出的对象,被transient修饰的属性是默认值。对于引用类型,值是null;基本类型,值是0;boolean类型,值是false。
- 重写writeObject与readObject方法:通过重写writeObject与readObject方法,可以自己选择哪些属性需要序列化, 哪些属性不需要。如果writeObject使用某种规则序列化,则相应的readObject需要相反的规则反序列化,以便能正确反序列化出对象。这里展示对名字进行反转加密。
private void writeObject(java.io.ObjectOutputStream out) throws IOException; private void readObject(java.io.ObjectIutputStream in) throws IOException,ClassNotFoundException; private void readObjectNoData() throws ObjectStreamException; public class Person implements Serializable { private String name; private int age; //省略构造方法,get及set方法 private void writeObject(ObjectOutputStream out) throws IOException { //将名字反转写入二进制流 out.writeObject(new StringBuffer(this.name).reverse()); out.writeInt(age); } private void readObject(ObjectInputStream ins) throws IOException,ClassNotFoundException{ //将读出的字符串反转恢复回来 this.name = ((StringBuffer)ins.readObject()).reverse().toString(); this.age = ins.readInt(); } }
- 更彻底的自定义序列化:writeReplace:在序列化时,会先调用此方法,再调用writeObject方法。此方法可将任意对象代替目标序列化对象;readResolve:反序列化时替换反序列化出的对象,反序列化出来的对象被立即丢弃。此方法在readeObject后调用。,通常用于反序列单例类i,来保证单例类的唯一性。
2、Externalizable:强制自定义序列化
通过实现Externalizable接口,必须实现writeExternal、readExternal方法。
public class ExPerson implements Externalizable {
private String name;
private int age;
//注意,必须加上pulic 无参构造器
public ExPerson() {
}
public ExPerson(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
//将name反转后写入二进制流
StringBuffer reverse = new StringBuffer(name).reverse();
System.out.println(reverse.toString());
out.writeObject(reverse);
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
//将读取的字符串反转后赋值给name实例变量
this.name = ((StringBuffer) in.readObject()).reverse().toString();
System.out.println(name);
this.age = in.readInt();
}
public static void main(String[] args) throws IOException, ClassNotFoundException {
try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ExPerson.txt"));
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("ExPerson.txt"))) {
oos.writeObject(new ExPerson("brady", 23));
ExPerson ep = (ExPerson) ois.readObject();
System.out.println(ep);
}
}
}
//输出结果
//ydarb
//brady
//ExPerson{name='brady', age=23}
Externalizable不同于Serializable接口,实现此接口必须实现接口中的两个方法,切必须提供无参构造器,因为在反序列化的时候需要反射创建对象。
3、对比
实现Serializable接口 | 实现Externalizable接口 |
---|---|
系统自动存储必要的信息 | 程序员决定存储哪些信息 |
Java内建支持,易于实现,只需要实现该接口即可,无需任何代码支持 | 必须实现接口内的两个方法 |
性能略差 | 性能略好 |
序列化版本号:
反序列化必须拥有class文件,但随着项目的升级,class文件也会升级,序列化怎么保证升级前后的兼容性呢?所以,java序列化提供了一个private static final long serialVersionUID 的序列化版本号,只有版本号相同,即使更改了序列化属性,对象也可以正确被反序列化回来。如果反序列化使用的class的版本号与序列化时使用的不一致,反序列化会报InvalidClassException异常。
修改serialVersionUID?
- 如果只是修改了方法,反序列化不容影响,则无需修改版本号;
- 如果只是修改了静态变量,瞬态变量(transient修饰的变量),反序列化不受影响,无需修改版本号;
- 如果修改了非瞬态变量,则可能导致反序列化失败。如果新类中实例变量的类型与序列化时类的类型不一致,则会反序列化失败,这时候需要更改serialVersionUID。如果只是新增了实例变量,则反序列化回来新增的是默认值;如果减少了实例变量,反序列化时会忽略掉减少的实例变量。
序列化总结:
- 所有需要网络传输的对象都需要实现序列化接口,通过建议所有的javaBean都实现Serializable接口。
- 对象的类名、实例变量(包括基本类型,数组,对其他对象的引用)都会被序列化;方法、类变量、transient实例变量都不会被序列化。
- 如果想让某个变量不被序列化,使用transient修饰。
- 序列化对象的引用类型成员变量,也必须是可序列化的,否则,会报错。
- 反序列化时必须有序列化对象的class文件。
- 当通过文件、网络来读取序列化后的对象时,必须按照实际写入的顺序读取。
- 单例类序列化,需要重写readResolve()方法;否则会破坏单例原则。
- 同一对象序列化多次,只有第一次序列化为二进制流,以后都只是保存序列化编号,不会重复序列化。
- 建议所有可序列化的类加上serialVersionUID 版本号,方便项目升级。
java对象克隆
首先,java实现克隆需要遵循以下两个规则:必须实现Cloneable接口;实现Cloneable接口的类应该重写clone()方法,重写方法的修饰符必须为public。实际上克隆调用的Object类的clone()方法,clone()是一个本地方法,默认的修饰符为protected。调用Object的clone()的时候,会返回一个复制后的对象,是对所有属性的逐一克隆(引用类型克隆的是引用,值类型克隆的是属性内容)。
protected native Object clone() throws CloneNotSupportedException;
浅克隆
在浅克隆中,如果原型对象的成员变量是值类型,将复制一份给克隆对象(String类型比较特殊,由于其为final修饰,故也重新创建一个对象);如果原型对象的成员变量是引用类型,则将引用对象的地址复制一份给克隆对象,也就是说原型对象和克隆对象的成员变量指向相同的内存地址。
class Address {
private String add;
public String getAdd() {
return add;
}
public void setAdd(String add) {
this.add = add;
}
}
class Student implements Cloneable{
private int number;
private Address addr;
public Address getAddr() {
return addr;
}
public void setAddr(Address addr) {
this.addr = addr;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public Object clone() {
Student stu = null;
try{
stu = (Student)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
}
在java中,通过覆盖Object类的clone()方法来实现浅克隆。
深克隆
在深克隆中,无论原型对象的成员变量是值类型还是引用类型,都将复制一份给克隆对象,深克隆将原型对象的所有引用对象也复制一份给克隆对象。在深克隆中,除了对象本身被复制外,对象所包含的所有成员变量也将复制。
public class Outer implements Serializable{
private static final long serialVersionUID = 369285298572941L; //最好是显式声明ID
public Inner inner;
//Discription:[深度复制方法,需要对象及对象所有的对象属性都实现序列化]
public Outer myclone() {
Outer outer = null;
try { // 将该对象序列化成流,因为写在流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。所以利用这个特性可以实现对象的深拷贝
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
// 将流序列化成对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
outer = (Outer) ois.readObject();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return outer;
}
}
class Inner implements Serializable{
private static final long serialVersionUID = 872390113109L; //最好是显式声明ID
public String name = "";
public Inner(String name) {
this.name = name;
}
@Override
public String toString() {
return "Inner的name值为:" + name;
}
}
在java中,若要实现深克隆,则可以通过覆盖Object类的clone()方法[要求引用类型属性对于的类实现Cloneable接口并重写clone()],也可以通过序列化(Serialization)等方式
于2020/10/09完成
参考链接:
https://www.cnblogs.com/9dragon/p/10901448.html
https://www.cnblogs.com/Qian123/p/5710533.html
https://baijiahao.baidu.com/s?id=1636492159314232573
https://blog.csdn.net/zhaoheng314/article/details/81985880