zl程序教程

您现在的位置是:首页 >  后端

当前栏目

JavaSE进阶 | 深入理解Java IO流(目录拷贝、对象专属流、IO和Properties的联合使用)

JAVAJavaSE对象IO 深入 理解 目录 进阶
2023-09-14 09:04:53 时间

目录

🥅目录拷贝

🥅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

序列化:用ObjectOutputStream类保存基本类型数据或对象的机制。方法为:public final void writeObject (Object obj) : 将指定的对象写出。

反序列化:用ObjectInputStream类读取基本类型数据或对象的机制。方法为:​​​​​​​public final Object readObject () : 读取一个对象。

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这个接口之后,会为该类自动生成一个序列化版本号

(4)步骤:

第一步:准备一个Java对象,实现Serializable接口,例如:Student implements Serializable ;注意:要想完成序列化必须实现Serializable接口,这是和前面操作不同的地方

第二步:创建一个文件输出流FileOutputStream,参数要写入文件的名字;

第三步:创建一个ObjectOutputStream对象,因为是包装流,把上面的FileOutputStream流写进去;

第四步:调用writeObject把上面创建的Java对象传进去,这样就完成了序列化;

第五步:调用readObject方法就可以完成反序列化。

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文件"));
        // 序列化对象,默认回去调用toString方法
        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)可以一次序列化多个对象呢?
可以一次性序列化多个对象,例如:将对象放到ArrayList集合当中,序列化ArrayList集合!
(2)参与序列化的ArrayList集合中的Java对象都需要实现 java.io.Serializable接口

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

对于某个属性我们不希望它参与序列化,就可以在属性前面加上transient关键字,加上关键字以后;对于基本数据类型打印出来的是默认值,对于引用数据类型打印出来的是null !

(4)序列化集合,调用readObject()方法,实际上返回的还是一个Object对象,我们可以打印这个对象,一下子全部取出来;也可以把它强制类型转换为List集合,然后遍历集合,一个一个的取出来。

序列化 

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"));
        // 反序列化回来还是Object对象
        Object obj = ois.readObject();
        //System.out.println(obj instanceof List); // true,得到返回的是List集合
       // 反序列化回来一个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自动生成序列版本号

如果我们不想手动输入序列化版本号,想让IDEA自动给我们生成一个怎们办呢?需要进行设置:File--->Settings--->Editor--->Code Style--->Inspections--->JVM languages--->把下面这个打上对勾--->Apply--->OK

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

总结自定义类要想实现序列化机制,需要满足:

① 自定义类需要实现接口:Serializable
② 要求自定义类声明一个全局常量: static final long serialVersionUID = 42234234L;用来唯一的标识当前的类。
③ 要求自定义类的各个属性也必须是可序列化的。
   > 对于基本数据类型的属性:默认就是可以序列化的
   > 对于引用数据类型的属性:要求实现Serializable接口

④ 如果不声明全局常量serialVersionUID,系统会自动声明生成一个针对于当前类的serialVersionUID。如果修改此类的话,会导致serialVersionUID变化,进而导致反序列化时,出现InvalidClassException异常。
⑤类中的属性如果声明为transient或static,则不会实现序列化

🥅IO和Properties联合使用

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

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

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

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

(5)步骤:

第一步:先准备一个jdbc.properties文本文件

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql:/localhost:8080/bjpowernode
username=root
password=1223

第二步:创建一个文件输入流FileReader(FileInputStream也可以)

第三步:创建一个集合Properties(继承HashTable,key和value只能存String类型)

第四步:调用集合的load(reader流)方法,加载上面的文件流到集合当中

第五步:调用集合的getProperties(key)方法获取,里面的参数的key就是jdbc.properties前面的值 

package com.bjpowernode.io;

import java.io.*;
import java.util.Properties;

// IO + Properties读属性配置文件
public class Test {
    public static void main(String[] args) {
        FileReader reader = null;
        try {
            // 第一步:创建输入流对象
            reader = new FileReader("jdbc.properties");
            // 第二步:创建IO流
            Properties properties = new Properties();
            // 第三步:调用load方法加载
            properties.load(reader);
            // 第四步:获取
            System.out.println(properties.getProperty("driver"));
            System.out.println(properties.getProperty("url"));
            System.out.println(properties.getProperty("username"));
            System.out.println(properties.getProperty("password"));


        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            // 关闭流
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

执行结果如下:

​​​​​​​