十八、IO流(完结)

十八、IO流


18.1 File 类


18.1.1 File 类介绍

  • java.io.File  类是 **文件 **和 目录路径名的抽象表示,主要用于文件和目录的创建、查找和删除等操作
  • 可以通过 File 封装成对象 File ,封装的对象仅仅是一个路径名。它可以是存在的,也可以是不存在的。

18.1.2 File 类的构造方法

方法名 说明
File(String pathname) 通过将给定的路径名字符串转换为抽象路径名来创建新的 File实例
File(String parent, String child) 从父路径名字符串和子路径名字符串创建新的 File实例
File(File parent, String child) 从父抽象路径名和子路径名字符串创建新的 File实例
import java.io.File;

/**
 * @author Carl Zhang
 * @description File类的构造方法
 * @date 2022/1/2 21:36
 */
public class FileConstructor {
    public static void main(String[] args) {
        //File(String pathname)
        //通过将给定的路径名字符串转换为抽象路径名来创建新的 File 实例
        File file = new File("D:\\Program Files\\a.txt");
        //保存的路径 D:\Program Files\a.txt
        System.out.println(file);

        //File(String parent, String child)
        //从父路径名字符串和子路径名字符串创建新的 File实例
        File file1 = new File("D:\\Program Files", "a.txt");
        //D:\Program Files\a.txt
        System.out.println(file1);

        //File(File parent, String child)
        //从父抽象路径名和子路径名字符串创建新的 File实例
        File file2 = new File(new File("D:\\Program Files"), "a.txt");
        //D:\Program Files\a.txt
        System.out.println(file2);
    }
}

18.1.3 相对路径和绝对路径

  • 相对路径:相对当前项目下的路径

image.png

  • 绝对路径:从盘符开始

image.png

18.2 File类的相关方法


18.2.1 创建方法

方法名 说明
public boolean createNewFile() 创建一个新的空的文件
public boolean mkdir() 创建一个单级文件夹,父路径不存在就创建失败
public boolean mkdirs() ! 创建一个多级文件夹,父路径不存在就创建父路径
import java.io.File;
import java.io.IOException;

/**
 * @author Carl Zhang
 * @description File类的创建功能
 * @date 2022/1/2 21:54
 *
 */
public class FileCreatMethod {
    public static void main(String[] args) throws IOException {
        File file1 = new File("D:\\TestFile.txt");
        //1 public boolean createNewFile()    : 创建一个新的空的文件,已存在返回false
        System.out.println(file1.createNewFile());

        //2 public boolean mkdir()   : 创建一个单级文件夹
        File file2 = new File("D:\\TestFile");
        System.out.println(file2.mkdir());
        File file3 = new File("D:\\Test\\TestFile");
        //false 只认最后的路径名,前面的D:\Test\ 不存在,发回false
        System.out.println(file3.mkdir());

        //3 public boolean mkdirs() : 创建一个多级文件夹
        // 父路径不存在,就会把父路径的文件夹一起创建
        File file4 = new File("D:\\Test\\TestFile");
        System.out.println(file4.mkdirs());

        //true,创建的是文件夹名字为TestFile.txt的文件夹
        File file5 = new File("D:\\Test\\TestFile.txt");
        System.out.println(file5.mkdirs());
    }
}

18.2.2 删除方法

方法名 说明
public boolean delete() 删除由此抽象路径名表示的文件或目录
  • delete 方法直接删除不走回收站。
  • 如果删除的是一个文件,直接删除。
  • 如果删除的是一个文件夹,需要先删除文件夹中的内容,最后才能删除文件夹
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

/**
 * @author Carl Zhang
 * @description File删除文件和文件夹方法
 * @date 2022/1/2 22:14
 */
public class FileDeleteMethod {
    public static void main(String[] args) throws IOException {
        File testFile = new File("TestFile");
        //System.out.println(testFile.mkdirs());

        File file = new File("TestFile\\aaa.txt");
        //System.out.println(file.createNewFile());

        //删除文件
        //System.out.println(file.delete());

        //删除空文件夹
        System.out.println(testFile.delete());

        //删除非空文件夹
        File file1 = new File("TestFile\\aaa.txt");
        file.mkdirs();
        //false
        System.out.println(testFile.delete());
    }
}

18.2.2 File类判断和获取

方法名 说明
public boolean isDirectory() 测试此抽象路径名表示的File是否为目录
public boolean isFile() 测试此抽象路径名表示的File是否为文件
public boolean exists() 测试此抽象路径名表示的File是否存在
public String getAbsolutePath() 返回此抽象路径名的绝对路径名字符串
public String getPath() 将此抽象路径名转换为路径名字符串
public String getName() 返回由此抽象路径名表示的文件或目录的名称
package com.heima.file;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;

/**
 * @author Carl Zhang
 * @description File类判断和获取功能
 * @date 2022/1/2 22:30
 */
public class FileGetMethod {
    public static void main(String[] args) throws IOException {
        File file1 = new File("TestFile");
        File file2 = new File("TestFile\\aaa.txt");
        file2.createNewFile();

        //public boolean isDirectory()   测试此抽象路径名表示的File是否为目录
        System.out.println(file1.isDirectory()); //true
        System.out.println(file2.isDirectory()); //false

        //public boolean isFile()    测试此抽象路径名表示的File是否为文件
        System.out.println(file1.isFile()); //false
        System.out.println(file2.isFile()); //true

        //public boolean exists()    测试此抽象路径名表示的File是否存在
        File testFile123 = new File("TestFile123");
        System.out.println(testFile123.exists()); //false
        System.out.println(file1.exists()); //true
        System.out.println(file2.exists()); //true

        //public String getAbsolutePath()    返回此抽象路径名的绝对路径名字符串
        System.out.println(file1.getAbsolutePath()); //D:\IDEA_JAVA_Project\chapter18\TestFile
        System.out.println(file2.getAbsolutePath()); //D:\IDEA_JAVA_Project\chapter18\TestFile\aaa.txt


        //public String getPath()    将此抽象路径名转换为路径名字符串
        System.out.println(file1.getPath()); //TestFile
        System.out.println(file2.getPath()); //TestFile\aaa.txt

        //public String getName()    返回由此抽象路径名表示的文件或目录的名称
        System.out.println(file1.getName()); //TestFile
        System.out.println(file2.getName()); //aaa.txt
    }
}

18.2.3 File类高级获取方法

方法名 说明
public File[] listFiles() 返回此抽象路径名表示的目录中的文件和目录的File对象数组

listFiles 方法注意事项

  • 当调用者不存在时,返回 null
  • 当调用者是一个文件时,返回 null
  • 当调用者是一个空文件夹时,返回一个长度为0的数组
  • 当调用者是一个有内容的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回
  • 当调用者是一个有隐藏文件的文件夹时,将里面所有文件和文件夹的路径放在 File 数组中返回,包含隐藏内容
import java.io.File;

/**
 * @author Carl Zhang
 * @description File类高级获取功能
 * @date 2022/1/2 22:52
 */
public class FileGetMethod02 {
    public static void main(String[] args) {
        //File chapter18 = new File("..\\");
        File chapter18 = new File("..\\");
        //File类高级获取功能
        //public File[] listFiles()  返回此抽象路径名表示的目录中的文件和目录的File对象数组
        File[] files = chapter18.listFiles();
        for (File file : files) {
            System.out.println(file.getName());
        }

        //listFiles方法注意事项:
        //1 当调用者不存在时,返回null,抛异常
        //2 当调用者是一个文件时,返回null,抛异常
        //3 当调用者是一个空文件夹时,返回一个长度为0的数组
        //4 当调用者是一个有内容的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回
        //5 当调用者是一个有隐藏文件的文件夹时,将里面所有文件和文件夹的路径放在File数组中返回,包含隐藏内容
    }
}

18.2.4 练习 1 - 统计一个文件夹中每种文件的个数

package com.heima.file;

import java.io.File;
import java.util.HashMap;

/**
 * @author Carl Zhang
 * @description
 * @date 2022/1/3 13:44
 * 练习二 :统计一个文件夹中每种文件的个数并打印。
 * <p>
 * 打印格式如下:
 * txt:3个
 * doc:4个
 * jpg:6个
 */
public class Exercise02 {
    public static void main(String[] args) {
        //1. 获取文件夹下的所有文件File对象
        //2. 获取每个文件的后缀 - 截取
        //3. 以键值对的形式保存在map集合里 - {后缀, 数量}
        //4. 遍历files数组,拿到一个后缀就将存一个在map集合里,数量为1
        //   如果后缀存在,就在原来的数量上+1

        File file = new File("D:\\医佳\\项目\\医佳云平台\\测试\\测试数据");
        //1. 获取文件夹下的所有文件File对象
        File[] files = file.listFiles();

        //用来存放统计的后缀和数量
        HashMap<String, Integer> hashMap = new HashMap<>();

        for (File file1 : files) {
            //获取每个文件的后缀 - 截取 - 因为.是正则,所以加转义字符
            String[] split = file1.getName().split("\\.");
            //如果是a.b.c.txt名字,就是{a, b, c, txt} ,后缀是最后一个元素
            String type = split[split.length - 1];
            //如果后缀存在,就在原来的数量上+1
            if (hashMap.containsKey(type)) {
                hashMap.put(type, hashMap.get(type) + 1);
                continue;
            }
            //后缀不存在,就添加进集合
            hashMap.put(type, 1);
        }

        //打印
        System.out.println(hashMap);

    }
}

18.2.5 练习2 - 递归删除文件夹

import java.io.File;

/**
 * @author Carl Zhang
 * @description 使用递归删除计算机中的指定文件夹
 * @date 2022/1/3 14:21
 */
public class DeleteFiles {
    public static void main(String[] args) {
        //分析:如果文件夹下不为空,就先删除文件夹下的文件 再删除该文件夹
        //1. 退出条件:空文件夹或文件,直接删除 - 调用file.delete(),return
        //2. 非空文件夹:先删除文件夹下的所有文件,再删除该文件夹 -- 获取目录 - 循环调用方法
        File file = new File("D:\\测试用文件夹\\TestFile02");
        deleteFiles(file);
    }

    /**
     * 传入file,删除该路径下的所有文件
     * @param file 要删除的文件/文件夹
     * @return 删除结果
     */
    public static void deleteFiles(File file) {
        //是文件或空文件夹就直接删除,返回
        if (file.delete()) {
            return;
        }

        //否则就是非空文件夹,先删除里面的文件
        File[] files = file.listFiles();
        for (File file1 : files) {
            deleteFiles(file1);
        }
        //再删除当前文件夹
        file.delete();
    }
}

18.3 IO流介绍


18.3.1 学IO流的原因

以前通过变量,数组,或者集合存储数据

问题

  • 都是不能永久化存储 , 因为数据都是存储在内存中
  • 只要代码运行结束,所有数据都会丢失

引出使用 IO

特点

  • 将数据写到文件中,实现数据永久化存储
  • 把文件中的数据读取到内存中( Java 程序)

18.3.2 IO 流介绍

  • I  表示 intput  ,是数据从硬盘进内存的过程,称之为读。
  • O 表示 output,是数据从内存到硬盘的过程。称之为写
  • IO 的数据传输,可以看做是一种数据的流动,按照流动的方向,以内存为参照物,进行读写操作
    • 简单来说:内存在读,内存在写

18.3.3 IO 流分类

分类:

  • 按照流向区分
    • 输入流 : 用来读数据
    • 输出流 : 用来写数据
  • 按照类型区分
    • 字节流
    • 字符流

注意:

  • 字节流可以操作任意文件
  • 字符流只能操作纯文本文件
    • windows 记事本打开能读的懂,那么这样的文件就是纯文本文件。

18.4 字节输出流


18.4.1 字节输出流介绍

FileOutputStream 类介绍

  • OutputStream 输出流有很多子类,我们从最简单的一个子类开始。
  • java.io.FileOutputStream 类是文件输出流,用于将数据写出到文件

构造方法 :

  • public FileOutputStream(File file):创建文件输出流以写入由指定的 File对象表示的文件。
  • public FileOutputStream(String name): 创建文件输出流以指定的名称写入文件。

注意:

  • 指定文件如果不存在,会自动创建这个文件
  • 指定文件如果存在,会先清空文件的内容
package com.heima.outputstream;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * @author Carl Zhang
 * @description FileOutPutStream构造方法演示
 * @date 2022/1/3 17:34
 */
public class OutputStream01 {
    public static void main(String[] args) throws IOException {
        //- public FileOutputStream(File file):创建文件输出流以写入由指定的 File对象表示的文件。
        //1. 指定文件如果不存在,会自动创建这个文件
        //2. 指定文件如果存在,会先清空文件的内容
        File file = new File("aaa.txt");
        FileOutputStream fileOutputStream1 = new FileOutputStream(file);

        //- public FileOutputStream(String name): 创建文件输出流以指定的名称写入文件。
        // 底层也是 this(name != null ? new File(name) : null, false) 创建了file对象
        FileOutputStream fileOutputStream2 = new FileOutputStream("aaa.txt");
    }
}

18.4.2 字节输出流写数据步骤

  1. 创建字节输出流对象。
  2. 写数据
    • 写出的整数,实际写出的是整数在码表上对应的字母。
  3. 释放资源
    • 每次使用完流必须要释放资源。
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * @author Carl Zhang
 * @description 字节输出流写数据快速入门
 * @date 2022/1/3 17:46
 */
public class OutputStream02 {
    public static void main(String[] args) throws IOException {
        //- 创建字节输出流对象。
        FileOutputStream fileOutputStream = new FileOutputStream("aaa.txt");

        //- 写数据
        // 写入的是一个字节 99 ,显示的是根据ASCII表翻译后的 c, 实际存储的还是99
        fileOutputStream.write(99);

        //- 释放资源 避免占用对应file文件
        // close() 关闭此文件输出流并释放与此流有关的所有系统资源。此文件输出流不能再用于写入字节
        fileOutputStream.close();
    }
}

18.4.3 字节输出流写入的方法

方法名 说明
void write(int b) 一次写一个字节数据
void write(byte[] b) 一次写一个字节数组数据
void write(byte[] b, int off, int len) 一次写一个字节数组的部分数据
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * @author Carl Zhang
 * @description 字节输出流写数据的方法
 * @date 2022/1/3 17:59
 */
public class OutputStream03 {
    public static void main(String[] args) throws IOException {
        //获取字节流对象
        FileOutputStream fileOutputStream = new FileOutputStream("aaa.txt");

        //- 1 void write(int b)    一次写一个字节数据
        fileOutputStream.write(99);
        fileOutputStream.write('a'); // 'a' -> 98 -> a

        //- 2 void write(byte[] b)    一次写一个字节数组数据
        byte[] b = {98, 99, 100};
        fileOutputStream.write(b); //bcd

        //- 3 void write(byte[] b, int off, int len)  一次写一个字节数组的部分数据
        // off - 起始下标。 含头不含尾
        fileOutputStream.write(b, 0, 2); //bc

        //问题:如何一次写入一段字符串
        //通过 getBytes()方法获取字节数组 间接输出
        fileOutputStream.write("hello,world".getBytes());

        //关闭流
        fileOutputStream.close();
    }
}

18.4.4 字节输出流追加写入和换行

  • 字节流写数据如何实现换行呢?写完数据后,加换行符
    • windows  : \r\n
    • linux  : \n
    • mac  : \r
  • 字节流写数据如何实现追加写入呢?
    • 通过构造方法 : public FileOutputStream(String name,boolean append)
    • 创建文件输出流以指定的名称写入文件。如果第二个参数为 true  ,不会清空文件里面的内容
package com.heima.outputstream;

import java.io.FileOutputStream;
import java.io.IOException;

/**
 * @author Carl Zhang
 * @description 字节流写数据的换行和追加写入
 * @date 2022/1/3 18:15
 */
public class OutputStream04 {
    public static void main(String[] args) throws IOException {
        FileOutputStream fileOutputStream = new FileOutputStream("aaa.txt");
        //1 字节流写数据如何实现换行呢?
        //    写完数据后,加换行符
        //    windows : \r\n
        //    linux : \n
        //    mac : \r
        fileOutputStream.write(98);
        //不能直接写入字符串,要通过getBytes()方法
        fileOutputStream.write("\r\n".getBytes());
        fileOutputStream.write(99);
        fileOutputStream.write("\r\n".getBytes());
        fileOutputStream.write(100);

        //2 字节流写数据如何实现追加写入呢?
        //    通过构造方法 : public FileOutputStream(String name,boolean append)
        //    创建文件输出流以指定的名称写入文件。如果第二个参数为true ,不会清空文件里面的内容
        FileOutputStream fileOutputStream2 = new FileOutputStream(
                "bbb.txt", true);
        //fileOutputStream2.write("hello,".getBytes());
        fileOutputStream2.write("world!".getBytes()); //hello,world!
    }
}

18.5 字节输入流


18.5.1 字节输入流介绍

字节输入流类

  • InputStream 类 : 字节输入流最顶层的类 , 抽象类
    • FileInputStream 类 : FileInputStream extends InputStream

构造方法

  • public FileInputStream(File file) :  从 file 类型的路径中读取数据
  • public FileInputStream(String name)  : 从字符串路径中读取数据

步骤

  1. 创建输入流对象
    1. 如果文件不存在,就直接报错。
  2. 读数据
    1. 读出来的是文件中数据的码表值。 a -> 97
  3. 释放资源
    1. 每次使用完流必须要释放资源。

18.5.2 一次读取一个字节案例

package com.heima.inputstream;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

/**
 * @author Carl Zhang
 * @description 一次读取一个字节
 * @date 2022/1/3 20:36
 */
public class InputStream01 {
    public static void main(String[] args) throws IOException {
        //1 创建输入流对象
        FileInputStream fileInputStream = new FileInputStream("bbb.txt");

        //2 读数据
        // 返回的是一个int类型的字节
        // 如果想看字符, 需要强转
        int read = fileInputStream.read();
        System.out.println((char) read);

        //3 释放资源
        fileInputStream.close();
    }
}

18.5.3 一次读取多个字节案例

import java.io.FileInputStream;
import java.io.IOException;

/**
 * @author Carl Zhang
 * @description 一次读取多个字节
 * @date 2022/1/3 20:36
 */
public class InputStream01 {
    public static void main(String[] args) throws IOException {
        //第三部分 : 字节输入流步骤
        //1 创建输入流对象
        //java.io.FileNotFoundException: bbb.txt (系统找不到指定的文件。)
        //路径指向的文件不存在,会抛异常
        FileInputStream fileInputStream = new FileInputStream("bbb.txt");

        //2 读数据
        // 接收读取的数据
        int read;
		// read()方法每次执行会指向后一个字节 - 类比iterator.next()方法
        // 如果读完了就会返回 -1
        while ((read = fileInputStream.read()) != -1) {
            System.out.print((char) read);
        }
        
        //3 释放资源
        fileInputStream.close();
    }
}

18.5.4 文件的拷贝案例 - 拷贝图片

import java.io.*;

/**
 * @author Carl Zhang
 * @description 拷贝图片
 * @date 2022/1/3 21:05
 */
public class CopyPicture {
    public static void main(String[] args) throws IOException {
        //思路:读取一个文件的字节,写一个字节
        //1. 根据要拷贝的图片路径创建字符输入流
        FileInputStream fileInputStream = new FileInputStream("" +
                "D:\\医佳\\项目\\医佳云平台\\测试\\测试数据\\公告测试图片1.jpg");
        //2. 根据要存放的图片的路径创建字符输出流
        FileOutputStream fileOutputStream = new FileOutputStream("公告测试图片.jpg");

        //3. 循环读取图片的字节,每读一个,写一个
        //保存读取的字节
        int read;
        while ((read = fileInputStream.read()) != -1) {
            fileOutputStream.write(read);
        }

        //4. 按照先后顺序释放资源
        fileInputStream.close();
        fileOutputStream.close();
    }
}

18.5.5 JDK7版本的资源释放优化

JDK7 以前释放资源的方式:手动释放资源,代码过于复杂

package com.itheima.inputstream_demo;


import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/*
    需求 : 对上一个赋值图片的代码进行使用捕获方式处理
 */
public class FileInputStreamDemo4 {
    public static void main(String[] args) {
        FileInputStream fis = null ;
        FileOutputStream fos = null;
        try {
            // 创建字节输入流对象
            fis = new FileInputStream("D:\\传智播客\\安装包\\好看的图片\\liqin.jpg");

            // 创建字节输出流
            fos = new FileOutputStream("day11_demo\\copy.jpg");

            // 一次读写一个字节
            int by;
            while ((by = fis.read()) != -1) {
                fos.write(by);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 释放资源
            if(fis != null){
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            // 释放资源
            if(fos != null){
                try {
                    fos.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

JDK7 版本优化处理方式 : 自动释放资源

  • JDK7 优化后可以使用 try-with-resource  语句 , 该语句确保了每个资源在语句结束时自动关闭。
    简单理解 : 使用此语句,会自动释放资源 , 不需要自己在写 finally 代码块了

  • 格式

try (创建流对象语句1 ; 创建流对象语句2 ...) {
        // 读写数据
} catch (IOException e) {
    处理异常的代码...
}
  • 案例:对上一个赋值图片的代码进行使用捕获方式处理
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * @author Carl Zhang
 * @description 对上一个赋值图片的代码进行使用捕获方式处理
 * @date 2022/1/3 21:26
 */
public class CloseResource {
    public static void main(String[] args) throws IOException {
        try (
                //将创建流对象的语句写自try()里
                FileInputStream fileInputStream = new FileInputStream("" +
                        "D:\\医佳\\项目\\医佳云平台\\测试\\测试数据\\公告测试图片1.jpg");
                FileOutputStream fileOutputStream = new FileOutputStream("公告测试图片.jpg")
        ) {
            //循环读取图片的字节,每读一个,写一个
            //保存读取的字节
            int read;
            while ((read = fileInputStream.read()) != -1) {
                fileOutputStream.write(read);
            }

            //释放资源 , 发现已经灰色 , 提示多余的代码 , 所以使用 try-with-resource 方式会自动关流
            //fileInputStream.close();
            //fileOutputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

18.5.6 一次读一个字节数组案例

问题:如果操作的文件过大,原来一次读取一个字节的方式速度会很慢

解决:为了解决速度问题,把字节流的数据读取字节数组中,提高读取效率。

方法:

  • 一次读一个字节数组的方法:
    • public int read(byte[] b) :从输入流读取最多 b.length 个字节的数据返回的是真实读到的数据个数
    • 返回的是真实读到的数据个数
  • String 构造方法:
    • public String(byte bytes[], int startIndex, int length) ,返回字节数组中指定长度的字节的字符串对象

案例1:

package com.heima.inputstream;


import java.io.FileInputStream;
import java.io.IOException;

/**
 * @author Carl Zhang
 * @description 字节输入流一次读一个字节数组案例
 * @date 2022/1/3 22:01
 */
public class ReadByteArray {
    public static void main(String[] args) {
        //创建一个字节输入流对象
        //使用 try-with-resource 方式自动关闭流
        try (FileInputStream fileInputStream = new FileInputStream("bbb.txt")) {
            //创建数组保存读取的数据
            byte[] read = new byte[3];

            //从bbb.txt里读取read数组个字节
            //len是每次真实读取到的字节个数
            int len = fileInputStream.read(read);

            //打印读取到的字节数
            System.out.println(len); //3
            //通过构造方法传入字节数组,获取对应字符串
            System.out.println(new String(read)); //abc

            //第二次读取
            //int len2 = fileInputStream.read(read);
            //System.out.println(len2); //2
            //System.out.println(new String(read)); //dec , 因为只读取了两个,所以只把数组里的前两个字节替换了

            //改进
            //int len2 = fileInputStream.read(read);
            //System.out.println(len2); //2
            //public String(byte bytes[], int startIndex, int length) ,只把读取到的两个数转换成字符串
            //System.out.println(new String(read, 0, len2));

            //通过循环获取多个字节
            //一般是1024的倍数
            byte[] bytes = new byte[1024];
            //保存每次读取到的字节个数
            int leng;
            //当读取到的字节个数为-1 表示没有内容了
            while ((leng = fileInputStream.read(bytes)) != -1) {
                //将读取到的字节转换成字符串打印
                System.out.println(new String(bytes, 0, leng));
            }
            
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

案例2:对复制图片的代码进行使用一次读写一个字节数组的方式进行改进

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 * @author Carl Zhang
 * @description 对复制图片的代码进行使用一次读写一个字节数组的方式进行改进
 * @date 2022/1/3 22:37
 */
public class CopyPicture02 {
    public static void main(String[] args) {
        try (
                //创建要拷贝的图片的字节输入流对象
                FileInputStream fileInputStream = new FileInputStream("" +
                        "D:\\医佳\\项目\\医佳云平台\\测试\\测试数据\\公告测试图片1.jpg");
                //创建图片要存放的路径的字节输入流对象
                FileOutputStream fileOutputStream = new FileOutputStream(
                        "公告测试图片.jpg")
        ) {
            //读取字节数组然后输出
            byte[] bytes = new byte[1024];
            int len;
            while ((len = fileInputStream.read(bytes)) != -1) {
                //将每次读取到的真实个数字节输出
                fileOutputStream.write(bytes, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

18.6 字节缓冲流


18.6.1  字节缓冲流概述

  • 字节缓冲流:
    • BufferOutputStream :缓冲输出流
      • image.png
    • BufferedInputStream :缓冲输入流
      • image.png
  • 构造方法:
    • 字节缓冲输出流:BufferedOutputStream(OutputStream out)
    • 字节缓冲输入流:BufferedInputStream(InputStream in)
  • 为什么构造方法需要的是字节流,而不是具体的文件或者路径呢?
    • 字节缓冲流仅仅提供缓冲区,不具备读写功能 , 而真正的读写数据还得依靠基本的字节流对象进行操作
import java.io.*;

/**
 * @author Carl Zhang
 * @description 需求 : 使用缓冲流进行复制文件
 * @date 2022/1/4 20:32
 */
public class BufferedStream01 {
    public static void main(String[] args) {
        try (
                //创建缓冲输入流对象
                //this(in, DEFAULT_BUFFER_SIZE); -> buf = new byte[size];
                //底层也是创建的字节数组,默认大小DEFAULT_BUFFER_SIZE = 8192;
                final BufferedInputStream bis = new BufferedInputStream(
                        new FileInputStream("C:\\Users\\carl\\Desktop\\樱木花道.png"));

                //创建缓冲输出流对象
                //this(out, 8192); -> buf = new byte[size];
                //底层也是创建的字节数组,默认大小 = 8192
                final BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("樱木花道.png"))
        ) {
            //一个字节一个字节复制
            int read;
            while ((read = bis.read()) != -1) {
                bos.write(read);
            }

            //一个字节数组一个字节数组复制
            byte[] bytes = new byte[1024];
            int len;
            while ((len = bis.read(bytes)) != -1) {
                bos.write(bytes, 0, len);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

18.6.2 字节缓冲流一次读取一个字节原理

image.png

18.6.3 字节缓冲流一次读取一个字节数组原理

image.png

18.6.4 四种复制视频方式

需求:把 “xxx.avi” 复制到模块目录下的 “copy.avi” , 使用四种复制文件的方式 , 打印所花费的时间

四种方式:

  • 基本的字节流一次读写一个字节
  • 基本的字节流一次读写一个字节数组
  • 缓冲流一次读写一个字节
  • 缓冲流一次读写一个字节数组
package com.heima.bufferedstream;

import java.io.*;

/**
 * @author Carl Zhang
 * @description 需求:把“xxx.avi”复制到模块目录下的“copy.avi” , 使用四种复制文件的方式 , 打印所花费的时间
 * @date 2022/1/4 21:06
 */
public class ReadVideo01 {
    public static void main(String[] args) {
        //获取开始时间
        final long start = System.currentTimeMillis();
        //method01(); //15202hm
        //method02(); //耗时24hm
        //method03(); //耗时64hm
        //method04(); //耗时9hm

        //获取结束时间
        final long end = System.currentTimeMillis();
        //打印总共耗时
        System.out.println("耗时" + (end - start) + "hm");
    }

    /**
     * 缓冲流一次读取一个字节数组
     */
    private static void method04() {
        try (
                //创建缓冲输入流和输出流对象
                BufferedInputStream bis = new BufferedInputStream(
                        new FileInputStream(
                                "D:\\Video\\Ranzer叶老师-玩一个 DISS 海尔兄弟.mp4"));
                BufferedOutputStream bos = new BufferedOutputStream(
                        new FileOutputStream(
                                "D:\\Video\\Ranzer叶老师-玩一个 DISS 海尔兄弟_副本.mp4"))
        ) {
            //循环一个字节数组一个字节数组复制
            byte[] bytes = new byte[1024];
            int len;
            while ((len = bis.read(bytes)) != -1) {
                bos.write(bytes, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 缓冲流一次读写一个字节
     */
    private static void method03() {
        try (
                //创建缓冲输入流和缓冲输出流对象
                BufferedInputStream bis = new BufferedInputStream(
                        new FileInputStream(
                                "D:\\Video\\Ranzer叶老师-玩一个 DISS 海尔兄弟.mp4"));
                BufferedOutputStream bos = new BufferedOutputStream(
                        new FileOutputStream(
                                "D:\\Video\\Ranzer叶老师-玩一个 DISS 海尔兄弟_副本.mp4"))

        ) {
            int read;
            while ((read = bis.read()) != -1) {
                bos.write(read);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 基本的字节流一次读写一个字节数组
     */
    private static void method02() {
        try (
                //创建字节输入流对象和字节输出流对象
                FileInputStream fileInputStream = new FileInputStream(
                        "D:\\Video\\Ranzer叶老师-玩一个 DISS 海尔兄弟.mp4");
                final FileOutputStream fileOutputStream = new FileOutputStream(
                        "D:\\Video\\Ranzer叶老师-玩一个 DISS 海尔兄弟_副本.mp4")
        ) {
            byte[] bytes = new byte[1024];
            int len;
            while ((len = fileInputStream.read(bytes)) != -1) {
                fileOutputStream.write(bytes, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 基本的字节流一次读写一个字节
     */
    private static void method01() {
        try (
                //创建字节输入流和字节输出流对象
                FileInputStream fileInputStream = new FileInputStream(
                        "D:\\Video\\Ranzer叶老师-玩一个 DISS 海尔兄弟.mp4");
                final FileOutputStream fileOutputStream = new FileOutputStream(
                        "D:\\Video\\Ranzer叶老师-玩一个 DISS 海尔兄弟_副本.mp4")
        ) {
            int read;
            //循环一个字节一个字节复制
            while ((read = fileInputStream.read()) != -1) {
                fileOutputStream.write(read);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

18.7 Properties在IO流中的应用


18.7.1 Properties 特有方法

Properties  常用方法见 十四、集合

方法名 说明
Object setProperty(String  key, String value) 设置集合的键和值,都是String类型,相当于put方法
String getProperty(String  key) 使用此属性列表中指定的键搜索属性,相当于get方法
Set  stringPropertyNames() 从该属性列表中返回一个不可修改的键集,其中键及其对应的值是字符串 ,  相当于keyset方法
package properties;

import java.util.Map;
import java.util.Properties;
import java.util.Set;

/**
 * @author: Carl Zhang
 * @create: 2022-01-05 09:28
 * Properties集合特有方法
 */
public class Properties01 {
    public static void main(String[] args) {
        //- 不需要加泛型 , 工作中只存字符串
        //public class Properties extends Hashtable<Object,Object>
        
        //创建集合
        Properties properties1 = new Properties();
        //Properties作为集合的特有方法
        //Object setProperty(String key, String value)	设置集合的键和值,都是String类型,相当于put方法
        properties1.setProperty("it001", "张三");
        properties1.setProperty("it002", "李四");
        properties1.setProperty("it003", "王五");

        //String getProperty(String key)	使用此属性列表中指定的键搜索属性 , 相当于get方法
        //Set<String> stringPropertyNames()	从该属性列表中返回一个不可修改的键集,其中键及其对应的值是字符串 , 相当于keySet方法
        //通过特有方法进行集合遍历
        Set<String> strings = properties1.stringPropertyNames();
        for (String key : strings) {
            String value = properties1.getProperty(key);
            System.out.println(key + "-" + value);
        }
    }
}

18.7.2 Properties中和IO相关的方法

方法名 说明
void  load(InputStream inStream) 以字节流形式 , 把文件中的键值对,  读取到集合中
void  load(Reader reader) 字符流形式 , 把文件中的键值对,  读取到集合中
void  store(OutputStream out, String comments) 把集合中的键值对,以字节流形式写入文件中 , 参数二为注释
void  store(Writer writer, String comments) 把集合中的键值对,以字符流形式写入文件中 , 参数二为注释
package properties;

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

/**
 * @author: Carl Zhang
 * @create: 2022-01-05 09:56
 * properties中和IO相关的方法
 */
public class Properties02 {
    public static void main(String[] args) throws IOException {
        //读取一个properties文件里的键值对到集合
        //- void load(InputStream inStream)    以字节流形式 , 把文件中的键值对, 读取到集合中
        Properties properties = new Properties();
        properties.load(new BufferedInputStream(new FileInputStream("test.properties")));

        //遍历
        //注意:通过字节输入流获取中文会乱码 001-°¡´ò·¢·¢´ï
        Set<String> strings = properties.stringPropertyNames();
        for (String key : strings) {
            String value = properties.getProperty(key);
            System.out.println(key + "-" + value);
        }


        //将Properties集合里的键值对写到properties文件中
        //- void store(OutputStream out, String comments) 把集合中的键值对,以字节流形式写入文件中 , 参数二为注释
        //注意:通过字节输出流写出中文会乱码
        properties.store(new BufferedOutputStream(new FileOutputStream(
                "copy.properties")), "test");
    }
}

18.8 使用ResourceBundle工具加载属性文件


18.8.1 API介绍

java.util.ResourceBundle它是一个抽象类,我们可以使用它的子类 PropertyResourceBundle 来读取以 .properties 结尾的配置文件。

通过静态方法直接获取对象:

static ResourceBundle getBundle(String baseName) 可以根据名字直接获取默认语言环境下的属性资源。
参数注意: baseName 
  	1.属性集名称不含扩展名。
    2.属性集文件是在src目录中的
  
比如:src中存在一个文件 user.properties
ResourceBundle bundle = ResourceBundle.getBundle("user");

ResourceBundle 中常用方法:

 `String getString(String key)` : 通过键,获取对应的值

18.8.2 代码实践

通过 ResourceBundle 工具类, 将一个属性文件 放到 src 目录中,使用 ResourceBundle 去获取键值对数据

import java.util.ResourceBundle;

/**
 * @author: Carl Zhang
 * @create: 2022-01-05 10:46
 * 通过  ResourceBundle  工具类
 * 将一个属性文件 放到  src  目录中,使用  ResourceBundle  去获取键值对数据
 */
public class ResourceBundle01 {
    public static void main(String[] args) {
        //1. 获取ResourceBundle对象
        ResourceBundle bundle = ResourceBundle.getBundle("test");

        //2. 通过方法获取指定键对应的值
        String username = bundle.getString("username");
        String password = bundle.getString("password");
        System.out.println("username = " + username);
        System.out.println("password = " + password);
    }
}

18.9 字节流读写中文出现乱码问题


18.9.1 字节流读中文出现乱码的原因

问题: 我们前面已经知道字节流读写中文可能会出现乱码
image.png
原因:因为字节流一次读一个字节,而不管GBK 还是 UTF-8 一个中文都是多个字节,用字节流每次只能读其中的一部分,所以就会出现乱码问题(有关编码的部分见 3.5.1 字符编码

18.9.2 字节流拷贝文件会出现乱码问题吗

问题:如果用字节流将文件中的数据读到内存中,打印在控制台会出现乱码。但是字节流进行拷贝文件的时候是不会出现乱码。这是为什么呢?

原因:因为字节流进行拷贝文件的时候是把文件的所有字节拷贝完了才展示的,而不是拷贝一半展示一半,所以不会出现乱码问题

18.9.3 思考:为什么字符流读取中文不会出现乱码问题?

image.png

18.10 字符输出流


18.10.1 字符流输出介绍

Writer类
  • 写入字符流的最顶层的类 , 是一个抽象类 ,不能实例化 , 需要使用其子类FileWriter类
FileWriter类  : 用来写入字符文件的便捷类
  • image.png

18.10.2 FileWriter的成员

  • 构造方法 :
    • public FileWriter(File file)  : 往指定的 File 路径中写入数据
    • public FileWriter(String fileName)  : 往指定的 String 路径中写入数据
      • 底层会创建对应路径的 File 对象
  • 成员方法 :
    | void write(int c) | 写一个字符 |
    | --- | --- |
    | void write(char[] cbuf) | 写入一个字符数组 |
    | void write(char[] cbuf, int off, int len) | 写入字符数组的一部分 |
    | void write(String str) | 写一个字符串 |
    | void write(String str, int off, int len) | 写一个字符串的一部分 |
flush() 刷新流,还可以继续写数据
close() 关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据

18.10.3 FileWriter写数据的步骤

  1. 创建字符输出流对象
    • 注意事项
      • 如果文件不存在,就创建。但是要保证父级路径存在。
      • 如果文件存在就清空内容
  2. 写数据
    • 注意事项:
      • 写出 int 类型的整数,实际写出的是整数在码表上对应的字母。
      • 写出字符串数据,是把字符串本身原样写出。
  3. 释放资源
    • 注意事项:
      • 每次使用完流必须要释放资源。
      • 释放后不能再次使用
package writer;

import java.io.FileWriter;
import java.io.IOException;

/**
 * @author: Carl Zhang
 * @create: 2022-01-05 13:32
 * 将字符写入硬盘
 */
public class FileWriter01 {
    public static void main(String[] args) throws IOException {
        //1. 创建字符输出流对象
        FileWriter fileWriter = new FileWriter("aaa.txt");

        //2. 调用方法将字符写入流的缓存
        fileWriter.write("你好!");

        //3. 调用方法刷新流,将字符刷新到硬盘
        // 不刷新不会写入硬盘 ,刷新完可继续写入
        fileWriter.flush();
        fileWriter.write("世界");
        fileWriter.flush();

        // 4. 关闭流
        fileWriter.close();
    }
}
import java.io.FileWriter;
import java.io.IOException;

/**
 * @author: Carl Zhang
 * @create: 2022-01-05 13:40
 */
public class FileWriter02 {
    public static void main(String[] args) throws IOException {

        FileWriter fileWriter = new FileWriter("test01.txt");

        //void write(int c)	写一个字符
        // 写入的是字符,显示时根据编码规范翻译成对应字符
        fileWriter.write(97);
        fileWriter.write('!');

        //flush()	刷新流,还可以继续写数据
        fileWriter.flush();

        //void write(char[] cbuf)	写入一个字符数组
        char[] chars = {'a', 'b', 'c', 'd', 'e'};
        fileWriter.write(chars);
        fileWriter.flush();

        //void write(char[] cbuf, int off, int len)	写入字符数组的一部分
        fileWriter.write(chars, 1, 4);
        fileWriter.flush();

        //void write(String str)	写一个字符串
        fileWriter.write("hello, world!");
        fileWriter.flush();

        //void write(String str, int off, int len)	写一个字符串的一部分
        fileWriter.write("你好", 0, 1);
        fileWriter.flush();

        //close()	关闭流,释放资源,但是在关闭之前会先刷新流。一旦关闭,就不能再写数据
        fileWriter.close();
    }
}

18.11 字符输入流


18.11.1 字符输入流介绍

  • Reader 类 :
    • 读取字符流的最顶层的类 , 是一个抽象类 ,不能实例化
    • 需要使用其子类 FileReader
  • FileReader 类 :
    • 用来读取字符文件的便捷类

18.11.2 FileReader的成员

  • 构造方法 :
    • public FileReader(File file) : 从指定的 File 路径中读取数据
    • public FileReader(String fileName)  : 从指定的String路径中读取数据
  • 成员方法 :
    | int read() | 一次读一个字符数据,没有内容了就返回-1 |
    | --- | --- |
    | int read(char[] cbuf) | 一次读一个字符数组数据,没有内容了就返回-1 |
import java.io.FileReader;
import java.io.IOException;

/**
 * @author: Carl Zhang
 * @create: 2022-01-05 15:10
 * 使用字符流一次读取一个字符
 */
public class Reader01 {
    public static void main(String[] args) {
        //1. 创建字符输入流对象
        try (FileReader fileReader = new FileReader("斗罗大陆.txt")) {
            //2. 创建变量保存字符输入流读取的字符数字
            int read;
            //3. 循环一个字符一个字符的读取
            while ((read = fileReader.read()) != -1) {
                //拿到的是字符对应的数字,需要强装
                System.out.print((char) read);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
import java.io.FileReader;
import java.io.IOException;

/**
 * @author: Carl Zhang
 * @create: 2022-01-05 15:16
 * 使用字符输入流读取一个字符数组
 */
public class Reader02 {
    public static void main(String[] args) {
        //1. 创建字符输入流对象
        try (FileReader fileReader = new FileReader("斗罗大陆.txt")) {
            //2. 创建字符数组保存读到的字符,创建变量保存真实读到的字数
            char[] chars = new char[1024];
            int len;
            //3. 循环一个字符数组一个字符数组的读
            while ((len = fileReader.read(chars)) != -1) {
                //4. 将读到的字符数组转成String再打印
                System.out.print(new String(chars, 0, len));
            }
            //5. 关闭流
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

18.12 字符缓冲流


18.12.1 字符缓冲流介绍

  • BufferedWriter :可以将数据高效的写出
  • BufferedReader :可以将数据高效的读入到内存
  • 注意 :  字符缓冲流不具备读写功能 , 只提供缓冲区 , 真正读写还是需要依赖于构造接收的基本的字符流
  • 构造方法
    • public BufferedWriter(Writer out)  : 构造方法中需要接收一个基本的字符输出流
    • public BufferedReader(Reader in): 构造方法中需要接收一个基本的字符输入流

18.12.2 使用字符缓冲流拷贝文本文件

package com.heima.bufferedwriter;

import java.io.*;

/**
 * @author: Carl Zhang
 * @create: 2022-01-05 15:36
 * 使用字符缓冲流拷贝文本文件
 * public BufferedReader(Reader in) : 构造方法中需要接收一个基本的字符输入流
 * public BufferedWriter(Writer out) : 构造方法中需要接收一个基本的字符输出流
 */
public class CopyText {
    public static void main(String[] args) {
        try (
                //创建一个字符缓冲输出流
                BufferedWriter bufferedWriter = new BufferedWriter(
                        new FileWriter("斗罗大陆_副本.txt"));
                //创建一个字符缓冲输入流
                BufferedReader bufferedReader = new BufferedReader(
                        new FileReader("斗罗大陆.txt"))
        ) {
            //一个字符一个字符的复制
            //copyChar(bufferedWriter, bufferedReader);

            //一个字符数组一个字符数组的复制
            copyChars(bufferedWriter, bufferedReader);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 一个字符数组一个字符数组的复制
     * @param bufferedWriter 目标文件的字符缓冲输出流
     * @param bufferedReader 要拷贝的文件的字符缓冲输入流
     * @throws IOException 异常
     */
    private static void copyChars(BufferedWriter bufferedWriter, BufferedReader bufferedReader) throws IOException {
        char[] chars = new char[1024];
        int len;
        while ((len = bufferedReader.read(chars)) != -1) {
            bufferedWriter.write(chars, 0, len);
            bufferedWriter.flush();
        }
    }

    /**
     * 一个字符一个字符的复制
     * @param bufferedWriter 目标位置的字符缓冲输出流
     * @param bufferedReader 要拷贝的文件的字符缓冲输入流
     * @throws IOException 异常
     */
    private static void copyChar(BufferedWriter bufferedWriter, BufferedReader bufferedReader) throws IOException {
        int read;
        while ((read = bufferedReader.read()) != -1) {
            bufferedWriter.write(read);
            //写一个字符刷新一次
            bufferedWriter.flush();
        }
    }
}

18.12.3 字符缓冲流的特有方法

BufferedWriter
  • void newLine() :写一个行分隔符,会根据操作系统的不同,写入不同的行分隔符
BufferedReader
  • public String readLine()  :读取文件一行数据, 不包含换行符号 ,  读到文件的末尾返回null
package com.heima.bufferedwriter;

import java.io.*;

/**
 * @author: Carl Zhang
 * @create: 2022-01-05 15:58
 * 将文本保存到文本文件,然后再从文件中或取打印
 * 昔人已乘黄鹤去,此地空余黄鹤楼。
 * 黄鹤一去不复返,白云千载空悠悠。
 * 晴川历历汉阳树,芳草萋萋鹦鹉洲。
 * 日暮乡关何处是?烟波江上使人愁。
 */
public class CopyLine {
    public static void main(String[] args) {
        writeText();

        readText();
    }
    
    private static void writeText() {
        // 1. 创建字符缓冲输出流
        try (BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(
                "黄鹤楼.txt"))) {
            // 2. 写一行,写一个分隔符
            bufferedWriter.write("昔人已乘黄鹤去,此地空余黄鹤楼。");
            bufferedWriter.newLine();
            bufferedWriter.write("黄鹤一去不复返,白云千载空悠悠。");
            bufferedWriter.newLine();
            bufferedWriter.write("晴川历历汉阳树,芳草萋萋鹦鹉洲。");
            bufferedWriter.newLine();
            bufferedWriter.write("日暮乡关何处是?烟波江上使人愁。");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    private static void readText() {
        // 3. 创建字符缓冲输入流
        try (BufferedReader bufferedReader = new BufferedReader(new FileReader(
                "黄鹤楼.txt"))) {
            // 4. 创建变量保存读取的字符串
            String read;
            // 5. 循环一个字符串一个字符串的获取打印
            // 5.1 当返回null就退出
            while ((read = bufferedReader.readLine()) != null) {
                System.out.println(read);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

18.12.4 字符缓冲流对文件的数据排序

import java.io.*;
import java.util.Arrays;

/**
 * @author: Carl Zhang
 * @create: 2022-01-05 16:20
 * 需求:读取文件中的数据 : 33 22 11 55 44
 * 排序后 : 11 22 33 44 55  再次写到本地文件
 */
public class Sort01 {
    public static void main(String[] args) {
        //* 步骤 :
        //* 1 创建高效的字符输入流对象 和高效的字符输出流对象
        try (
                BufferedWriter bufferedWriter = new BufferedWriter(new FileWriter(
                        "number02.txt"));
                BufferedReader bufferedReader = new BufferedReader(
                        new FileReader("number01.txt"))) {
            //* 2 读取文件中的一行数据
            String readLine = bufferedReader.readLine();
            //* 3 将数据按照空格切割
            String[] s = readLine.split(" ");
            //* 4 把字符串数组转成int类型数组
            int[] nums = new int[s.length];
            for (int i = 0; i < s.length; i++) {
                nums[i] = Integer.parseInt(s[i]);
            }
            //* 5 对int类型的数组进行排序
            Arrays.sort(nums);
            //* 7 遍历数组,把数组中的数据写入到文件中
            for (int num : nums) {
                bufferedWriter.write(num + " ");
                //!,7 下列代码出现乱码:因为写入数字重合在一起,会根据编码规范转换成数字对应字符
                //bufferedWriter.write(num);
                bufferedWriter.flush();
            }
            //* 8 释放资源
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

18.13 转换流


18.13.1 转换流介绍

  • 转换流就是来进行字节流和字符流之间转换的桥梁

18.13.2 转换流分类

  • 本质上还是字符流

  • InputStreamReader 是从字节流到字符流的桥梁

    • image.png
    • public InputStreamReader(InputStream in) : 创建一个使用默认编码的 InputStreamReader。
    • public InputStreamReader(InputStream in , String charsetName)  : 创建使用指定编码的 InputStreamReader。
  • OutputStreamWriter 是从字符流到字节流的桥梁

    • image.png
    • public OutputStreamWriter(OutputStream out) : 创建使用默认字符编码的 OutputStreamWriter
    • public OutputStreamWriter(OutputStream out, String charsetName) : 创建使用指定编码的 OutputStreamWriter。

18.13.2 字符流通过转换流读取数据原理

image.png

18.13.3 转换流练习

package com.heima.brige;

import java.io.*;

/**
 * @author: Carl Zhang
 * @create: 2022-01-05 17:17
 * 需求1 : 使用转换流 , 把以下数据按照GBK的编码写入文件 , 在使用GBK的编码读取数据
 * 数据如下 :
 * 远桥之下泛莲舟
 * 岱岩石上松溪流
 * 万仞翠山梨亭在
 * 莫闻空谷声悠悠
 */
public class Bridge01 {
    public static void main(String[] args) throws IOException {
        //创建转换输出流
        OutputStreamWriter outputStreamWriter = new OutputStreamWriter(
                new FileOutputStream("诗.txt"), "GBK");
        //写一句字符串,写一个换行
        outputStreamWriter.write("远桥之下泛莲舟");
        outputStreamWriter.write("\r\n");
        outputStreamWriter.write("岱岩石上松溪流");
        outputStreamWriter.write("\r\n");
        outputStreamWriter.write("万仞翠山梨亭在");
        outputStreamWriter.write("\r\n");
        outputStreamWriter.write("莫闻空谷声悠悠");

        outputStreamWriter.flush();
        //关闭
        outputStreamWriter.close();

        //创建转换输入流
        InputStreamReader inputStreamReader = new InputStreamReader(
                new FileInputStream("诗.txt"), "GBK");
        //循环读取字符串,读一段打印一段
        int read;
        while ((read = inputStreamReader.read()) != -1) {
            System.out.print((char) read);
        }
    }
}
package com.heima.brige;

import java.io.*;

/**
 * @author: Carl Zhang
 * @create: 2022-01-05 17:26
 * 需求2 :  将模块根目录中GBK编码的文本文件 , 转换为UTF-8编码的文本文件
 * 1. 创建GBK格式的转换输入流对象
 * 2. 通过UTF-8的转换输出流输出
 */
public class Bridge02 {
    public static void main(String[] args) throws IOException {
        InputStreamReader isr = new InputStreamReader(new FileInputStream("诗.txt"), "GBK");
        OutputStreamWriter osw  = new OutputStreamWriter(new FileOutputStream("诗_副本.txt"), "UTF-8");
        int read;
        while ((read = isr.read()) != -1) {
            osw.write(read);
        }

        isr.close();
        osw.close();
    }
}

18.13.4 转换流与便携类的关系

FileWriterOutputStreamWriter 的子类(便携类),采用默认的编码集,不能指定编码(JDK11 创建 FileWriter 对象时可以指定编码格式)

  • 如果用系统默认的编码,就用 Filewriter
  • 要指定编码时还是用 OutputStreamWriter

18.14 对象操作流


18.14.1 对象操作流介绍

  • 可以把对象以字节的形式写到本地文件,直接打开文件,是读不懂的,需要再次用对象操作流读到内存中

18.14.2 对象操作流的分类

  • ObjectOutputStream 对象操作输出流

    • image.png
    • 对象操作输出流(对象序列化流):就是将对象写到本地文件中,或者在网络中传输对象
  • ObjectInputStream 对象操作输入流

    • image.png
    • 对象操作输入流(对象反序列化流):把写到本地文件中的对象读到内存中,或者接收网络中传输的对象

18.14.3  对象操作流的注意事项

  • 注意 : 如果一个类对象想要被序列化 , 那么此类需要实现 Serializable 接口

    • Serializable 接口的含义 :
      • 是一个标记性接口 , 里面没有任何抽象方法
      • 只要一个类实现了此接口 , 表示此类的对象可以被序列化
  • 问题:

  • 用对象序列化流序列化了一个对象后,假如我们修改了对象所属的 Javabean 类,读取数据会不会出问题呢?

    • 会出问题,会抛出 InvalidClassException 异常
  • 如果出问题了,如何解决呢?

    • 给对象所属的类加一个 serialVersionUID   ,设置一个固定的无法被修改的值
    • private static final long serialVersionUID = 42L;
  • 如果一个对象中的某个成员变量的值不想被序列化,又该如何实现呢?

    • 给该成员变量加 transient 关键字修饰,该关键字标记的成员变量不参与序列化过程
import java.io.Serializable;

/**
 * @author carl
 * 如果此类对象想要被序列化 , 那么此类需要实现Serializable接口
 * Serializable接口的含义 :
 * 是一个标记性接口 , 里面没有任何抽象方法
 * 只要一个类实现了此接口 , 表示此类的对象可以被序列化
 */
public class User implements Serializable {
    /*
        问题分析 :
            serialVersionUID : 序列号
            序列号是根据类的信息进行生成的
            如果没有自己给出序列号 , JVM会根据类的信息自动计算一个序列号
            如果改动了类的信息 , 那么JVM会重新计算一个序列号

            第一步 : 把对象序列化到本地中 , 序列号为 -4446663370728791812 也会存储到本地中
            第二步 : 我们自己修改了类 , 会重新计算一个新的序列号 2908680347500030933
            第三步 : 当把对象读到内存中时 , 本地中的序列号和类中的序列号不一致就会发生 InvalidClassException异常

        解决方案 :
            我们自己手动给出序列号, 不让虚拟机自动生成 , 并且这个值恒久不变
            private static final long serialVersionUID = 值L;
     */

    private static final long serialVersionUID = 111L;

    private String username;
    //transient修饰,不会被序列化
    private transient String password;

    public User() {
    }

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

import java.io.*;

/**
 * @author Carl Zhang
 * @description 对象操作流演示
 * @date 2022/1/5 20:49
 */
public class ObjectOutputStream01 {
    public static void main(String[] args) {
        //创建对象输出流,将对象写入硬盘
        try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(
                new FileOutputStream("user.txt"))) {

            User user = new User("CarlZhang", "123456");
            //将对象写入硬盘
            objectOutputStream.writeObject(user);
        } catch (
                IOException e) {
            e.printStackTrace();
        }

        //创建对象输入流,将对象加载到内存,打印对象信息
        try (ObjectInputStream objectInputStream = new ObjectInputStream(
                new FileInputStream("user.txt"))) {
            //读取文件中的对象,并强转成User类
            User user = (User) objectInputStream.readObject();
            System.out.println(user);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

18.14.4 案例:将多个对象写入文件再读取出来

package com.heima.objectoutputstream;

import java.io.*;
import java.util.ArrayList;

/**
 * @author Carl Zhang
 * @description 将多个对象写入文件再读取出来
 * @date 2022/1/5 21:03
 */
public class Exercise01 {
    public static void main(String[] args) {
        //1. 创建多个User对象,保存到集合里
        ArrayList<User> users = new ArrayList<>();
        users.add(new User("zhaosi", "123456"));
        users.add(new User("benshan", "456123"));
        users.add(new User("debiao", "654321"));
        users.add(new User("dashuai", "321654"));


        //2. 创建对象输出流对象,循环将集合里的对象写入文件中
        try (ObjectOutputStream outputStream = new ObjectOutputStream(
                new FileOutputStream("user.txt"))) {
            for (User user : users) {
                outputStream.writeObject(user);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }

        //3. 创建对象输入流对象,循环将文件中的对象存入新集合然后打印
        try (ObjectInputStream objectInputStream = new ObjectInputStream(
                new FileInputStream("user.txt"))) {
            //保存文件中读取的对象
            User user;
            //当读完了会抛异常 EOFException
            //1. 方式一:死循环读对象,当抛异常就将异常捕获提示读取完毕
            //2. 方式二: 将包含对象的集合写入文件,再从文件中读取集合  -- 见Exercise02
            while (true) {
                user = (User) objectInputStream.readObject();
                System.out.println(user);
            }
        } catch (EOFException e) {
            System.out.println("对象读取完毕...");
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
package com.heima.objectoutputstream;

import java.io.*;
import java.util.ArrayList;

/**
 * @author Carl Zhang
 * @description 通过读写集合完成读写多个对象
 * @date 2022/1/5 21:28
 */
public class Exercise02 {
    public static void main(String[] args) {
        //1. 创建多个User对象,保存到集合里
        ArrayList<User> users = new ArrayList<>();
        users.add(new User("zhaosi", "123456"));
        users.add(new User("benshan", "456123"));
        users.add(new User("debiao", "654321"));
        users.add(new User("dashuai", "321654"));

        //2. 创建对象输出流,将集合对象输出到文件
        try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(
                new FileOutputStream("users.txt"))) {
            objectOutputStream.writeObject(users);
        } catch (IOException e) {
            e.printStackTrace();
        }

        //3. 创建对象输入流,将集合对象输入到内存
        try (ObjectInputStream objectInputStream = new ObjectInputStream(
                new FileInputStream("users.txt"))) {
            ArrayList<User> user = (ArrayList<User>) objectInputStream.readObject();
            for (User user1 : user) {
                System.out.println(user1);
            }
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

18.15 打印流 PrintStream


18.15.1 打印流介绍

PrintStream
image.png

  • 构造方法:
    • public PrintStream(String filePath): 构建一个打印流对象,传入接收数据的文件路径
  • 方法:
    • public void println(数据) :将数据打印到目标文件中,打印后换行
    • public void print(数据) : 将数据打印到目标文件中, 打印不换行

18.15.2 打印流使用案例

package com.heima.printstram;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;

/**
 * @author Carl Zhang
 * @description 打印流的使用演示
 * @date 2022/1/5 21:50
 */
public class PrintStream01 {
    public static void main(String[] args) {
        //创建打印流对象
        try (PrintStream printStream = new PrintStream(new FileOutputStream("print.txt"))) {
            //调用方法打印数据到文件里
            printStream.println("Hello, World!");
            printStream.println(123);
            printStream.print("你好,");
            printStream.print("世界!");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}
  • System.out.println()
    • 其中调用的就是一个System 类的打印流对象 public final static PrintStream out = null;
    • 可以改变系统输出的流向:System.setOut(打印流)
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;

/**
 * @author Carl Zhang
 * @description 修改系统的打印流
 * @date 2022/1/5 21:54
 */
public class PrintStream02 {
    public static void main(String[] args) throws FileNotFoundException {
        System.setOut(new PrintStream(new FileOutputStream("test01.txt")));
        
        //打印到了test01.txt文件中
        System.out.println("Hello, World!");
        System.out.println(123);
        System.out.print("你好,");
        System.out.print("世界!");
    }
}

18.15.3 打印流使用场景

  • 打印流使用很简单,print/println 方法
  • 作用:就是为了方便记录数据(日志)

18.16 装饰者设计模式


18.16.1 装饰者设计模式介绍

  • 装饰模式指的是在不改变原类, 不使用继承的基础上,动态地扩展一个对象的功能。
  • 不使用继承数据扩展功能, 可以降低耦合

18.16.2 使用原则

  • 装饰类和被装饰类需要有共同的父类型。
    • 在之前学习过的 BufferedWriterFileWriter 就是装饰设计模式
    • BufferedWriter 的父类为 Writer
    • FileWriter 的父类也是 Writer
    • 我们把 FileWriter 的对象传递到 BufferedWriter 的构造中 , 那么可以理解为 BufferedWriter 是装饰类 , FileWriter 是被装饰类
    • BufferedWriterFileWriter 的功能做了增强
  • 装饰类的构造要接收被装饰类的对象
    • FileWriter fw = new FileWriter("路径");
    • BufferedWriter bw = new BufferedWriter(fw);
  • 在装饰类中把要增强扩展的功能进行扩展
    • BufferedWriter  和 FileWriter 的功能一样, 都具备 Writer 中写数据的功能
    • 但是 BufferedWriter 提供了缓冲区 , 相当于对 FileWriter 功能做了扩展
  • 对于不要增强的功能直接调用
    • 不需要增强的功能直接继承父类的即可

18.16.3 使用案例

已知有接口 Star 和其子类型 LiuDeHua。

public interface Star {
    public abstract void sing();
    public abstract void dance();
}
  • 需求 :在不改变 LiuDeHua 类及不使用继承的技术前提下,动态的扩展 LiuDeHuasing 功能。

    • LiuDeHua 就是一个被装饰类 , 需要对唱歌的功能进行扩展
  • 思路 :定义一个装饰类,去装饰增强 LiuDehua 类。

  • 步骤:

    1. 创建 LiuDeHua 类并实现接口 Star 【被装饰类】
    2. 定义一个装饰类 LiuDeHuaWrapper 实现 Star  【装饰类】
    3. 在装饰类里面定义一个成员变量类型是 LiuDeHua ,可以使用构造方法进行传入被装饰类对象。
    4. 在装饰类中对 sing 方法进行功能扩展
    5. dance 不做改动
    6. 测试类分别创建装饰类的对象和被装饰类的对象。将被装饰类对象刘德华对象设置给装饰类对象
package com.itheima.design_demo;

/*
    装饰模式指的是在不改变原类, 不使用继承的基础上,动态地扩展一个对象的功能

    使用原则 :
        1. 装饰类和被装饰类需要有共同的父类型。
        2. 装饰类要传入被装饰类的对象
        3. 在装饰类中把要增强扩展的功能进行扩展
        4. 对于不要增强的功能直接调用


    需求 : 在不改变LiuDeHua类,及不使用继承的技术前提下,动态的扩展LiuDeHua的sing功能。
            LiuDeHua就是一个被装饰类 , 需要对唱歌的功能进行扩展
    步骤:
        1. 创建LiuDeHua类并实现接口Star【被装饰类】
        2. 定义一个装饰类LiuDeHuaWrapper实现Star 【装饰类】
        3. 在装饰类里面定义一个成员变量类型是LiuDeHua,可以使用构造方法进行传入被装饰类对象。
        4. 在装饰类中对sing方法进行功能扩展
        5. 对dance不做改动
        6. 测试类分别创建装饰类的对象和被装饰类的对象。将被装饰类对象刘德华对象设置给装饰类对象
 */
public class Test {
    public static void main(String[] args) {
        //  6. 测试类分别创建装饰类的对象和被装饰类的对象。将被装饰类对象刘德华对象设置给装饰类对象

        // 创建被装饰的类对象
        LiuDeHua huaZai = new LiuDeHua();// 0x001
        // 创建装饰类对象
        LiuDeHuaWrapper liuDeHuaWrapper = new LiuDeHuaWrapper(huaZai);

        liuDeHuaWrapper.sing();
        liuDeHuaWrapper.dance();
    }
}

// 明星接口 , 装饰类和被装饰类的父类型
interface Star {
    public abstract void sing(); // 唱歌

    public abstract void dance();// 跳舞
}

// 1. 创建LiuDeHua类并实现接口Star【被装饰类】
class LiuDeHua implements Star {

    @Override
    public void sing() {
        System.out.println("唱忘情水....");
    }

    @Override
    public void dance() {
        System.out.println("华仔在跳老年迪斯高...");
    }
}

// 2. 定义一个装饰类LiuDeHuaWrapper实现Star 【装饰类】
class LiuDeHuaWrapper implements Star {
    // 3. 在装饰类里面定义一个成员变量类型是LiuDeHua,可以使用构造方法进行传入被装饰类对象
    private LiuDeHua huaZai;// 0x001

    public LiuDeHuaWrapper(LiuDeHua huaZai) {// 0x001
        this.huaZai = huaZai;
    }

    // 4. 在装饰类中对sing方法进行功能扩展
    @Override
    public void sing() {
        System.out.print("华仔正在深情的");
        huaZai.sing();
    }

    // 5. 对dance不做改动
    @Override
    public void dance() {
        huaZai.dance();
    }
}

18.16.4 注意事项和使用细节

  • 装饰类和被装饰类需要有共同的父类型
  • 装饰类的构造器要传入被装饰类的对象
  • 在装饰类中把要增强扩展的功能进行扩展
  • 对于不要增强的功能直接调用

18.17 IO工具包 : commons-io


使用工具包可以简化 IO 操作

18.17.1 导入 jar 包

  1. 下载 commons-io 相关 jar 包;
  2. commons-io-2.6.jar 包复制到指定的 Modulelib 目录中
  3. commons-io-2.6.jar 加入到项目中
  4. 右键 jar 包:

image.png
image.png

18.17.2 API 介绍

  • **org.apache.commons.io.IOUtils**** 类**
public static int copy(InputStream in, OutputStream out):
	把input输入流中的内容拷贝到output输出流中,返回拷贝的字节个数(适合文件大小为2GB以下)
    
public static long copyLarge(InputStream in, OutputStream out):
	把input输入流中的内容拷贝到output输出流中,返回拷贝的字节个数(适合文件大小为2GB以上)
  • 代码实践 :
 
package com.itheima.commons_io;

import org.apache.commons.io.IOUtils;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

/*
    org.apache.commons.io.IOUtils类

    public static int copy(InputStream in, OutputStream out):
	把input输入流中的内容拷贝到output输出流中,返回拷贝的字节个数(适合文件大小为2GB以下)

    public static long copyLarge(InputStream in, OutputStream out):
	把input输入流中的内容拷贝到output输出流中,返回拷贝的字节个数(适合文件大小为2GB以上)
 */
public class Test1 {
    public static void main(String[] args) throws IOException {
        IOUtils.copy(new FileInputStream("liqin2.jpg"), new FileOutputStream("liqin2.jpg"));
    }
}
  • **org.apache.commons.io.FileUtils**
public static void copyFileToDirectory(final File srcFile, final File destFile): 
	复制文件到另外一个目录下。
public static void copyDirectoryToDirectory(File src , File dest ):
	复制src目录到dest位置。
  • 代码实践:
package com.itheima.commons_io;

import org.apache.commons.io.FileUtils;

import java.io.File;
import java.io.IOException;

/*
    org.apache.commons.io.FileUtils

    public static void copyFileToDirectory(final File srcFile, final File destFile):
	复制文件到另外一个目录下。

    public static void copyDirectoryToDirectory(File src , File dest ):
	复制src目录到dest目录中。
 */
public class Test2 {
    public static void main(String[] args) throws IOException {
        FileUtils.copyDirectoryToDirectory(new File("D:\\传智播客\\安装包\\好看的图片") , new File("D:\\图片"));
    }
}

18.18 IO 流总结

IO总结.xmind

posted @   Carl-Zhang  阅读(120)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
点击右上角即可分享
微信分享提示