数据输入流允许应用程序以与机器无关方式从底层输入流中读取基本 Java 数据类型。应用程序可以使用数据输出流写入稍后由数据输入流读取的数据。
对象被写到文件上,对象所属的类必须实现Serializable接口。该接口没有任何方法,只是一个标识接口而已。
对象的反序列化创建对象并不会调用到构造方法。
serialVersionUID 是用于记录class文件的版本信息的,serialVersionUID这个数字是通过一个类的类名、成员、包名、工程名算出的一个数字。
使用ObjectInputStream反序列化的时候,ObjeectInputStream会先读取文件中的serialVersionUID,然后与本地的class文件的serialVersionUID进行对比,如果这两个id不一致,反序列则失败
如果序列化与反序列化的时候可能会修改类的成员,那么最好一开始就给这个类指定一个serialVersionUID,如果一类已经指定的serialVersionUID,然后在序列化与反序列化的时候,jvm都不会再自己算这个 class的serialVersionUID了
如果一个对象某个数据不想被序列化到硬盘上,可以使用关键字transient修饰
如果一个类维护了另外一个类的引用,则另外一个类也需要实现Serializable接口
一旦变量被transient修饰,变量将不再是对象持久化的一部分,该变量内容在序列化后无法获得访问
transient关键字只能修饰变量,而不能修饰方法和类。注意,本地变量是不能被transient关键字修饰的。变量如果是用户自定义类变量,则该类需要实现Serializable接口
被transient关键字修饰的变量不再能被序列化,一个静态变量不管是否被transient修饰,均不能被序列化
反序列化后类中static型变量的值为当前JVM中对应static变量的值,这个值是JVM中的不是反序列化得出的
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 最初只是终止所有现有块数据记录。
//实现Serializable接口
public class User implements Serializable{
private static final long serialVersionUID = 1L;
private String name;
private int age;
private transient String password;
public User(String name, int age, String password) {
this.name = name;
this.age = age;
this.password = password;
}
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;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
return "name=" + name + ", age=" + age + ", password=" + password;
}
}
对象输出流
public class ObjectOutputStreamJava {
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
User u1 = new User("User_1", 18, "1234");
User u2 = new User("User_2", 28, "2234");
User u3 = new User("User_3", 38, "3234");
User u4 = new User("User_4", 48, "4234");
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("d:" + File.separator + "javatest" + File.separator + "Object.txt"));
oos.writeObject(u1);
oos.writeObject(u2);
oos.writeObject(u3);
oos.writeObject(u4);
oos.close();
}
}
对象输入流
public class ObjectInputStreamJava {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
// TODO Auto-generated method stub
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("d:" + File.separator + "javatest" + File.separator + "Object.txt"));
// 读取对象一
User u1 = (User) ois.readObject();
System.out.println(u1.toString());
// 读取对象二
User u2 = (User) ois.readObject();
System.out.println(u2.toString());
// 读取对象三
User u3 = (User) ois.readObject();
System.out.println(u3.toString());
// 读取对象四
User u4 = (User) ois.readObject();
System.out.println(u4.toString());
ois.close();
}
}
上面是分别存入4个对象,再读取的话需要读取四次。可以先把要存入的对象存入集合中,再直接读取集合就可以
public class ObjectOutputStreamJava {
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
User u1 = new User("User_1", 18, "1234");
User u2 = new User("User_2", 28, "2234");
User u3 = new User("User_3", 38, "3234");
User u4 = new User("User_4", 48, "4234");
//把要传入对象流的对象存入集合中
LinkedList<User> ll = new LinkedList<>();
ll.add(u1);
ll.add(u2);
ll.add(u3);
ll.add(u4);
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("d:" + File.separator + "javatest" + File.separator + "Object.txt"));
// oos.writeObject(u1);
// oos.writeObject(u2);
// oos.writeObject(u3);
// oos.writeObject(u4);
//把集合写入
oos.writeObject(ll);
oos.close();
}
}
输入流
public class ObjectInputStreamJava {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
// TODO Auto-generated method stub
ObjectInputStream ois = new ObjectInputStream(
new FileInputStream("d:" + File.separator + "javatest" + File.separator + "Object.txt"));
// // 读取对象一
// User u1 = (User) ois.readObject();
// System.out.println(u1.toString());
//
// // 读取对象二
// User u2 = (User) ois.readObject();
// System.out.println(u2.toString());
//
// // 读取对象三
// User u3 = (User) ois.readObject();
// System.out.println(u3.toString());
//
// // 读取对象四
// User u4 = (User) ois.readObject();
// System.out.println(u4.toString());
//读取到集合
@SuppressWarnings("unchecked")
LinkedList<User> ll = (LinkedList<User>) ois.readObject();
for(User u : ll) {
System.out.println(u);
}
ois.close();
}
}
在文件末尾追加对象,在FileInputStream追加设置为true就可以
public class ObjectOutputStreamJava {
public static void main(String[] args) throws IOException {
// TODO Auto-generated method stub
User u1 = new User("User_1", 18, "1234");
User u2 = new User("User_2", 28, "2234");
User u3 = new User("User_3", 38, "3234");
User u4 = new User("User_4", 48, "4234");
//把要传入对象流的对象存入集合中
LinkedList<User> ll = new LinkedList<>();
ll.add(u1);
ll.add(u2);
ll.add(u3);
ll.add(u4);
ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream("d:" + File.separator + "javatest" + File.separator + "Object.txt"));
// oos.writeObject(u1);
// oos.writeObject(u2);
// oos.writeObject(u3);
// oos.writeObject(u4);
//把集合写入
oos.writeObject(ll);
//未关闭流追加对象到文件
User u5 = new User("User_5", 58, "5234");
oos.writeObject(u5);
oos.close();
//关闭流又重新建立一个流对象追加文件到对象
ObjectOutputStream oos2 = new ObjectOutputStream(
new FileOutputStream("d:" + File.separator + "javatest" + File.separator + "Object.txt", true));
User u6 = new User("User_6", 68, "6234");
oos2.writeObject(u6);
oos2.close();
}
}
两种追加文件的方式是不一样的。没有关闭流直接添加的话和普通添加对象一样,如果是关闭了流又重新建立一个流添加,流会在建立的时候在添加头数据,两个short,这里如果直接通过reObject()读取的话会报错。可以用FileInputStream读掉这两个Short数据,再用ObjectInputStream读对象。
上面先把对象添加到集合中,然后添加了一个对象,接着关闭流,这里后添加的对象不在集合中,所以读的时候只读集合的话是没法把后面添加的对象读出来的。
ObjectInputStream必须根据添加顺序读取对象。
public class ObjectInputStreamJava {
public static void main(String[] args) throws FileNotFoundException, IOException, ClassNotFoundException {
// TODO Auto-generated method stub
FileInputStream fis =new FileInputStream("d:" + File.separator + "javatest" + File.separator + "Object.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
//读取到集合
//添加对象到文件的时候这里只能读到4个对象,因为只添加到集合中4个对象
@SuppressWarnings("unchecked")
LinkedList<User> ll = (LinkedList<User>) ois.readObject();
for(User u : ll) {
System.out.println(u);
}
User readUser5 = (User) ois.readObject();
System.out.println(readUser5.toString());
//这里会报错,因为添加5的时候没有关闭流,添加6的之前关闭了流,6是之后重新建立的流
//而新建一个对象流的时候会首先在写入两个Short类型的数据,所以直接读取会报StreamCorruptedException
//可以在读对象之前把这四个字节的头数据读出来
byte[] buf = new byte[4];
fis.read(buf);
//再读对象
User readUser6 = (User)ois.readObject();
System.out.println(readUser6.toString());
ois.close();
}
}
上面的方法读取的时候每次都要先读四个字节的头信息很不方便,可以使用下面代码实现,不需要每次都把头信息读掉
public class NewObjectOutputStream {
public static void main(String[] args) throws FileNotFoundException, IOException {
User u1 = new User("User_1", 18, "1234");
User u2 = new User("User_2", 28, "2234");
User u3 = new User("User_3", 38, "3234");
User u4 = new User("User_4", 48, "4234");
//把要传入对象流的对象存入集合中
LinkedList<User> ll = new LinkedList<>();
ll.add(u1);
ll.add(u2);
ll.add(u3);
ll.add(u4);
String sPath = "d:" + File.separator + "javatest" + File.separator + "Object.txt";
ObjectOutputStream oos = MyObjectOutputStream.newInstance(sPath);
oos.writeObject(u1);
oos.close();
ObjectOutputStream oos2 = MyObjectOutputStream.newInstance(sPath);
oos2.writeObject(u2);
oos2.close();
ObjectOutputStream oos3 = MyObjectOutputStream.newInstance(sPath);
oos3.writeObject(u3);
oos3.close();
ObjectOutputStream oos4 = MyObjectOutputStream.newInstance(sPath);
oos4.writeObject(u4);
oos4.close();
}
}
class MyObjectOutputStream extends ObjectOutputStream{
public MyObjectOutputStream(OutputStream oos) throws IOException {
super(oos); //这里会调用writeStreamHeader()
}
//重写writeStreamHeader()方法
//writeStreamHeader()方法在建立流对象的时候会添加文件头,重写之后就不会写入文件头
public void writeStreamHeader() throws IOException {
//头数据
// bout.writeShort(STREAM_MAGIC);
// bout.writeShort(STREAM_VERSION);
super.reset();
}
public static ObjectOutputStream newInstance(String file) throws FileNotFoundException, IOException {
File f = new File(file);
long length = f.length();
ObjectOutputStream oos = null;
//文件为空
if(length == 0) {
oos = new ObjectOutputStream(new FileOutputStream(new File(file), true));
//文件不为空
}else {
oos = new MyObjectOutputStream(new FileOutputStream(new File(file), true));
}
return oos;
}
}
public class newObjectInputStream {
public static void main(String[] args) throws IOException, ClassNotFoundException {
// TODO Auto-generated method stub
FileInputStream fis =new FileInputStream("d:" + File.separator + "javatest" + File.separator + "Object.txt");
ObjectInputStream ois = new ObjectInputStream(fis);
User u1 = (User)ois.readObject();
System.out.println(u1.toString());
User u2 = (User)ois.readObject();
System.out.println(u2.toString());
User u3 = (User)ois.readObject();
System.out.println(u3.toString());
User u4 = (User)ois.readObject();
System.out.println(u4.toString());
ois.close();
}
}