2. 序列化流ObjectOutputStream java.io.ObjectOutputStream extends OutputStream
ObjectOutputStream:对象的序列化流 - 作用:把对象以流的形式写入文件中保存
构造方法: ObjectOutputStream(OutputStream out) 创建写入指定 OutputStream 的 ObjectOutputStream 特有的成员方法: void writeObject(Object obj) 将指定的对象写入 ObjectOutputStream
使用步骤: 1 创建ObjectOutputStream对象,构造方法中传递字节输出流 2 使用ObjectOutputStream对象中的方法writeObject,把对象写入到文件中 3 释放资源
对象序列化和反序列化需要实现Serializable 接口 序列化和反序列化时,会抛出NotSerializableException没有序列化异常 类通过实现 java.io.Serializable 接口以启用其序列化功能,未实现此接口的类将无法使其任何状态序列化或反序列化Serializable接口也叫标记型接口:接口里什么都没有实现,只是作为标记 要进行序列化和反序列化的类必须实现Serializable接口,就会给类添加一个标记当我们进行序列化和反序列化时,就会检测类上是否有这个标记 - 有:就可以进行序列化和反序列化 - 没有:抛出NotSerializableException没有序列化异常 //创建要写入的对象的类Person,注意需要实现Serializable接口 public class Person 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; } public Person() { super(); } public Person(String name, int age) { super(); this.name = name; this.age = age; } @Override public String toString() { return "Person [name=" + name + ", age=" + age + "]"; } } //main public class demo05ObjectOutputStream { public static void main(String[] args) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("D:\\Eclipse\\workplace\\day1\\src\\num12\\person.txt")); //类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化 oos.writeObject(new Person("泡泡",20)); //NotSerializableException:当实例需要具有序列化接口时,抛出此异常。序列化运行时或实例的类会抛出此异常 oos.close(); } } //由于是以二进制存储,这个文件我们没法直接查看3. 反序列化流ObjectIntputStream java.io.ObjectInputStream extends InputStream
ObjectInputStream:对象的反序列流 - 作用:把文件中保存的对象以流的方式读取
构造方法: ObjectInputStream(InputStream in) 创建从指定 InputStream 读取的 ObjectInputStream - 参数:InputStream in:字节输入流
特有的成员方法: Object readObject() 从 ObjectInputStream 读取对象
使用步骤: 1 创建ObjectInputStream对象,构造方法中传递字节输入流 2 使用ObjectInputStream对象中的方法readObject读取保存对象的文件 3 释放资源 4 使用读取出来的对象(打印)
public class demo06ObjectInputStream { public static void main(String[] args) throws IOException, ClassNotFoundException { //声明两个异常 ObjectInputStream ois = new ObjectInputStream(new FileInputStream("D:\\Eclipse\\workplace\\day1\\src\\num12\\person.txt")); Object obj = ois.readObject(); ois.close(); System.out.println(obj); //Person [name=泡泡, age=20] //强制类型转换 ---> 使用子类特有的属性和方法 Person p = (Person) obj; System.out.println(p.getName()+p.getAge()); //泡泡20 } }4. 异常的处理:
readObject方法声明抛出了ClassNotFoundException(class文件找不到异常) 当不存在对象的class文件时抛出此异常 * 反序列化前提: - 1 类必须实现Serializable接口 - 2 必须存在类对应的class文件
另外,当JVM反序列化对象时,能找到class文件,但是当class文件在序列化对象之后发生了修改,那么反序列化操作也会失败,抛出InvalidClassException异常
异常的原因: - 该类的序列化版本号与流中读取的类描述符的版本号不匹配 - 该类包含未知的数据类型 - 该类没有可访问的无参数构造方法测试: 1 .将person类中的age属性改为public修饰(类重新编译),直接进行反序列化(使用新的序列号和文本原来的序列号比较),则会抛出InvalidClassException异常(serialVersionUID序列号冲突)。 2 .原理: 定义Person类实现序列化接口,那么编译器会把Person.java编译为Person.class字节码文件,会根据类的定义给class里添加serialVersionUID序列化,在进行序列化的时候会把对象保存在文本中,文本中也有这个序列号,再进行反序列化操作,这时class文件中的序列号和文本中的序列号会进行比较,如果一样则反序列化成功,如果序列号不一样,就会抛出InvalidClassException异常解决方案: 无论是否对类的定义进行修改,都不重新生成序列号,可以手动给类添加一个序列号(该字段必须是静态 (static)、最终 (final) 的 long 型字段) eg:static final long serialVersionUID = 42L;static:静态关键字 静态优先于非静态加载到内存中(静态优先于对象进入内存中) 被static修饰的成员变量是不能被序列化的,序列化的都是对象
transient关键字:瞬态关键字 被transient关键字修饰的成员变量,不能被序列化 使用上述例子,将Person的age添加static关键字,进行序列化后再反序列化,会发现读出来的age为 0 (成员变量默认值)。
(效果和static差不多,但是没有静态含义) //Person [name=泡泡, age=0]
java.io.PrintStream:打印流 extends OutputStream
PrintStream:为其他输出流添加了内容,使它们能够方便地打印各种数据值表示形式
PrintStream的特点: 1 只负责数据的输出,不负责数据的读取 2 与其他输出流不同,PrintStream永远不会抛出IOException 3 特有的方法: - void print(任意类型的值) - void println(任意类型的值并换行)构造方法: PrintStream(File file):输出的目的地是一个文件 PrintStream(OutputStream out):输出的目的是一个字节输出流 PrintStream(String fileName):输出的目的地是一个文件路径
继承自父类的成员方法: void close() 关闭此输出流并释放与此流有关的所有系统资源。 void flush() 刷新此输出流并强制写出所有缓冲的输出字节。 void write(byte[] b) 将 b.length 个字节从指定的 byte 数组写入此输出流。 void write(byte[] b, int off, int len) 将指定 byte 数组中从偏移量 off 开始的 len 个字节写入此输出流。 abstract void write(int b) 将指定的字节写入此输出流。
注意:
如果使用继承自父类的write方法写数据,那么查看数据的时候就会查询编码表 97 -> a如果使用自己特有的方法print/println方法写数据,写的数据原样输出 public class demo08PrintStream { public static void main(String[] args) throws FileNotFoundException { //创建打印流PrintStream对象,构造方法中绑定要输出的目的地 PrintStream ps = new PrintStream("D:\\Eclipse\\workplace\\day1\\src\\num12\\print.txt"); //会抛出文件找不到异常,但是没有IO异常 ps.write(97); //使用继承父类的方法 a ps.println(97); //使用自己的方法 97 ps.println('a'); ps.println("hello world"); ps.println(true); ps.close(); } } 改变输出语句的目的地(打印流的流向) 输出语句,默认在控制台输出使用System.setOut方法改变输出语句的目的地为参数传递的打印流的目的地 - static void setOut(PrintStream out) 重新分配“标准”输出流 public class demo09PrintStream { public static void main(String[] args) throws FileNotFoundException { System.out.println("控制台输出"); //创建打印流PrintStream对象,构造方法中绑定要输出的目的地 PrintStream ps = new PrintStream("D:\\Eclipse\\workplace\\day1\\src\\num12\\目的地是打印流.txt"); //会抛出文件找不到异常,但是没有IO异常 System.setOut(ps); //把输出语句的目的地改变为打印流的目的地 System.out.println("我在打印流的目的地中输出"); ps.close(); } }