对象可以被序列化也可以展开。对象有状态和行为两种属性。行为存在于类中,而状态则保存在个别对象中。java中数据的保存分为2种:
1. 序列化(Serialization):将序列化的对象写入到文件中。然后你就可以让你的程序去文件中读取序列化的对象并把它们展开到活生生的状态【只有自己写的java程序会用到这些数据】(序列化的文件是很难让一般人阅读的,但是它比纯文本文件更容易让程序恢复之前的状态,也比较安全,因为一般问不会知道要如何动手脚改数据);
2. 写一个纯文本文件。用其他应用程序可以解析的特殊字符写到文件中。例如写成tab字符来分割的文件以便让电子表格或数据库应用程序能够应用【如果数据需要被其他应用程序所引用】。
如果要让类能够被序列化,就需要实现Serializable接口,该接口又被称为maker或者tag类的标记用接口,因为此接口中并没有任何方法是需要实现的。它的唯一目的就是声明有实现它的类是可以被序列化的。也就是说,此类型的对象可以通过序列化的机制来存储。如果某类是可序列化的,则其子类也自动可以序列化(接口的本意就是如此)。
如果某个实例变量不能或者不应该被序列化,就把它标记位transient(瞬时)的。
反序列化(Deserialization):还原对象
将对象进行序列化整件事情的重点就在于你可以在事后,在不同的JVM执行期(甚至不是同一个JVM虚拟机),把对象恢复到存储时的状态。
静态变量不会被序列化,因为static代表“每个类一个”而不是“每个对象一个”。当对象被还原时,静态变量会维持类中原来的样子,而不是存储时的样子。
对象在进行反序列化的时候,新的对象会被配置在堆上,但是构造方法不会执行!很明显,这样会把对象的状态抹去又变成新的,而这不是我们想要的结果。我们需要的是对象回到存储时的状态。
如果对象在继承树上有个不可序列化的祖先类,则该不可序列化的类以及它之上的类(就算是可序列化的也一样)的构造方法就会执行。一旦构造方法连锁启动之后将无法停止。也就是说,从第一个不可序列化的父类开始,它之上的全部回到初始状态。
对象的实例变量会被还原成序列化时点的状态值。transient变量会被赋值null的对象引用或者primitive主数据类型的默认值0、false等值。
SerialVersionUID:
对象读取的顺序必须和写入的顺序相同。
import java.io.Serializable;
public class Student implements Serializable{
/*需要实现序列化接口*/
private int age;
private String name;
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Student(int age, String name) {
super();
this.age = age;
this.name = name;
}
@Override
public String toString() {
return "Student [age=" + age + ", name=" + name + "]";
}
}
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class ObjectSerializableDemo {
public static void main(String[] args) {
Student s1 = new Student(12, "Tom");
Student s2 = new Student(13, "Kitty");
Student s3 = new Student(15, "Happy");
// 假设此处有改变对象状态的代码
/* 序列化对象 */
ObjectOutputStream oos = null;
try {
oos = new ObjectOutputStream(
new FileOutputStream(new File("E:" + File.separator + "tmp"
+ File.separator + "Student.info")));
oos.writeObject(s1);
oos.writeObject(s2);
oos.writeObject(s3); oos.close(); //记得关闭流哦!
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
s1 = s2 = s3 = null; // 设定成null,因此无法存储堆上的对象
/* 反序列化对象 */
try {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(
new File("E:" + File.separator + "tmp" + File.separator
+ "Student.info")));
s1 = (Student) ois.readObject();
s2 = (Student) ois.readObject();
s3 = (Student) ois.readObject();
System.out.println(s1);
System.out.println(s2);
System.out.println(s3); ois.close();//记得关闭流哦!
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
运行结果:E盘会生成一个Student.info的文件(该文件不是文本文件,用记事本打开是乱码)控制台输出:
Student [age=12, name=Tom]
Student [age=13, name=Kitty]
Student [age=15, name=Happy]
如果将上述代码中Student类中的age属性加入transient关键字修饰,那么age将不被序列化,读取对象的时候默认是0.
Student [age=0, name=Tom]
Student [age=0, name=Kitty]
Student [age=0, name=Happy]
写入和读取文本文件:
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class FileWriterAndFileReaderDemo {
public static void main(String[] args) {
File file = new File("E:" + File.separator + "tmp" + File.separator
+ "hello.txt");
try {
FileWriter fileWriter = new FileWriter(file);
/* 将字符串写入文件 */
fileWriter.write("日照香炉生紫烟");
fileWriter.write("\r\n遥看瀑布挂前川");
fileWriter.close(); // 记得关闭流哦!
} catch (IOException e) {
e.printStackTrace();
}
BufferedReader reader;
try {
reader = new BufferedReader(new FileReader(file));
String str = null;
while ((str = reader.readLine()) != null) {
System.out.println(str);
}
reader.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
PS:获取系统的换行符,我们可以使用
System.getProperty("line.separator")。InputStreamReader和OutputStreamWriter的构造方法中可以指定字符集,常用来解决乱码问题。