对象IO流

一、对象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();//错误
    }
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值