1.概念
序列化是指将一个对象转换为一个字节序列(包含
对象的数据
、对象的类型
和对象中存储的属性
等信息),它可以将多个InputStream对象串联起来,使得它们可以被当作一个单一的输入流来处理,以便在网络上传输或保存到文件中,或者在程序之间传递。在 Java 中,序列化通过实现 java.io.Serializable 接口来实现,只有实现了 Serializable 接口open in new window的对象才能被序列化。
当你序列化对象时,你把它包装成一个特殊文件,可以保存、传输或存储。反序列化则是打开这个文件,读取序列化的数据,然后将其还原为对象,以便在程序中使用。
序列流的基本特性包括:
- 懒加载:序列流的操作是懒加载,意味着它们不会立即执行,而是在需要结果时才执行。
- 不可变:一旦创建了序列流,就不能修改它。如果需要修改,必须创建一个新的流。
- 并行处理:序列流支持并行处理,可以利用多核处理器提高性能。
2.ObjectOutputStream
java.io.ObjectOutputStream
继承自 OutputStream 类,因此可以将序列化后的字节序列写入到文件、网络等输出流中。
来看 ObjectOutputStream 的构造方法:ObjectOutputStream(OutputStream out)
该构造方法接收一个 OutputStream 对象作为参数,用于将序列化后的字节序列输出到指定的输出流中。例如:
FileOutputStream fos = new FileOutputStream("file.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
使用示例如下:
public class Employee implements Serializable {
public String name;
public String address;
public transient int age; // transient瞬态修饰成员,不会被序列化
}
接下来,来聊聊 writeObject (Object obj)
方法,该方法是 ObjectOutputStream 类中用于将对象序列化成字节序列并输出到输出流中的方法,可以处理对象之间的引用关系、继承关系、静态字段和 transient 字段。
public class ObjectOutputStreamDemo {
public static void main(String[] args) {
Person person = new Person("沉默王二", 20);
try {
FileOutputStream fos = new FileOutputStream("logs/person.dat");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(person);
oos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class Person implements Serializable {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
一个对象要想序列化,必须满足两个条件:
- 该类必须实现java.io.Serializable 接口open in new window,否则会抛出
NotSerializableException
。- 该类的所有字段都必须是可序列化的。如果一个字段不需要序列化,则需要使用transient 关键字open in new window进行修饰。
3.ObjectInputStream
ObjectInputStream 可以读取 ObjectOutputStream 写入的字节流,并将其反序列化为相应的对象(包含
对象的数据
、对象的类型
和对象中存储的属性
等信息)。
主要功能就是将ObjectOutputStream序列化的字节流转换回java对象。
使用步骤:
1、创建对象输入流:
FileInputStream fileIn =new FileInputStream("example.ser");
ObjectInputStream in = new ObjectInputStream(fileIn);
2、读取对象:使用readObject的方法从流中读取对象。注意,读取的对象需要强制类型转换到正确的类型。
ObjectInputStream in =new ObjectInputStream(fileIn);
Object obj =in.readObject();
Employee emp =(Employee) obj;
整体演示:
String filename = "logs/person.dat"; // 待反序列化的文件名
try (FileInputStream fileIn = new FileInputStream(filename);
ObjectInputStream in = new ObjectInputStream(fileIn)) {
// 从指定的文件输入流中读取对象并反序列化
Object obj = in.readObject();
// 将反序列化后的对象强制转换为指定类型
Person p = (Person) obj;
// 打印反序列化后的对象信息
System.out.println("Deserialized Object: " + p);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
我们首先指定了待反序列化的文件名(前面通过 ObjectOutputStream 序列化后的文件),然后创建了一个 FileInputStream 对象和一个 ObjectInputStream 对象。接着我们调用 ObjectInputStream 的 readObject 方法来读取指定文件中的对象,并将其强制转换为 Person 类型。最后我们打印了反序列化后的对象信息。
4.transient
transient关键字是一个变量修饰符,用于声明一个字段不会被序列化。当一个对象被序列化时,默认状态下,对象的所有非静态和非瞬态字段都会被保存在字节流中。然而,如果你不希望某个字段被序列化,可以将其声明为transient。
关键用途:
- 保护敏感数据:如果你有一个包含敏感信息到字段(比如密码,信用卡等等)。
- 避免序列化不必要的字段:有些字段可能在序列化时没有意义,或者不应该被保留在对象状态中。
public class TransientTest {
public static void main(String[] args) {
User user = new User();
user.setUsername("我是大帅比");
user.setPasswd("123456");
System.out.println("read before Serializable: ");
System.out.println("username: " + user.getUsername());
System.err.println("password: " + user.getPasswd());
try {
ObjectOutputStream os = new ObjectOutputStream(
new FileOutputStream("user.txt"));
os.writeObject(user); // 将User对象写进文件
os.flush();
os.close();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
try {
ObjectInputStream is = new ObjectInputStream(new FileInputStream(
"user.txt"));
user = (User) is.readObject(); // 从流中读取User的数据
is.close();
System.out.println("\nread after Serializable: ");
System.out.println("username: " + user.getUsername());
System.err.println("password: " + user.getPasswd());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
class User implements Serializable {
private static final long serialVersionUID = 8294180014912103005L;
private String username;
private transient String passwd;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPasswd() {
return passwd;
}
public void setPasswd(String passwd) {
this.passwd = passwd;
}
}
运行结果:
read before Serializable:
username: 沉默王二
password: 123456
read after Serializable:
username: 沉默王二
password: null
transitent的使用小结:
一旦字段被 transient 修饰,成员变量将不再是对象持久化的一部分,该变量的值在序列化后无法访问。
transient 关键字只能修饰字段,而不能修饰方法和类。
被 transient 关键字修饰的字段不能被序列化,一个静态变量(static关键字open in new window修饰)不管是否被 transient 修饰,均不能被序列化。