概要
ObjectOutputStream 将 Java 对象的基本数据类型和图形写入 OutputStream。可以使用 ObjectInputStream 读取(重构)对象。通过在流中使用文件可以实现对象的持久存储。如果流是网络套接字流,则可以在另一台主机上或另一个进程中重构对象。
只能将支持 java.io.Serializable 接口的对象写入流中。每个 serializable 对象的类都被编码,编码内容包括类名和类签名、对象的字段值和数组值,以及从初始对象中引用的其他所有对象的闭包。
writeObject 方法用于将对象写入流中。所有对象(包括 String 和数组)都可以通过 writeObject 写入。可将多个对象或基元写入流中。必须使用与写入对象时相同的类型和顺序从相应 ObjectInputstream 中读回对象。
还可以使用 DataOutput 中的适当方法将基本数据类型写入流中。还可以使用 writeUTF 方法写入字符串。
对象的默认序列化机制写入的内容是:对象的类,类签名,以及非瞬态和非静态字段的值。其他对象的引用(瞬态和静态字段除外)也会导致写入那些对象。可使用引用共享机制对单个对象的多个引用进行编码,这样即可将对象的图形恢复为最初写入它们时的形状。
例如,要写入可通过 ObjectInputStream 中的示例读取的对象,请执行以下操作:
FileOutputStream fos = new FileOutputStream("t.tmp");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeInt(12345);
oos.writeObject("Today");
oos.writeObject(new Date());
oos.close();
在序列化和反序列化过程中需要特殊处理的类必须实现具有下列准确签名的特殊方法:
private void readObject(java.io.ObjectInputStream stream)
throws IOException, ClassNotFoundException;
private void writeObject(java.io.ObjectOutputStream stream)
throws IOException
private void readObjectNoData()
throws ObjectStreamException;
writeObject 方法负责写入特定类的对象状态,以便相应的 readObject 方法可以恢复它。该方法本身不必与属于对象的超类或子类的状态有关。状态是通过使用 writeObject 方法或使用 DataOutput 支持的用于基本数据类型的方法将各个字段写入 ObjectOutputStream 来保存的。
序列化操作不写出没有实现 java.io.Serializable 接口的任何对象的字段。不可序列化的 Object 的子类可以是可序列化的。在此情况下,不可序列化的类必须有一个无参数构造方法,以便允许初始化其字段。在此情况下,子类负责保存和恢复不可序列化的类的状态。经常出现的情况是,该类的字段是可访问的(public、package 或 protected),或者存在可用来恢复状态的 get 和 set 方法。
在 writeObject 和 readObject 方法的实现中抛出 NotSerializableException,可以阻止对象的序列化。ObjectOutputStream 将捕获异常并中止序列化进程。
实现 Externalizable 接口允许对象假定可以完全控制对象的序列化形式的内容和格式。调用 Externalizable 接口的方法(writeExternal 和 readExternal)来保存和恢复对象的状态。通过类实现时,它们可以使用 ObjectOutput 和 ObjectInput 的所有方法读写它们自己的状态。对象负责处理出现的任何版本控制。
Enum 常量的序列化不同于普通的 serializable 或 externalizable 对象。enum 常量的序列化形式只包含其名称;常量的字段值不被传送。为了序列化 enum 常量,ObjectOutputStream 需要写入由常量的名称方法返回的字符串。与其他 serializable 或 externalizable 对象一样,enum 常量可以作为序列化流中后续出现的 back 引用的目标。用于序列化 enum 常量的进程不可定制;在序列化期间,由 enum 类型定义的所有类特定的 writeObject 和 writeReplace 方法都将被忽略。类似地,任何 serialPersistentFields 或 serialVersionUID 字段声明也将被忽略,所有 enum 类型都有一个 0L 的固定的 serialVersionUID。
基本数据(不包括 serializable 字段和 externalizable 数据)以块数据记录的形式写入 ObjectOutputStream 中。块数据记录由头部和数据组成。块数据部分包括标记和跟在部分后面的字节数。连续的基本写入数据被合并在一个块数据记录中。块数据记录的分块因子为 1024 字节。每个块数据记录都将填满 1024 字节,或者在终止块数据模式时被写入。调用 ObjectOutputStream 方法 writeObject、defaultWriteObject 和 writeFields 最初只是终止所有现有块数据记录。
示例
class Address implements Serializable
{
String country;
String city;
public Address(String country, String city)
{
this.country = country;
this.city = city;
}
}
class User implements Serializable{
private static final long serialVersionUID = -1241976087141510667L;
String userName ;
String password ;
transient int age;
Address address;
public User(String name, String pass)
{
this.userName = name;
this.password = pass;
}
public User(String name, String pass, int age, Address address)
{
this.userName = name;
this.password = pass;
this.age = age;
this.address = address;
}
@Override
public String toString()
{
return "用户名:"+this.userName+" 密码:"+this.password+" 年龄:"+this.age+" 地址:"+this.address.city;
}
}
public class Demo3
{
public static void main(String[] args) throws IOException, ClassNotFoundException
{
//把user的信息持久化
/*User user = new User("admin", "123");
System.out.println(user);
*/
writeObj();
readObj();
}
//把文件中的对象信息取出来
public static void readObj() throws IOException, ClassNotFoundException
{
File file = new File("D:\\users.txt");
FileInputStream fileInputStream = new FileInputStream(file);
//建立对象的输入流对象
ObjectInputStream inputStream = new ObjectInputStream(fileInputStream);
//读取独对象信息
User user = (User) inputStream.readObject(); //创建对象坑定要依赖对象所属的class文件
System.out.println(user);
}
//定义方法把对象的信息写到硬盘上------>对象的序列化
public static void writeObj() throws IOException
{
Address address = new Address("China", "Anhui");
User user = new User("admin", "123", 15, address);
//1、找到目标文件
File file= new File("D:\\users.txt");
//建立数据的输出流对象
FileOutputStream fileOutputStream = new FileOutputStream(file);
//建立对象的输出流对象
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
//把对象写出去
objectOutputStream.writeObject(user);//序列化house的内容 是给JVM看的 一般我们是看不懂的
//关闭资源
objectOutputStream.close();
}
}
内部类的序列化
所有的内部类,Local内部类,匿名内部类都可以直接访问外面的封装类的实例变量和方法。而静态嵌套类则不能。
内部类,Local内部类,匿名内部类的实例都持有一个外部封装类实例的隐式引用,而java对象序列化要求对象里所有的对象成员都实现序列化接口。所以,如果只有内部类实现序列化,而外部封装类没有实现序列化接口,就会在对内部类进行序列化的时候报出异常。总结起来,内部类的序列化要求以下2点:
- 外部封装类实现序列化接口;
- 内部类和外部封装类的成员变量,都实现序列化接口;