一、对象IO流
1、对象IO流的作用:直接读和写Java的对象
2、对象IO流有:
ObjectOutputStream:输出对象,又称为对象序列化
对象序列化:把Java对象转为字节序列
构造器:ObjectOutputStream(OutputStream out)
ObjectInputStream:读取对象,又称为对象反序列化
对象反序列化:把字节序列重构成一个Java对象
构造器:ObjectInputStream(InputStream in)
3、说明
(1)当对象要输出(序列化)时,必须要求对象的类型实现java.io.Serializable接口,否则会报 NotSerializableException不支持序列化的异常
例如:要输出Student对象,Student类要实现java.io.Serializable接口,
(2)当对象要输出(序列化)时,必须要求对象的类型实现java.io.Serializable接口,
如果对象中包含其他的引用数据类型的属性,那么这个属性的类型也要实现java.io.Serializable接口,
例如:要输出Student对象,Student类要实现java.io.Serializable接口,
同时因为Student对象中有一个Teacher属性,它也是一个对象,输出学生对象时,也要求Teacher类实现java.io.Serializable接口,
(3)因为序列化和反序列化的代码不一定是同一个主机运行的,也不一定是同一个时间运行的,
那么有可能之前序列化的好的文件,在后来反序列化时(或者这个文件传到别的主机上反序列化时),会找不到对象对应的类型,
那么就可能发生ClassNotFoundException。
当我们对象反序列化时,需要当前的运行环境中,必须有这个对象反序列化需要的类,否则就会报 ClassNotFoundException
例如:stu.data文件中存储了一个Student对象,那么在反序列化时,必须要求当前运行环境中Student类的.class文件
(4)当我们把对象序列化之后,又修改了对象的类型,即相当于这个对象对应的类的.class文件重新生成,
再次运行反序列化的代码时,就报InvalidClassException:无效的类异常
例如:Student类的对象序列化之后,对Student类做了修改,Student.class重新生成了,此时反序列化时报
java.io.InvalidClassException: com.atguigu.io.Student;
local class incompatible( 不相容的;矛盾的):
stream(流) classdesc(类描述description) serialVersionUID(序列化版本ID) = -5681219687330532574,
local(本地) class serialVersionUID = 1199428526584274670
解释:如果我们没有在类中“固定”serialVersionUID(序列化版本ID),那么每一次编译都会自动生成一个新的serialVersionUID(序列化版本ID),
那么之前序列化(输出对象)时,序列化版本ID是-5681219687330532574,后来修改了Student类,生成了新的serialVersionUID是1199428526584274670。
如何解决呢?
当我们实现java.io.Serializable接口时,同时应该在类中固定一个serialVersionUID(序列化版本ID)。否则就会导致每一次对类的修改都自动生成新serialVersionUID(序列化版本ID)。
例如:Student类
private static final long serialVersionUID = -5681219687330532574L; //这个值填什么都可以
(5)当对象的某个属性不希望序列化时,可以给这个属性(成员变量)加transient修饰
transient:临时的,短暂的,不需要序列化,持久化
(6)如果某个成员变量是static,那么序列化也会忽略它,换句话说,静态变量不会序列化
为什么静态的变量不序列化?
因为序列化是针对“对象”,不是针对“类”,所以需要存储的是对象的“独有”的信息。而静态变量是所有对象“共享”的信息。
public class TestObjectIO {
@Test
public void test03() throws IOException {
//演示Student对象中包含Teacher对象
//调用构造器,方法时,如何查看“形参列表”,快捷键Ctrl + P(parameter)
Student stu = new Student(1,"张三",85.5, new Teacher());
//需求:把这个对象存到d:/stu.data文件中
//要输出数据到文件,又不是纯文本文件,那么需要FileOutputStream
FileOutputStream fos = new FileOutputStream("d:/stu.data");
//因为要直接输出对象,所以要用ObjectOutputStream,
ObjectOutputStream oos = new ObjectOutputStream(fos);
//输出对象
oos.writeObject(stu); //NotSerializableException不支持序列化的异常
//关闭资源
oos.close();
fos.close();
}
@Test
public void test02() throws IOException, ClassNotFoundException {
//需求:读取d:/stu.data文件中存储的学生对象
//要读取文件,又不是纯文本文件,那么需要FileInputStream
FileInputStream fis = new FileInputStream("d:/stu.data");
//要读取文件中的对象,所以要用ObjectInputStream
ObjectInputStream ois = new ObjectInputStream(fis);
//读取对象
Object obj = ois.readObject();//编译时,方法的返回值类型是Object类型,
// 实际返回的对象的类型是看你文件中存储的对象的类型,它才是对象的运行时类型
System.out.println(obj);
System.out.println(obj.getClass());
//释放资源
ois.close();
fis.close();
}
@Test
public void test01() throws IOException {
//调用构造器,方法时,如何查看“形参列表”,快捷键Ctrl + P(parameter)
Student stu = new Student(1,"张三",85.5);
//需求:把这个对象存到d:/stu.data文件中
//要输出数据到文件,又不是纯文本文件,那么需要FileOutputStream
FileOutputStream fos = new FileOutputStream("d:/stu.data");
//因为要直接输出对象,所以要用ObjectOutputStream,
ObjectOutputStream oos = new ObjectOutputStream(fos);
//输出对象
oos.writeObject(stu); //NotSerializableException不支持序列化的异常
//关闭资源
oos.close();
fos.close();
}
}
问?ObjectOutpuStream输出一个对象,那么如果想要输出多个对象怎么办?
如果有多个对象,我们可以把多个对象,放到“集合”中,然后输出“集合对象”
public class TestObjectIO2 {
@Test
public void test01() throws IOException {
Student s1 = new Student(1,"张三",85.5);
Student s2 = new Student(2,"李四",95.5);
Student s3 = new Student(3,"王五",83.5);
ArrayList<Student> list = new ArrayList<>();
list.add(s1);
list.add(s2);
list.add(s3);
FileOutputStream fos = new FileOutputStream("d:/students.data");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(list);
oos.close();
fos.close();
}
@Test
public void test02() throws IOException, ClassNotFoundException {
FileInputStream fis = new FileInputStream("d:/students.data");
ObjectInputStream ois = new ObjectInputStream(fis);
Object obj = ois.readObject();
ArrayList<Student> list = (ArrayList<Student>) obj;
for (Student student : list) {
System.out.println(student);
}
ois.close();
fis.close();
}
}
二、IO流的关闭有顺序要求。
原则:
先关外面的,再关里面的。
例如:
FileWriter fw = new FileWriter("d:/1.txt"); //里面 穿内衣
BufferedWriter bw = new BufferedWriter(fw); //外面 穿外套
关闭
bw.close(); //先关外面的 先脱外套
fw.close(); //再关里面的 再脱内衣
是否有别的解决方案?
方案一:包装在里面的IO流使用匿名对象,最后只关最外面的
方案二:使用try...catch的新语法
JDK1.7新增的语法,称为try...catch...with...resource,专门为关闭资源和处理相应异常的新try...catch形式
语法格式:
try(
资源对象的创建和声明
){
可能发生异常的业务逻辑代码
}catch(异常类型1 参数名){
处理异常的代码
}catch(异常类型2 参数名){
处理异常的代码
}
这个形式的try...catch,可以保证在try()中声明的资源,无论是否发生异常,无论是否处理异常,都会自动关闭。
这里有一个要求,在try()中的资源对象的类型必须实现java.lang.AutoClosable接口
public class TestIOClose {
@Test
public void test05() {
try(
FileWriter fw = new FileWriter("d:/1.txt");
BufferedWriter bw = new BufferedWriter(fw);
){
bw.write("hello");
}catch(IOException e){
e.printStackTrace();
}
}
@Test
public void test03() {
FileWriter fw = null;//提取出来的目的是,为了在finally中仍然可以使用fw,bw
BufferedWriter bw = null;
try {
fw = new FileWriter("d:/1.txt");
bw = new BufferedWriter(fw);
bw.write("hello");
} catch (IOException e) {
e.printStackTrace();
} finally{
try {
if(bw!=null) {
bw.close();
}
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
if(fw != null){
fw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
@Test
public void test04() {
FileWriter fw = null;
BufferedWriter bw = null;
try {
fw = new FileWriter("d:/1.txt");
bw = new BufferedWriter(fw);
bw.write("hello");
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
bw.close(); //如果这句代码关闭时发生异常了,下面fw.close()不执行,关闭可能不彻底
fw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
@Test
public void test02() throws IOException {
BufferedWriter bw = new BufferedWriter(new FileWriter("d:/1.txt"));
bw.write("hello");
bw.close();
}
@Test
public void test01() throws IOException {
FileWriter fw = new FileWriter("d:/1.txt");
BufferedWriter bw = new BufferedWriter(fw);
bw.write("hello");
fw.close();
bw.close();//java.io.IOException: Stream closed
/*
缓冲流BufferedWriter,把数据先写到缓冲区,
默认情况下是当缓冲区满,或调用close,或调用flush这些情况才会把缓冲区的数据输出。
bw.close()时,需要把数据从缓冲区的数据输出。
数据的流向: 写到bw(缓冲区)-->fw ->"d:/1.txt"
此时,我先把fw关闭了,bw的数据无法输出了
*/
}
}
问?序列化需要实现什么接口?
java.io.Serializable接口(重点)
那么这个接口是默认的序列化的规则,我们能否自己指定序列化的规则(顺序,哪些成员变量需要序列化)?
意思:实现Serializable接口,对象的属性序列化的顺序是默认的,transient和static的成员变量是不序列化的,
那么有没有别的方式,可以我们自己确定属性序列化的顺序,以及我想要把transient和static的成员变量也序列化,是否可以做到?
答:有
实现java.io.Externalizable接口(了解)
java.io.Externalizable接口要求序列化的对象必须有无参构造。
java.io.Externalizable接口要求序列化的对象的类加一个serialVersionUID
Externalizable接口有两个抽象方法需要重写:
@Override
public void writeExternal(ObjectOutput out) throws IOException {
//写哪些属性需要序列化,以及确定顺序
}
@Override
public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
//反序列化的过程
public class TestExam {
@Test
public void test02()throws Exception{
FileInputStream fis = new FileInputStream("d:/chinese.data");
ObjectInputStream ois = new ObjectInputStream(fis);
Chinese.setCountry("中华人民共和国");
System.out.println("country = " + Chinese.getCountry());
Object obj = ois.readObject();
System.out.println(obj);
System.out.println("country = " + Chinese.getCountry());
//释放资源
ois.close();
fis.close();
}
@Test
public void test01()throws Exception{
FileOutputStream fos = new FileOutputStream("d:/chinese.data");
ObjectOutputStream oos = new ObjectOutputStream(fos);
Chinese c = new Chinese("张三",23);
Chinese.setCountry("中国");
oos.writeObject(c);
//释放资源
oos.close();
fos.close();
}
}
三、重新认识之前的IO流
1、java.io.PrintStream(了解)
System.out
System.err
System.out,err,in对象是Java的常量对象
System类
public final static InputStream in = null;
public final static PrintStream out = null;
public final static PrintStream err = null;
因为它们有final修饰,表示在Java层面无法修改。
但是发现System类提供set方法来修改in,out,err,为什么?
因为它们的修改是通过C语言等底层的方式修改。
private static native void setIn0(InputStream in);
private static native void setOut0(PrintStream out);
private static native void setErr0(PrintStream err);
2、java.util.Scanner
不只是键盘输入
public class TestPrintStreamScanner {
@Test
public void test05()throws Exception{
Scanner input = new Scanner(new File("d:/1.txt"));
while(input.hasNextLine()){
String line = input.nextLine();
System.out.println(line);
}
//加上这句话
input.close();
}
@Test
public void test04()throws Exception{
System.setIn(new FileInputStream("d:/1.txt"));
Scanner input = new Scanner(System.in);
while(input.hasNextLine()){
String line = input.nextLine();
System.out.println(line);
}
//加上这句话
input.close();
}
@Test
public void test03(){
Scanner input = new Scanner(System.in);
System.out.print("请输入姓名:");
String name = input.nextLine();
System.out.println("name = " + name);
//加上这句话
input.close();
}
@Test
public void test02() throws FileNotFoundException {
System.setOut(new PrintStream("d:/1.txt"));
System.out.println("hello");
System.out.println("world");
System.out.println("java");
}
@Test
public void test01(){
PrintStream out = System.out;
out.println("hello");
out.println(12);
out.println();
out.print("world");
out.print('a');
// out.print();//错误
}
}