2024年最新版零基础详细Java知识笔记【IO流】⑧

第八章 IO流


前言

2024年最新版零基础Java笔记,最全最详细笔记,适用于零基础小白、软件设计师、就业、提升等,笔记会持续更新,共同进步,希望可以帮助更多的小伙伴!

感谢杜老师的超级无敌详细讲解,感谢动力节点,真的受益匪浅!
笔记根据老杜《动力节点》视频进行编写,视频地址:动力节点Java零基础视频(下)
笔记有写的不好的地方,恳请在评论区指正批评,谢谢!


8.1 IO流概述

  1. 什么是IO流?
    • 水分子的移动形成了水流。
    • IO流指的是:程序中数据的流动。数据可以从内存流动到硬盘,也可以从硬盘流动到内存。
    • Java中IO流最基本的作用是:完成文件的读和写。
  2. IO流的分类?
    • 根据数据流向分为:输入和输出是相对于内存而言的。
      • 输入流:从硬盘到内存。(输入又叫做读:read)
      • 输出流:从内存到硬盘。(输出又叫做写:write
    • 根据读写数据形式分为:
      • 字节流:一次读取一个字节。适合读取非文本数据。例如图片、声音、视频等文件。(当然字节流是万能的。什么都可以读和写。)
      • 字符流:一次读取一个字符。只适合读取普通文本。不适合读取二进制文件。因为字符流统一使用Unicode编码,可以避免出现编码混乱的问题。
        注意:Java的所有IO流中凡是以Stream结尾的都是字节流。凡是以Reader和Writer结尾的都是字符流。
    • 根据流在IO操作中的作用和实现方式来分类:
      • 节点流:节点流负责数据源和数据目的地的连接,是IO中最基本的组成部分。
      • 处理流:处理流对节点流进行装饰/包装,提供更多高级处理操作,方便用户进行数据处理。
  3. Java中已经将io流实现了,在java.io包下,可以直接使用。

在这里插入图片描述

8.2 IO流体系结构

  1. 如图是常用的IO流。实际上IO流远远不止这些。
  2. InputStream:字节输入流
  3. OutputStream:字节输出流
  4. Reader:字符输入流
  5. Writer:字符输出流
  6. 以上4个流都是抽象类,是所有IO流的四大头领!!!
  7. 所有的流都实现了Closeable接口,都有close()方法,流用完要关闭。
  8. 所有的输出流都实现了Flushable接口,都有flush()方法,flush方法的作用是,将缓存清空,全部写出。养成好习惯,以防数据丢失。

图8-2 IO体系结构

  • 四大头领:

    • InputStream
    • OutputStream
    • Reader
    • Writer
  • File相关的:

    • FileInputStream
    • FileOutputStream
    • FileReader
    • FileWriter
  • 缓冲流相关的:

    • BufferedInputStream
    • BufferedOutputStream
    • BufferedReader
    • BufferedWriter
  • 转换流相关的:

    • InputStreamReader
    • OutputStreamWriter
  • 打印流相关的:

    • PrintStream
    • PrintWriter
  • 对象相关的:

    • ObjectInputStream
    • ObjectOutputStream
  • 数据相关的:

    • DataInputStream
    • DataOutputStream
  • 字节数组相关的:

    • ByteArrayInputStream
    • ByteArrayOutputStream
  • 压缩和解压缩相关的:

    • GZIPInputStream
    • GZIPOutputStream
  • 线程相关的:

    • PipedInputStream
    • PipedOutputStream

8.3 FileInputStream

  1. 文件字节输入流,可以读取任何文件。
  2. 是一个万能流,在任何文件都能读。但还是建议读二进制文件。例如:图片,声音,视频等。
  3. 但是FileInputStream肯定也是可以读普通文本的。只不过一次读取一个字符。容易出现乱码问题。
  4. 常用构造方法
    FileInputStream(String name):创建一个文件字节输入流对象,参数是文件的路径
  5. 常用方法
    • int read();从文件读取一个字节(8个二进制位),返回值int类型,返回值读取到的字节本身,表示读入的一个字节的值,如果读不到任何数据返回-1
    • int read(byte[] b); 返回值表示读入的字节数,到流末尾时返回-1,参数表示读入的数据存放的位置。(一次读取多个字节,如果文件内容足够多,则一次最多读取b.length个字节。返回值是读取到字节总数。如果没有读取到任何数据,则返回 -1)
    • int read(byte[] b, int off, int len); 返回值表示读入的字节数,到流末尾时返回-1,参数分别表示读入的数据存放的位置,从哪个下标开始,最大长度多少。(读到数据后向byte数组中存放时,从off开始存放,最多读取len个字节。读取不到任何数据则返回 -1)
    • long skip(long n); 跳过n个字节
    • int available(); 返回流中剩余的估计字节数量。
    • void close() 关闭流。
  6. 使用FileInputStream读取的文件中有中文时,有可能读取到中文某个汉字的一半,在将byte[]数组转换为String时可能出现乱码问题,因此FileInputStream不太适合读取纯文本。

8.4 FileOutputStream

  1. 文件字节输出流,负责写的。
  2. 常用构造方法:
    • FileOutputStream(String name) 创建输出流,先将文件清空,再不断写入。(谨慎使用)
    • FileOutputStream(String name, boolean append) 创建输出流,在原文件最后面以追加形式不断写入。
      • 当append是true的时候,不会清空原文件的内容,在原文件的末尾追加写入。
      • 当append是false的时候,会清空原文件的内容,然后写入。
    • 注意:
      • append==true表示:不会清空原文件的内容,在原文件内容后面追加
      • append==false表示:清空原文件内容,在文件中写入。
  3. 常用方法:
    • write(int b) 写一个字节
    • void write(byte[] b); 将整个byte字节数组中所有数据全部写出
    • void write(byte[] b, int off, int len); 将byte字节数组的一部分写出
    • void close() 关闭流
    • void flush() 刷新
  4. 使用FileInputStream和FileOutputStream完成文件的复制。
public class FileInputOutputStreamCopy {
    public static void main(String[] args) {
        // 输入流
        FileInputStream in = null;
        // 输出流
        FileOutputStream out = null;
        try {
            in = new FileInputStream("/Users/xfanny/IdeaProjects/project/1.txt");
            out = new FileOutputStream("/Users/xfanny/IdeaProjects/project/2.txt");
            // 核心代码开始,其余都是大概框架
            // 一次至少拷贝1KB
            byte[] bytes = new byte[1024];
            int readCount = 0;
            while ((readCount = in.read(bytes)) != -1) {
                out.write(bytes, 0, readCount);
            }
            // 核心代码结束
            // 刷新
            out.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 关闭(分别try...catch...)
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
            if (out != null) {
                try {
                    out.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }
}    

Java7的新特性:try-with-resources资源自动关闭
凡是实现了AutoCloseable接口的流都可以使用try-with-resource。都会自动关闭。
try-with-resources语法格式:

try(声明流;声明流;声明流;声明流){ ...... }catch(Exception e){ ... }

8.5 FileReader

  1. 文件字符输入流。读。以字符的形式读文件。一次至少读取一个完整的字符。
  2. 常用的构造方法:
    • FileReader(String fileName)
  3. 常用的方法:
    • int read()
    • int read(char[] cbuf);
    • int read(char[] cbuf, int off, int len);
    • long skip(long n);
    • void close()

8.6 FileWriter

  1. 文件字符输出流
  2. 常用的构造方法:
    • FileWriter(String fileName)
    • FileWriter(String fileName, boolean append)
  3. 常用的方法:
    • void write(char[] cbuf)
    • void write(char[] cbuf, int off, int len);
    • void write(String str);
    • void write(String str, int off, int len);
    • void flush();
    • void close();
    • Writer append(CharSequence csq, int start, int end)
  4. 使用FileReader和FileWriter拷贝普通文本文件

8.7 缓冲流

  1. BufferedInputStream、BufferedOutputStream(适合读写非普通文本文件)
  2. BufferedReader、BufferedWriter(适合读写普通文本文件。)
  3. 缓冲流的读写速度快,原理是:在内存中准备了一个缓存。读的时候从缓存中读。写的时候将缓存中的数据一次写出。都是在减少和磁盘的交互次数。如何理解缓冲区?家里盖房子,有一堆砖头要搬在工地100米外,单字节的读取就好比你一个人每次搬一块砖头,从堆砖头的地方搬到工地,这样肯定效率低下。然而聪明的人类会用小推车,每次先搬砖头搬到小车上,再利用小推车运到工地上去,这样你再从小推车上取砖头是不是方便多了呀!这样效率就会大大提高,缓冲流就好比我们的小推车,给数据暂时提供一个可存放的空间。
  4. BufferedInputStream是缓冲流(处理流/包装流)。FileInputStream/FileOutputStream是节点流。
  5. BufferedInputStream对FileInputStream进行了功能增强。增强了一个缓冲区的功能。
  6. 关闭流只需要关闭最外层的处理流即可,通过源码就可以看到,当关闭处理流时,底层节点流也会关闭。
  7. 输出效率是如何提高的?在缓冲区中先将字符数据存储起来,当缓冲区达到一定大小或者需要刷新缓冲区时,再将数据一次性输出到目标设备。
  8. 输入效率是如何提高的? read()方法从缓冲区中读取数据。当缓冲区中的数据不足时,它会自动从底层输入流中读取一定大小的数据,并将数据存储到缓冲区中。大部分情况下,我们调用read()方法时,都是从缓冲区中读取,而不需要和硬盘交互。
  9. 可以编写拷贝的程序测试一下缓冲流的效率是否提高了!
  10. 缓冲流的特有方法(输入流):以下两个方法的作用是允许我们在读取数据流时回退到原来的位置(重复读取数据时用)
    • void mark(int readAheadLimit); 标记位置(在Java21版本中,参数无意义。低版本JDK中参数表示在标记处最多可以读取的字符数量,如果你读取的字符数超出的上限值,则调用reset()方法时出现IOException。)
    • void reset(); 重新回到上一次标记的位置
    • 这两个方法有先后顺序:先mark再reset,另外这两个方法不是在所有流中都能用。有些流中有这个方法,但是不能用
    • 这两个方法组合起来完成的任务是:某段内容重复读取。
public class BufferedInputStreamTest01 {
    public static void main(String[] args) {
        BufferedInputStream bis = null;
        try {
            // 创建节点流
            //FileInputStream in = new FileInputStream("file.txt");
            // 创建包装流
            //bis = new BufferedInputStream(in);
            // 组合起来写
            bis = new BufferedInputStream(new FileInputStream("file.txt"));

            // 读,和FileInputStream用法完全相同
            byte[] bytes = new byte[1024];
            int readCount = 0;
            while((readCount = bis.read(bytes)) != -1){
                System.out.print(new String(bytes, 0, readCount));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 包装流以及节点流,你只需要关闭最外层的那个包装流即可。节点流不需要手动关闭。
            if (bis != null) {
                try {
                    bis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
public class BufferedOutputStreamTest01 {
    public static void main(String[] args) {
        BufferedOutputStream bos = null;
        try {
            bos = new BufferedOutputStream(new FileOutputStream("file.txt"));
            bos.write("XFanny".getBytes());
            // 缓冲流需要手动刷新。
            bos.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (bos != null) {
                try {
                    // 只需要关闭最外层的包装流即可。
                    bos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

8.8 转换流

8.8.1 InputStreamReader(主要解决读的乱码问题)

  1. InputStreamReader为转换流,属于字符流。

  2. 作用是将文件中的字节转换为程序中的字符。转换过程是一个解码的过程。

  3. 常用的构造方法:

    • InputStreamReader(InputStream in, String charsetName) // 指定字符集
    • InputStreamReader(InputStream in) // 采用平台默认字符集
  4. 乱码是如何产生的?文件的字符集和构造方法上指定的字符集不一致。

  5. FileReader是InputStreamReader的子类。本质上以下代码是一样的:

    • Reader reader = new InputStreamReader(new FileInputStream(“file.txt”)); //采用平台默认字符集
    • Reader reader = new FileReader(“file.txt”); //采用平台默认字符集

    因此FileReader的出现简化了代码的编写。

  • 以下代码本质上也是一样的:
    • Reader reader = new InputStreamReader(new FileInputStream(“file.txt”), “GBK”);
    • Reader reader = new FileReader("e:/file1.txt", Charset.forName("GBK"));

在这里插入图片描述

public class InputStreamReaderDecodingTest {
    public static void main(String[] args) throws Exception{
        // 创建一个转换流对象(输入流)
        // 节点流
        //FileInputStream in = new FileInputStream("");
        // 包装流
        //InputStreamReader isr = new InputStreamReader(in);

        //InputStreamReader isr = new InputStreamReader(new FileInputStream("E:\\powernode\\02-JavaSE\\code\\file3.txt"), "GBK");

        // 以上代码太长了。在IO流的继承体系结构中,IO流又给InputStreamReader提供了一个子类:FileReader
        // 代码可以这样写了:
        //FileReader isr = new FileReader("E:\\powernode\\02-JavaSE\\code\\file3.txt", Charset.defaultCharset());
        FileReader isr = new FileReader("E:\\powernode\\02-JavaSE\\code\\file3.txt", Charset.forName("GBK"));

        // 开始读
        int readCount = 0;
        char[] chars = new char[1024];
        while((readCount = isr.read(chars)) != -1){
            System.out.print(new String(chars, 0, readCount));
        }
        // 关闭流
        isr.close();
    }
}

8.8.2 OutputStreamWriter(主要解决写的乱码问题)

  1. OutputStreamWriter是转换流,属于字符流。
  2. 作用是将程序中的字符转换为文件中的字节。这个过程是一个编码的过程。
  3. 常用构造方法:
    • OutputStreamWriter(OutputStream out, String charsetName) // 使用指定的字符集
    • OutputStreamWriter(OutputStream out) //采用平台默认字符集
  4. 乱码是如何产生的?文件的字符集与程序中构造方法上的字符集不一致。
  5. FileWriter是OutputStreamWriter的子类。以下代码本质上是一样的:
    • Writer writer = new OutputStreamWriter(new FileOutputStream(“file1.txt”)); // 采用平台默认字符集

    • Writer writer = new FileWriter(“file1.txt”); // 采用平台默认字符集
      因此FileWriter的出现,简化了代码。

    • 以下代码本质上也是一样的:

      • Writer writer = new OutputStreamWriter(new FileOutputStream(“file1.txt”), “GBK”);
      • Writer writer = new FileWriter(“file1.txt”, Charset.forName(“GBK”));

在这里插入图片描述

public class OutputStreamWriterEncodingTest {
    public static void main(String[] args) throws Exception{
        // 创建转换流对象OutputStreamWriter
        // 以下代码采用的是UTF-8的字符集进行编码。(采用平台默认的字符集)
        // 注意:以下代码中输出流以覆盖的方式输出/写。
        //OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\powernode\\02-JavaSE\\code\\file4.txt"));

        //OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\powernode\\02-JavaSE\\code\\file4.txt"), "GBK");

        //OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("E:\\powernode\\02-JavaSE\\code\\file4.txt", true), "GBK");

        /*OutputStreamWriter osw = new OutputStreamWriter(
                new FileOutputStream("E:\\powernode\\02-JavaSE\\code\\file4.txt", true));*/

        /*OutputStreamWriter osw = new OutputStreamWriter(
                new FileOutputStream("E:\\powernode\\02-JavaSE\\code\\file4.txt", true), "GBK");*/

        FileWriter osw = new FileWriter("E:\\powernode\\02-JavaSE\\code\\file4.txt", Charset.forName("UTF-8"), true);

        // 开始写
        osw.write("学习Java");
        osw.flush();;
        osw.close();
    }
}

8.9 数据流

DataOutputStream/DataInputStream:对象流(对象字节输入流)

  1. 这两个流都是包装流,读写数据专用的流。
  2. DataOutputStream直接将程序中的数据写入文件,不需要转码,效率高。程序中是什么样子,原封不动的写出去。写完后,文件是打不开的。即使打开也是乱码,文件中直接存储的是二进制。
  3. 使用DataOutputStream写的文件,只能使用DataInputStream去读取。并且读取的顺序需要和写入的顺序一致,这样才能保证数据恢复原样。
  4. 构造方法:
    • DataInputStream(InputStream in)
    • DataOutputStream(OutputStream out)
  5. 写的方法:
    • writeByte()、writeShort()、writeInt()、writeLong()、writeFloat()、writeDouble()、writeBoolean()、writeChar()、writeUTF(String)
  6. 读的方法:
    • readByte()、readShort()、readInt()、readLong()、readFloat()、readDouble()、readBoolean()、readChar()、readUTF()

8.10 对象流

ObjectOutputStream/ObjectInputStream:对象流(对象字节输出流)

  1. 通过这两个流,可以完成对象的序列化和反序列化。
  2. 序列化(Serial):将Java对象转换为字节序列。(为了方便在网络中传输),使用ObjectOutputStream序列化。
  3. 反序列化(DeSerial):将字节序列转换为Java对象。使用ObjectInputStream进行反序列化。
  4. 参与序列化和反序列化的java对象必须实现一个标志性接口java.io.Serializable可序列化接口,这个接口是一个标志接口,没有任何方法,只是起到一个标记的作用。
  5. 实现了Serializable接口的类,编译器会自动给该类添加序列化版本号的属性:serialVersionUID
  6. 在java中,是通过“类名 + 序列化版本号”来进行类的区分的。
  7. serialVersionUID实际上是一种安全机制。在反序列化的时候,JVM会去检查存储Java对象的文件中的class的序列化版本号是否和当前Java程序中的class的序列化版本号是否一致。如果一致则可以反序列化。如果不一致则报错。
  8. 如果一个类实现了Serializable接口,还是建议将序列化版本号固定死,建议显示的定义出来,原因是:类有可能在开发中升级(改动),升级后会重新编译,如果没有固定死,编译器会重新分配一个新的序列化版本号,导致之前序列化的对象无法反序列化。显示定义序列化版本号的语法:private static final long serialVersionUID = XXL;
  9. 为了保证显示定义的序列化版本号不会写错,建议使用 @java.io.Serial 注解进行标注。并且使用它还可以帮助我们随机生成序列化版本号。
  10. 不参与序列化的属性需要使用瞬时关键字修饰:transient

在这里插入图片描述

8.11 打印流

8.11.1 PrintStream

  1. 打印流(字节形式)
  2. 主要用在打印方面,提供便捷的打印方法和格式化输出。主要打印内容到文件或控制台。
  3. 常用方法:
    • print(Type x)
    • println(Type x)
  4. 便捷在哪里?
    • 直接输出各种数据类型
    • 自动刷新和自动换行(println方法)
    • 支持字符串转义
    • 自动编码(自动根据环境选择合适的编码方式)
  5. 格式化输出?调用printf方法。
    • %s 表示字符串
    • %d 表示整数
    • %f 表示小数(%.2f 这个格式就代表保留两位小数的数字。)
    • %c 表示字符

8.11.2 PrintWriter

  1. 打印流(字符形式)注意PrintWriter使用时需要手动调用flush()方法进行刷新。

  2. 比PrintStream多一个构造方法,PrintStream参数只能是OutputStream类型,但PrintWriter参数可以是OutputStream,也可以是Writer。

  3. 常用方法:

    • print(Type x)
    • println(Type x)

    同样,也可以支持格式化输出,调用printf方法。

8.12 标准输入流&标准输出流

8.12.1 标准输入流

  1. System.in获取到的InputStream就是一个标准输入流。
  2. 标准输入流是用来接收用户在控制台上的输入的。(普通的输入流,是获得文件或网络中的数据)
  3. 标准输入流不需要关闭。(它是一个系统级的全局的流,JVM负责最后的关闭。)
  4. 也可以使用BufferedReader对标准输入流进行包装。这样可以方便的接收用户在控制台上的输入。(这种方式太麻烦了,因此JDK中提供了更好用的Scanner。)
    • BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    • String s = br.readLine();
  5. 当然,你也可以修改输入流的方向(System.setIn())。让其指向文件。
/**
 * 标准输入流:System.in
 *      1. 标准输入流怎么获取?
 *          System.in
 *      2. 标准输入流是从哪个数据源读取数据的?
 *          控制台。
 *      3. 普通输入流是从哪个数据源读取数据的?
 *          文件或者网络或者其他.....
 *      4. 标准输入流是一个全局的输入流,不需要手动关闭。JVM退出的时候,JVM会负责关闭这个流。
 */
public class SystemInTest {
    public static void main(String[] args) throws Exception{
        // 获取标准输入流对象。(直接通过系统类System中的in属性来获取标准输入流对象。)
        InputStream in = System.in;
        // 开始读
        byte[] bytes = new byte[1024];
        int readCount = in.read(bytes);

        for (int i = 0; i < readCount; i++) {
            System.out.println(bytes[i]);
        }
    }
}

8.12.2 标准输出流

  1. System.out获取到的PrintStream就是一个标准输出流。
  2. 标准输出流是用来向控制台上输出的。(普通的输出流,是向文件和网络等输出的。)
  3. 标准输出流不需要关闭(它是一个系统级的全局的流,JVM负责最后的关闭。)也不需要手动刷新。
  4. 当然,你也可以修改输出流的方向(System.setOut())。让其指向文件。
/**
 * 标准输出流:System.out
 *      1. 标准输出流怎么获取?
 *          System.out
 *      2. 标准输出流是向哪里输出呢?
 *          控制台。
 *      3. 普通输出流是向哪里输出呢?
 *          文件或者网络或者其他.....
 *      4. 标准输出流是一个全局的输出流,不需要手动关闭。JVM退出的时候,JVM会负责关闭这个流。
 */
public class SystemOutTest {
    public static void main(String[] args) throws Exception {
        // 获取标准输出流,标准输出流默认会向控制台输出。
        PrintStream out = System.out;
        // 输出
        out.println("hello world");
        out.println("hello world");
        // 标准输出流也是可以改变输出方向的。
        System.setOut(new PrintStream("log"));
        System.out.println("zhangsan");
        System.out.println("lisi");

        // 获取系统当前时间
        Date now = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
        String str = sdf.format(now);
        System.out.println(str + ": SystemOutTest's main method invoked!");
    }
}

8.13 File类

File类

  1. File类不是IO流,和IO的四个头领没有关系。因此通过File是无法读写文件。
  2. File类是路径的抽象表示形式,这个路径可能是目录,也可能是文件。因此File代表了某个文件或某个目录。
  3. File类常用的构造方法:File(String pathname);
  4. File类的常用方法:
    • boolean createNewFile(); 当且仅当具有此名称的文件尚不存在时,原子性地创建一个由此抽象路径名命名的新空文件。
    • boolean delete();删除此抽象路径名表示的文件或目录。
    • boolean exists();测试此抽象路径名表示的文件或目录是否存在。
    • String getAbsolutePath();返回此抽象路径名的绝对路径名字符串。
    • String getName();返回此抽象路径名表示的文件或目录的名称。
    • String getParent();返回此抽象路径名的父目录的路径名字符串,如果此路径名没有命名父目录,则返回null。
    • boolean isAbsolute();测试此抽象路径名是否为绝对路径名
    • boolean isDirectory();测试此抽象路径名表示的文件是否是目录。
    • boolean isFile();测试此抽象路径名表示的文件是否为正常文件。
    • boolean isHidden();测试此抽象路径名命名的文件是否为隐藏文件。
    • long lastModified();返回此抽象路径名表示的文件上次修改的时间。
    • long length();返回此抽象路径名表示的文件的长度。
    • File[] listFiles(); 返回一个抽象路径名数组,表示此抽象路径名所表示的目录中的文件。
    • File[] listFiles(FilenameFilter filter);返回一个抽象路径名数组,表示此抽象路径名所表示的目录中满足指定筛选器的文件和目录。
    • boolean mkdir();创建以此抽象路径名命名的目录。
    • boolean mkdirs();创建由此抽象路径名命名的目录,包括任何必要但不存在的父目录。
    • boolean renameTo(File dest); 重命名此抽象路径名表示的文件
    • boolean setReadOnly();标记此抽象路径名命名的文件或目录,以便只允许读取操作。
    • boolean setWritable(boolean writable);一种方便的方法,用于设置所有者对此抽象路径名的写入权限。
  5. 编写程序要求可以完成目录的拷贝。

8.14 读取属性配置文件

Properties + IO

  1. xxx.properties文件称为属性配置文件。
  2. 属性配置文件可以配置一些简单的信息,例如连接数据库的信息通常配置到属性文件中。这样可以做到在不修改java代码的前提下,切换数据库。
  3. 属性配置文件的格式:
    key1=value1
    key2=value2
    key3=value3
    
    注意:使用 # 进行注释。key不能重复,key重复则value覆盖。key和value之间用等号分割。等号两边不要有空格。
  4. Java中如何读取属性配置文件?
  5. 当然,也可以使用Java中的工具类快速获取配置信息:ResourceBundle
    • 这种方式要求文件必须是xxx.properties
    • 属性配置文件必须放在类路径当中

jdbc.properties

#connect mysql database info
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://192.168.137.154:3306/powernode
user=admin
password=11111

LoadProperties

/**
 * 使用Properties集合类 + IO流来读取属性配置文件。
 * 将属性配置文件中的配置信息加载到内存中。
 */
public class LoadProperties {
    public static void main(String[] args) throws Exception {
        // 创建输入流对象
        //FileReader reader = new FileReader("chapter08/src/db.properties");
        String path = Thread.currentThread().getContextClassLoader().getResource("jdbc.properties").getPath();
        FileReader reader = new FileReader(path);
        // 创建一个Map集合(属性类对象)
        Properties pro = new Properties();
        // 加载:将jdbc.properties文件中的配置信息加载到Properties对象中。
        pro.load(reader);
        // 获取所有key
        /*Enumeration<?> names = pro.propertyNames();
        while (names.hasMoreElements()) {
            String name = (String)names.nextElement();
            String value = pro.getProperty(name);
            System.out.println(name + "=" + value);
        }*/
        // 通过key来获取value
        String driver = pro.getProperty("driver");
        String url = pro.getProperty("url");
        String user = pro.getProperty("user");
        String password = pro.getProperty("password");

        System.out.println(driver);
        System.out.println(url);
        System.out.println(user);
        System.out.println(password);
        // 关闭输入流
        reader.close();
    }
}

使用JDK中提供的资源绑定器来绑定属性配置文件

public class BundleProperties {
    public static void main(String[] args) {
        // 获取资源绑定器对象
        // 使用这个工具要求文件也必须是一个属性配置文件。xxx.properties
        ResourceBundle bundle = ResourceBundle.getBundle("com.powernode.javase.io.jdbc");
        //ResourceBundle bundle = ResourceBundle.getBundle("com/powernode/javase/io/jdbc");

        // 这个获取的是类的根路径下的jdbc.properties文件。
        //ResourceBundle bundle = ResourceBundle.getBundle("jdbc");
        // 这个代码的意思是从类的根路径下找db.properties文件。
        //ResourceBundle bundle = ResourceBundle.getBundle("db");

        // 以下两行都是错误的:资源找不到。
        //ResourceBundle bundle = ResourceBundle.getBundle("com.powernode.javase.io.db.properties");
        //ResourceBundle bundle = ResourceBundle.getBundle("com/powernode/javase/io/db.properties");
        
        // 通过key获取value
        String driver = bundle.getString("driver");
        String url = bundle.getString("url");
        String user = bundle.getString("user");
        String password = bundle.getString("password");

        System.out.println(driver);
        System.out.println(url);
        System.out.println(user);
        System.out.println(password);
    }
}

8.15 装饰器设计模式Decorator Pattern

8.15.1 基本概念

  1. 思考:如何扩展一个类的功能?继承确实也可以扩展对象的功能,但是接口下的实现类很多,每一个子类都需要提供一个子类。就需要编写大量的子类来重写父类的方法。会导致子类数量至少翻倍,会导致类爆炸问题。
  2. 装饰器设计模式是GoF23种设计模式之一,属于结构型设计模式。(结构型设计模式通常处理对象和类之间的关系,使程序员能够更好地组织代码并更好地利用现有代码。)
  3. IO流中使用了大量的装饰器设计模式
  4. 装饰器设计模式作用:装饰器模式可以做到在不修改原有代码的基础之上,完成功能扩展,符合OCP原则。并且避免了使用继承带来的类爆炸问题。
  5. 装饰器设计模式中涉及到的角色包括:
    1. 抽象的装饰者
    2. 具体的装饰者1、具体的装饰者 2被装饰者
    3. 装饰者和被装饰者的公共接口/公共抽象类

在这里插入图片描述

8.15.2 软件设计师中级内容

  1. 意图
    动态地给一个对象添加一些额外的职责。就增加功能而言,Decorator 模式比生成子类更加灵活。
    2. 结构
    装饰模式的结构如图 7-34 所示。
    在这里插入图片描述

    其中:

    • Component 定义一个对象接口,可以给这些对象动态地添加职责。
    • ConcreteComponent 定义一个对象,可以给这个对象添加一些职责。
    • Decorator 维持一个指向 Component 对象的指针,并定义一个与 Component 接口一致的接口。
    • ConcreteDecorator 向组件添加职责。
    1. 适用性
      Decorator 模式适用于:
      • 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
      • 处理那些可以撤销的职责。
      • 当不能采用生成子类的方式进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是,由于类定义被隐藏,或类定义不能用于生成子类。

8.15.3 代码实现

  • Bird类
    // 被装饰者
    public class Bird implements Flyable{
        @Override
        public void fly() {
            System.out.println("Bird fly!");
        }
    }
    
  • Cat类
    // 被装饰者
    public class Cat implements Flyable{
        @Override
        public void fly() {
            System.out.println("Cat fly!");
        }
    }
    
  • Flyable接口
    public interface Flyable {
        void fly();
    }
    
  • FlyableDecorator
    /**
     * 所有的装饰者应该有一个共同的父类。这个父类通常是一个抽象类。
     * 所有装饰者的头领。
     */
    public abstract class FlyableDecorator implements Flyable{
        private Flyable flyable;
    
        public FlyableDecorator(Flyable flyable) {
            this.flyable = flyable;
        }
        @Override
        public void fly() {
            flyable.fly();
        }
    }
    
  • TimerDecorator
    // 装饰者
    public class TimerDecorator extends FlyableDecorator{
    
       // 有一个被装饰者的引用。
       // 这个引用的类型最好是抽象的。不是具体的。
       // 因为Cat和Bird都实现了接口Flyable。
       // 因此这里的被装饰者引用,它的类型是 Flyable
       //private Flyable flyable;
    
       public TimerDecorator(Flyable flyable){
           super(flyable);
       }
    
    
       @Override
       public void fly() {
           // 这里可以添加代码(前增强)
           long begin = System.currentTimeMillis();
           super.fly();
           // 这里可以添加代码(后增强)
           long end = System.currentTimeMillis();
           System.out.println("耗时"+(end - begin)+"毫秒");
       }
    }
    
  • LogDecorator
    // 装饰者
    public class LogDecorator extends FlyableDecorator{
    
        public LogDecorator(Flyable flyable) {
            super(flyable);
        }
    
        @Override
        public void fly() {
            Date now = new Date();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
            System.out.println(sdf.format(now) + ": 起飞前的准备");
    
            super.fly();
    
            now = new Date();
            System.out.println(sdf.format(now) + ": 安全降落");
        }
    }
    
  • Test
    /**
     * 1. 装饰器设计模式的主要目标:在松耦合的前提下,能够完成功能的扩展。
     * 2. 在装饰器设计模式中有两个非常重要的角色:装饰者,被装饰者。
     * 3. 装饰器设计模式当中要求:装饰者 与 被装饰者 应实现同一个接口/同一些接口,继承同一个抽象类....
     * 4. 为什么装饰者 与 被装饰者 要实现同一个接口呢?
     *      因为实现了同一个接口之后,对于客户端程序来说,使用装饰者的时候就向在使用被装饰者一样。
     * 5. 装饰者含有被装饰者的引用。(A has a B。尽量使用has a【耦合度低一些】。不要使用is a。)
     */
    public class Test {
        public static void main(String[] args) {
            //Flyable flyable1 = new Cat();
            Flyable flyable1 = new TimerDecorator(new Cat());
            //Flyable flyable1 = new LogDecorator(new Cat());
            //BufferedReader br = new BufferedReader(new FileReader(""));
            flyable1.fly();
    
            //Flyable flyable2 = new Bird();
            //Flyable flyable2 = new TimerDecorator(new Bird());
            Flyable flyable2 = new LogDecorator(new Bird());
            flyable2.fly();
        }
    }
    

在这里插入图片描述

8.16 压缩和解压缩流

8.16.1 GZIPOutputStream(压缩)

  1. 使用GZIPOutputStream可以将文件制作为压缩文件,压缩文件的格式为 .gz 格式。
  2. 核心代码:
    • FileInputStream fis = new FileInputStream("d:/test.txt"); // 被压缩的文件:test.txt
    • GZIPOutputStream gzos = new GZIPOutputStream(new FileOutputStream("d:/test.txt.gz")) // 压缩后的文件
    • 接下来就是边读边写:
    int length;
    while ((length = fis.read(buffer)) > 0) {
        gzos.write(buffer, 0, length);
    }
    
    • gzos.finish(); // 在压缩完所有数据之后调用finish()方法,以确保所有未压缩的数据都被刷新到输出流中,并生成必要的 Gzip 结束标记,标志着压缩数据的结束。
  3. 注意(补充):实际上所有的输出流中,只有带有缓冲区的流才需要手动刷新,节点流是不需要手动刷新的,节点流在关闭的时候会自动刷新。

8.16.2 GZIPInputStream(解压缩)

  1. 使用GZIPInputStream可以将 .gz 格式的压缩文件解压。
  2. 核心代码:
    GZIPInputStream gzip = new GZIPInputStream(new FileInputStream("d:/test.txt.gz"));
    FileOutputStream out = new FileOutputStream("d:/test.txt");
    byte[] bytes = new byte[1024];
    int readCount = 0;
    while((readCount = gzip.read(bytes)) != -1){
        out.write(bytes, 0, readCount);
    }
    

8.17 字节数组流

字节数组流(内存流)

  1. ByteArrayInputStream和ByteArrayOutputStream都是内存操作流,不需要打开和关闭文件等操作。这些流是非常常用的,可以将它们看作开发中的常用工具,能够方便地读写字节数组、图像数据等内存中的数据。
  2. ByteArrayInputStream和ByteArrayOutputStream都是节点流。
  3. ByteArrayOutputStream,将数据写入到内存中的字节数组当中。
  4. ByteArrayInputStream,读取内存中某个字节数组中的数据。

在这里插入图片描述

8.18 对象克隆

对象的深克隆

  1. 除了我们之前所讲的深克隆方式(之前的深克隆是重写clone()方法)。使用字节数组流也可以完成对象的深克隆。
  2. 原理是:将要克隆的Java对象写到内存中的字节数组中,再从内存中的字节数组中读取对象,读取到的对象就是一个深克隆。
  3. 目前为止,对象拷贝方式:
    1. 调用Object的clone方法,默认是浅克隆,需要深克隆的话,就需要重写clone方法。
    2. 可以通过序列化和反序列化完成对象的克隆。
    3. 也可通过ByteArrayInputStream和ByteArrayOutputStream完成深克隆。
  • Address
    public class Address implements Serializable {
    
        @Serial
        private static final long serialVersionUID = -4947432823777553978L;
        private String city;
        private String street;
        public Address() {
        }
        public Address(String city, String street) {
            this.city = city;
            this.street = street;
        }
        public String getCity() {
            return city;
        }
        public void setCity(String city) {
            this.city = city;
        }
        public String getStreet() {
            return street;
        }
        public void setStreet(String street) {
            this.street = street;
        }
        @Override
        public String toString() {
            return "Address{" +
                    "city='" + city + '\'' +
                    ", street='" + street + '\'' +
                    '}';
        }
    }
    
  • User
    public class User implements Serializable {
        @Serial
        private static final long serialVersionUID = -4947432823777553977L;
        private String name;
        private int age;
        private Address addr;
        public User() {
        }
        public User(String name, int age, Address addr) {
            this.name = name;
            this.age = age;
            this.addr = addr;
        }
        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 Address getAddr() {
            return addr;
        }
        public void setAddr(Address addr) {
            this.addr = addr;
        }
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", addr=" + addr +
                    '}';
        }
    }
    
  • DeepCloneTest
    /**
     * 使用ByteArrayOutputStream和ByteArrayInputStream直接复制的对象就是一个深克隆。
     */
    public class DeepCloneTest {
        public static void main(String[] args) throws Exception{
            // 准备对象
            Address addr = new Address("北京", "朝阳");
            User user = new User("zhangsan", 20, addr);
    
            // 将Java对象写到一个byte数组中。
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(baos);
    
            oos.writeObject(user);
            oos.flush();
    
            // 从byte数组中读取数据恢复java对象
            ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
            ObjectInputStream ois = new ObjectInputStream(bais);
    
            // 这就是哪个经过深拷贝之后的新对象
            User user2 = (User) ois.readObject();
    
            user2.getAddr().setCity("南京");
            System.out.println(user);
            System.out.println(user2);
        }
    }
    

  • 每章一句:“ 学习是一场旅行,你通过这样的旅行,可以了解全新的思想,不断地拓展自己的眼界。”
  • 恭喜你已阅读完第八章!点个赞证明你已经挑战成功,进入第九章关卡《多线程》吧【更新中……】!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值