JavaSE进阶 | 深入理解Java IO流(第三篇)

发布于:2023-01-26 ⋅ 阅读:(16) ⋅ 点赞:(0) ⋅ 评论:(0)

✅作者简介:大家好我是@每天都要敲代码,希望一起努力,一起进步!
📃个人主页:@每天都要敲代码的个人主页

🔥系列专栏:JavaSE从入门到精通
💬推荐一款模拟面试、刷题神器,从基础到大厂面试题👉点击跳转刷题网站进行注册学习

目录

🥅目录拷贝

🥅ObjectInputStream && ObjectOutputStream

1.序列化的实现

2.反序列化的实现

3.序列化 && 反序列化多个对象

4.序列化版本号

🥅IO和Properties联合使用

结束语


🥅目录拷贝

把一个目录拷贝到另一个目录(包括里面的内容)
需要用到:FileInputStream+FileOutputStream+File+递归的思想

package com.bjpowernode.java.io;

import java.io.*;

public class CopyAll {
    public static void main(String[] args) {
        // 拷贝源
        File srcFile = new File("C:\\Java学习\\javaSE学习");
        // 拷贝目标
        File destFile = new File("D:\\a\\b\\c");
        // 调用方法进行拷贝
        copyDir(srcFile,destFile);
    }

    /**
     * 拷贝目录
     * @param srcFile 拷贝源
     * @param destFile 拷贝目标
     */
    private static void copyDir(File srcFile, File destFile) {
        //4、递归结束的条件,如果是文件就结束(就开始拷贝)
        if(srcFile.isFile()){
            // 是文件的是要要拷贝文件,一边读一遍写
            FileInputStream in = null;
            FileOutputStream out = null;
            try {
                // 读文件
                in = new FileInputStream(srcFile);
                // 写文件
                String path =  (destFile.getAbsolutePath().endsWith("\\")?destFile.getAbsolutePath() :
                        destFile.getAbsolutePath()+"\\")+srcFile.getAbsolutePath().substring(3);
                System.out.println(path);
               out = new FileOutputStream(path);
                // 一边读一边写
                byte[] bytes = new byte[1024*1024];// 1M
                int readCount = 0;
                while((readCount = in.read(bytes)) != -1){
                    out.write(bytes,0,readCount);
                }
                // 刷新
                out.flush();
            } catch (FileNotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                if (out != null) {
                    try {
                        out.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            return; // srcFile如果是一个文件,递归结束
        }

        //1、 获取拷贝源下面的子目录
        File[] files = srcFile.listFiles(); //列出源文件下的子目录(目录和文件)
        // 这个file有可能是目录,也有可能是文件;但都是一个File对象
        for(File file:files){
            // 获取所有文件的(目录和文件)绝对路径;进行打印测试
            //System.out.println(file.getAbsolutePath());

            // 3、创建对应的目录(目标文件对应的目录,要提前创建好)
            if(srcFile.isDirectory()){ // 如果是一个目录
                // 新建对应的目录
                String srcDir = file.getAbsolutePath(); // 拿出所有的源目录
                // 这里如果只是一个盘符的话,写上"//"会被识别出来;如果后面有其它目录,就识别不了"//"
                // srcDir.substring(3)表示从下标为3的地方开始截取
                String destDir = (destFile.getAbsolutePath().endsWith("\\")? destFile.getAbsolutePath() :
                        destFile.getAbsolutePath()+"\\")+srcDir.substring(3); // 目标目录
                // 获取路径后,开始创建
                File newFile = new File(destDir);
                if(!newFile.exists()){ // 如果不存在,就递归创建目录
                    newFile.mkdirs();
                }
            }

            //2、 递归调用(是目录的话一直往下拿,直到是一个文件就开始拷贝)
            copyDir(file,destFile);

        }
    }
}

🥅ObjectInputStream && ObjectOutputStream

1、首先我们先明白两个概念:序列化(Serialize)反序列化(DeSerialize)

        序列化:将java对象存储到文件中,将java对象的状态保存下来的过程

        反序列化:将硬盘上的数据重新恢复到内存当中,恢复成java对象

2、ObjectInputStream 和 ObjectOutputStream的作用

        ObjectOutputStream是用来序列化的---》拆分对象

        ObjectInputStream是用来反序列化的---》组装对象

3、通过图,来理清楚它们之间的关系

 

1.序列化的实现

(1)参与序列化和反序列化的对象必须实现Serializable接口。如果下面的Student类没有实现Serializable接口,会报java.io.NotSerializableException,译为:Student对象不支持序列化!

(2)注意:通过源代码发现,Serializable接口只是一个标志接口:

 public interface Serializable {
    }

(3)这个接口当中什么代码都没有;那么它起到一个什么作用呢?
       起到标识的作用,标志的作用,java虚拟机看到这个类实现了这个接口,可能会对这个类进行特殊待遇。Serializable这个标志接口是给java虚拟机参考的,java虚拟机看到Serializable这个接口之后,会为该类自动生成一个序列化版本号

package com.bjpowernode.java.io;

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;


public class ObjectOutputStreamTest01 {
    public static void main(String[] args) throws Exception {
        // 创建Java对象
        Student stu = new Student(111,"zhangsan");
        // 序列化
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("s文件"));
        // 序列化对象
        oos.writeObject(stu);
        // 刷新
        oos.flush();
        // 关闭
        oos.close();


    }
}

// 学生类
class Student<toString> implements Serializable {
    private int no;
    private String name;
    // 构造方法
    public Student() {
    }
    public Student(int no, String name) {
        this.no = no;
        this.name = name;
    }
    // setter and getter
    public int getNo() {
        return no;
    }
    public void setNo(int no) {
        this.no = no;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    // 重写toString
    public String toString() {
        return "Student{" +
                "no=" + no +
                ", name='" + name + '\'' +
                '}';
    }
}

2.反序列化的实现

package com.bjpowernode.java.io;
// 反序列化
import java.io.FileInputStream;
import java.io.ObjectInputStream;

public class ObjectInputStreamTest01 {
    public static void main(String[] args) throws Exception{
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("s文件"));
        // 开始反序列化,读
        Object obj = ois.readObject();
        // 反序列化回来是一个学生对象,所以会调用学生对象的toString方法。
        System.out.println(obj);
        // 关闭
        ois.close();
    }
}

3.序列化 && 反序列化多个对象

1、可以一次序列化多个对象呢?
     可以将对象放到集合当中,序列化集合!
2、参与序列化的ArrayList集合以及集合中的元素User都需要实现 java.io.Serializable接口

3、补充一个关键字:transient;这个关键字表示游离的,不参与序列化。     

序列化 

package com.bjpowernode.java.io;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

public class ObjectOutputStreamTest02 {
    public static void main(String[] args) throws Exception {
        // 创建集合
        List<User> userList = new ArrayList<>();
        // 增加元素
        userList.add(new User(1,"zhangsan"));
        userList.add(new User(2,"lisi"));
        userList.add(new User(3,"wangwu"));
        // 序列化
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Users"));
        // 序列化集合--->一次性序列化多个对象
        oos.writeObject(userList);

        // 刷新
        oos.flush();
        // 关闭
        oos.close();


    }
}

// USer类
class User implements Serializable {
    private int num;
    // 表示name不参与序列化操作;name打印出来的结果就是null
    private transient String name; 
    // 构造方法
    public User() {
    }
    public User(int num, String name) {
        this.num = num;
        this.name = name;
    }
    // setter and getter
    public int getNum() {
        return num;
    }
    public void setNum(int num) {
        this.num = num;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    // 重写toStringF方法
    public String toString() {
        return "User{" +
                "num=" + num +
                ", name='" + name + '\'' +
                '}';
    }
}

反序列化 

package com.bjpowernode.java.io;

import java.util.List;
import java.io.FileInputStream;
import java.io.ObjectInputStream;

// 反序列化
public class ObjectInputStreamTest02 {
    public static void main(String[] args) throws Exception {
        // 开始反序列化,读
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Users"));
        // 反序列化回来一个List集合
        //Object obj = ois.readObject();
        //System.out.println(obj instanceof List); // true,得到返回的是List集合

       List<User> userList = (List<User>)ois.readObject();
       for(User u:userList){
           System.out.println(u);
       }
        // 关闭
        ois.close();
    }
}

4.序列化版本号

(1)java语言中是采用什么机制来区分类的?
      第一:首先通过类名进行比对,如果类名不一样,肯定不是同一个类。
      第二:如果类名一样,再怎么进行类的区别?靠序列化版本号进行区分。

(2)序列化版本号有什么用呢?

    小鹏编写了一个类:com.bjpowernode.java.bean.Student implements Serializable
    胡浪编写了一个类:com.bjpowernode.java.bean.Student implements Serializable

所以序列化版本号是用来区分类的!例如:类名相同,我们可以通过不通的序列化版本号来区分它;对于但也有缺点,对于同一个代码,我们更改了,必须重新编译才可以!

例如:对于上述的Student类,我们采用默认的序列化版本号,一年前我们实现了这个功能,有一个序列化版本号;一年后我们优化更改了代码,此时JVM就会认为这已经不是同一个类,必须重新进行编译,才能反序列化
(3)自动生成序列化版本号的好处和缺陷    

好处:不同的人编写了同一个类,但“这两个类确实不是同一个类”。这个时候序列化版本就起上作用了。对于java虚拟机来说,java虚拟机是可以区分开这两个类的,因为这两个类都实现了Serializable接口,都有默认的序列化版本号,他们的序列化版本号不一样。所以区分开了。)
缺陷:这种自动生成的序列化版本号缺点是一旦代码确定之后,不能进行后续的修改,因为只要修改,必然会重新编译,此时会生成全新的序列化版本号,这个时候java虚拟机会认为这是一个全新的类。

(4)总结:从需求来看

凡是一个类实现了Serializable接口,建议给该类提供一个固定不变的序列化版本号。这样,以后这个类即使代码修改了,但是版本号不变,java虚拟机会认为是同一个类。

Java虚拟机看到Serializable接口之后,会自动生成一个序列化版本号。
这里没有手动写出来,java虚拟机会默认提供这个序列化版本号。
建议将序列化版本号手动的写出来。不建议自动生成   

 private static final long serialVersionUID = 1L;

   java虚拟机识别一个类的时候先通过类名,如果类名一致,再通过序列化版本号。

(5)设置IDEA自动生成序列版本号

File--->Settings--->Code Style--->Inspections--->把下面这个打上对勾--->Apply--->OK

 最终回到我们继承Serializable接口的类名上,alt+Enter即可自动生成序列版本号!

🥅IO和Properties联合使用

(1)设计理念:把以后经常改变的数据,可以单独写到一个文件中,使用程序动态读取。
    将来只需要修改这个文件的内容,java代码不需要改动,不需要重新编译,服务器也不需要重启;就可以拿到动态的信息。

(2)类似于以上机制的这种文件被称为配置文件;并且当配置文件中的内容格式是:
        key1=value
        key2=value
的时候,我们把这种配置文件叫做属性配置文件,在属性配置文件当中#注释,如果key重复的话,value会自动覆盖。

(3)java规范中有要求:属性配置文件建议以.properties结尾,但这不是必须的。
    这种以.properties结尾的文件在java中被称为:属性配置文件;其中Properties是专门存放属性配置文件内容的一个类

(4)Properties是一个Map集合,key和value都是String类型

package com.bjpowernode.java.io;

import java.io.FileReader;
import java.util.Properties;

public class IoProperiesTest02 {
    public static void main(String[] args) throws Exception {
        // 新建一个输入流对象
        FileReader reader = new FileReader("temp.txt");
        // 新建一个Map集合
        Properties pro = new Properties();
        // 调用Properties对象的load方法将文件中的数据加载到Map集合中。
        pro.load(reader);// 文件中的数据顺着管道加载到Map集合中,其中等号=左边做key,右边做value
        // 通过key获取value
        String username = pro.getProperty("username");
        System.out.println(username); //admin
        // 如果我们把admin改成root,不需要重新进行编译,就能直接运行得到root
        System.out.println(pro.getProperty("password")); // 123
    }

}

/*temp.txt文件里的内容
username=admin
password=123
        */

结束语

今天的分享就到这里啦!快快通过下方链接注册加入刷题大军吧!各种大厂面试真题在等你哦!

 💬刷题神器,从基础到大厂面试题👉点击跳转刷题网站