IO笔记-Java

一. IO 简介

1. 什么是 IO

对于任何程序设计语言而言, 输出和输入(Input/Output) 系统都是非常核心的功能, 程序运行需要数据, 数据的获取往往需要跟外部系统进行通信, 外部系统可能是文件、数据库、其他程序、网络、IO设备等等, 外部系统比较复杂多变, 那么我们有必要通过某种手段进行抽象、屏蔽外部的差异, 从而实现更加便捷的编程.

输入(Input)指的是: 可以让程序从外部系统获得数据(核心含义是"读" 读取外部数据)

常见的应用

  • 读取硬盘上的文件内容到程序, 例如: 播放器打开一个视频文件、word打开一个doc文件
  • 读取网络上某个位置内容到程序, 例如: 浏览器中输入网址后, 打开该网址对应的网页内容; 下载网络上某个网址的文件
  • 读取数据库系统的数据到程序
  • 读取某些硬件系统数据到程序, 例如: 车载电脑读取雷达扫描信息到程序; 温控系统等

输出(Output) 指的是: 程序输出数据给外部系统从而可以操作外部系统(核心含义是"写", 将程序写出到外部系统) 常见的应用有:

  • 将数据写到硬盘中, 例如: 我们编辑完一个word文档后, 将内容写到硬盘上进行保存
  • 将数据写到数据库系统中, 例如: 我们注册一个网站会员, 实际就是后台程序向数据库中写入一条记录
  • 将数据写到某些硬盘系统中. 例如: 导弹系统导航程序将新的路径输出到飞控子系统, 飞控子系统根据数据修正飞行路径

java.io 包为我们提供了相关的API, 实现了对所有外部系统的输入输出操作

2. 数据源

数据源 Data Source, 提供数据的原始媒介, c行间的数据源有: 数据库、文件、其他程序、内存、网络连接、IO设备

数据源分为: 源设备、目标设备

  • 源设备: 为程序提供数据, 一般对应输入流
  • 目标设备: 程序数据的目的地, 一般对应输出流

3. 流的概念

流是一个抽象、动态的概念, 是一连串连续动态的数据集合

对于输入流而言, 数据源就像冰箱, 流(Stream) 就像水管中流动的水流, 程序就是我们最终的用户, 我们通过流(A Stream) 将数据源(Souree)中的数据(information) 输送到程序中(Program) 中

对于输出流而言, 目标数据源就是目的地(dest), 我们通过流(A Stream) 将程序(Program) 中的数据(information) 输送到目的的数据源(dest)中

在这里插入图片描述

输入/输入流的划分是相对程序而言的, 并不是相对数据源


4. Java 中四大 IO 抽象类

InputStream/OutputStream 和 Reader/Writer 类是所有 IO 流类的抽象父类, 通过它们具体的子类熟悉相关的用法

  • InputStream

    • 此抽象类是表示字节输入流的所有类的父类, InputStream是一个抽象类, 它不可以实例化, 数据的读取需要由它的子类来实现, 根据节点的不同, 它派生了不同的节点流子类

      继承自 InputStream 的流都是用于向程序中输入数据, 且数据的单位为字节(8bit)

      • int read() 读取一个字节的数据, 并将字节的值作为int类型返回(0-255之间的一个值) 如果未读出字节则返回 -1 (返回值为-1表示读取结束)
      • void close() 关闭输入流对象 释放相关系统资源
  • OutputStream

    • 此抽象类表示字节输出流的所有类的父类, 输出流接收输出字节并将这些字节发送到某个目的地

      • void write(int n) 向目的地中写入一个字节
      • void close() 关闭输出流对象 释放相关系统资源
  • Reader

    • Reader用于读取字符流抽象类, 数据单位为字符

      • int read() 读取一个字符的数据, 并将字符的值作为 int 类型返回 (0-65535 之间的一个值, 即Unicode 值) 如果未读出字符则返回 -1 (返回值为-1表示读取结束)
      • void close() 关闭流对象, 释放相关系统资源
  • Writer

    • Writer 用于输出的字符流抽象类, 数据单位为字符

      • void write(int n) 向输出流中写入一个字符串
      • void close() 关闭输出流对象, 释放相关系统资源

5. Java 中流的概念细分

  • 按流的方向分类:

    • 输入流: 数据流从数据源到程序 (以 InputStream、Reader 结尾的流)
    • 输出流: 数据流从程序到目的地 (以 OutputStream、Writer 结尾的流)
  • 按处理的数据单元分类:

    • 字节流: 以字节为单位获取数据, 命名上以 Stream 结尾的流一般是字节流, 如 FileInputStream、FileOutputStream
    • 字符流: 以字符为单位获取数据, 命名上以 Reader/Writer 结尾的流一般是字符流, 如 FileReader、FileWriter
  • 按处理对象不同分类:

    • 节点流: 可以直接从数据源或目的地读写数据, 如 FileInputStream、FileReader、DataInputStream等
    • 处理流: 不直接连接到数据源或目的地, 是"处理流的流" 通过对其他流的处理提高程序的性能, 如 BufferedInputStream、BufferedReader等, 处理流也叫包装流

    节点流处于 IO 操作的第一线, 所有操作必须通过它们进行, 处理流可以对节点流进行包装, 提高性能 提高程序的灵活性

在这里插入图片描述

6. Java 中IO流类的体系

Java 为我们提供了多种多样的 IO 流, 我们可以根据不同的功能及性能要求挑选合适的 IO 流

简单总结:

  1. InputStream/OutputStream
    • 字节流的抽象类
  2. Reader/Writer
    • 字符流的抽象类
  3. FileInputStream/FileOutputStream
    • 节点流: 以字节为单位直接操作"文件"
  4. ByteArrayInputStream/ByteArrayOutputStream
    • 节点流: 以字节为单位直接操作"字节数组对象"
  5. ObjectInputStream/ObjectOutputStream
    • 处理流: 以字节为单位直接操作"对象"
  6. DataInputStream/DataOutputStream
    • 处理流: 以字节为单位直接操作"基本数据类型与字符串类型"
  7. FileReader/FileWriter
    • 节点流: 以字符为单位直接操作"文本文件" (注意: 只能读写文本文件)
  8. BufferedReader/BufferedWriter
    • 处理流: 将Reader/Writer 对象进行包装, 增加缓存功能, 提高读写效率
  9. BufferedInputStream/BufferedOutputStream
    • 处理流: 将InputStream/OutputStream 对象进行包装, 增加缓存功能, 提高读写效率
  10. InputStreamReader/OutputStreamWriter
    • 处理流: 将字节流对象转化成字符流对象
  11. PrintStream
    • 处理流: 将OutputStream 进行包装, 可以方便地输出字符, 更加灵活

二. IO流入门案例

1. 第一个简单的IO流程序

当程序需要读取数据源的数据时, 就会通过 IO 流对象开启一个通向数据源的流, 通过这个IO流对象的相关方法可以顺序读取数据源中的数据

    public static void main(String[] args) {
    
    
        FileInputStream fis = null;
        try {
    
    
            // 创建字节输入流对象
            fis = new FileInputStream("/Users/angel/a.txt");	// 文件中写的是abc
            int s1 = fis.read();    // 打印输入字符a对应的ASCII码 97
            int s2 = fis.read();    // 打印输入字符a对应的ASCII码 98
            int s3 = fis.read();    // 打印输入字符a对应的ASCII码 99
            int s4 = fis.read();
            System.out.println(s1);
            System.out.println(s2);
            System.out.println(s3);
            System.out.println(s4); // -1

        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            if (fis != null) {
    
    
                try {
    
    
                    fis.close();
                } catch (Exception e) {
    
    
                    e.printStackTrace();
                }
            }
        }
    }

2. 改造入门案例

	public static void main(String[] args) {
    
    

        FileInputStream fis = null;
        try {
    
    
            // 创建字节输入流对象
            fis = new FileInputStream("/Users/angel/CODE/javaio-test/a.txt");
            StringBuilder sb = new StringBuilder();
            int temp = 0;
            while ((temp = fis.read()) != -1) {
    
    
                System.out.println(temp);
                sb.append((char)temp);
            }
            System.out.println(sb);
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                if (fis != null) {
    
    
                    fis.close();
                }
            } catch (IOException e) {
    
    
                e.printStackTrace();
            }
        }
    }

三. File 类的使用

1. File类简介

1.1 File 类的作用

File类是Java提供的针对磁盘中的文件或目录转换对象的包装类, 一个File对象而可以代表一个文件或目录, File 对象可以实现获取文件和目录属性等功能, 可以实现对文件和目录的创建, 删除功能

1.2 File 类操作目录与文件的常用方法

1.2.1 针对文件操作的方法

createNewFile() 创建新文件

delete() 直接从磁盘上删除

exists() 查询磁盘中的文件是否存在

getAbsolutePath() 获取绝对路径

getPath() 获取相对路径

getName() 获取文件名 相当于调用了一个 toString 方法

isFile() 判断是否是文件

length() 查看文件中的字节数

isHidden() 测试文件是不是一个隐藏文件

1.2.2 针对目录操作的方法

exists() 查询目录是否存在

isDirectory() 判断当前路径是否为目录

mkdir() 创建目录

getParentFile() 获取当前目录的父级目录

list() 返回一个字符串数组, 包含目录中的文件和目录的路径名

listFiles 返回一个File数组, 表示用此抽象路径名表示的目录中的文件

2. File的基本使用

2.1 操作文件

				// 创建File对象
        File file = new File("/Users/angel/CODE/java-io-test/aa.txt");
//        File file = new File("/Users/angel/.zshrc");    // 隐藏文件


        System.out.println(file.createNewFile()); // 创建成功会返回true 已存在返回false
//        System.out.println(file.delete());      // 删除成功返回true 反之返回false
        System.out.println(file.exists());  // 判断文件是否存在 存在返回true
//        System.out.println(file.getAbsoluteFile()); // 返回绝对路径
//        System.out.println(file.getPath());         // 返回相对路径
        System.out.println(file.getName());     // 返回文件名+后缀
        System.out.println(file.isFile());     // 文件存在返回true
        System.out.println(file.isHidden());     // 判断是否为隐藏文件

2.2 操作目录

    public static void main(String[] args) {
    
    
        // 创建File对象
        File file = new File("/Users/angel/CODE/java-io-test/b/c");
//        System.out.println(file.mkdir());   // 创建单级目录 创建成功返回true
//        System.out.println(file.mkdirs()); // 创建多级目录    创建成功返回true
//        System.out.println(file.exists());  // 判断文件夹是否存在 存在返回true
//        System.out.println(file.isFile());      // 判断是否为文件 是返回true
//        System.out.println(file.isDirectory()); // 判断是否为文件夹 是返回true
//        System.out.println(file.getParent());       // 返回父级目录字符串类型路径
//        System.out.println(file.getParentFile());       // 返回父级目录File对象
//        System.out.println(file.getParentFile().getName());   // 返回目录名字

        File file2 = new File("/Users/angel");
        String[] arr = file2.list();    // 获取全部文件和文件夹包括隐藏文件 返回字符串数组
        for (String temp : arr) {
    
    
            System.out.println(temp);
        }
        System.out.println("--------------");
        File[] arr2 = file2.listFiles();// 获取全部文件和文件夹包括隐藏文件的绝对路径 返回File数组
        for (File temp : arr2) {
    
    
            System.out.println(temp);
        }

    }

四. 常用流对象

1. 文件字节流

FileInputStream 通过字节的方式读取文件, 适合读取所有类型的文件 (图像、视频、文本文件等), Java也提供了FileReader专门读取文本文件

FileOutputStream 通过字节的方式写数据到文件中, 适合所有类型的文件, Java也提供了FileWriter 专门写入文本文件

1.1 文件字节输入流

	public static void main(String[] args) {
    
    
        FileInputStream fis = null;
        try {
    
    
            // 创建文件字节输入流对象
            fis = new FileInputStream("/Users/angel/CODE/java-io-test/abc.gif");
            int temp = 0;
            while ((temp = fis.read()) != -1) {
    
    
                System.out.println(temp);
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                if (fis != null) {
    
    
                    fis.close();
                }
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }

    }

1.2 文件字节输出流

    public static void main(String[] args) {
    
    
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
    
    
            // 创建文件字节输入流对象
            fis = new FileInputStream("/Users/angel/CODE/java-io-test/abc.gif");
            // 创建字节输出流对象
            fos = new FileOutputStream("/Users/angel/CODE/java-io-test/aa.gif");
            int temp = 0;
            while ((temp = fis.read()) != -1) {
    
    
                fos.write(temp);    // 写入
            }
            fos.flush();    // 将数据从内存中写入到磁盘中

        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                if (fis != null) {
    
    
                    fis.close();
                }
                if (fos != null) {
    
    
                    fos.close();
                }
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }

    }

1.3 通过缓冲区提高读写效率

1.3.1 方式一

通过创建一个指定长度的字节数组作为缓冲区, 以此来提高IO流的读写效率, 该方式适用于读取较大图片时的缓冲区定义 注意: 缓冲区的长度一定是2的整数幂, 一般情况下1024长度较为合适

    public static void main(String[] args) {
    
    
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
    
    
            // 创建文件字节输入流对象
            fis = new FileInputStream("/Users/angel/CODE/java-io-test/123.mov");
            // 创建字节输出流对象
            fos = new FileOutputStream("/Users/angel/CODE/java-io-test/456.MP4");
            // 创建一个缓冲区, 提高读写效率
            byte[] buff = new byte[1024];
            int temp = 0;
            while ((temp = fis.read(buff)) != -1) {
    
     //
//                fos.write(temp);
                fos.write(buff, 0, temp);   // 从 0 写到 temp
            }
            fos.flush();    // 将数据从内存中写入到磁盘中

        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                if (fis != null) {
    
    
                    fis.close();
                }
                if (fos != null) {
    
    
                    fos.close();
                }
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }
    }

1.3.2 方式二

通过创建一个字节数组作为缓冲区, 数组长度时通过流入对象的available() 返回当前文件的预估长度来定义的, 是在一次读写操作中完成文件读写操作的, 注意: 如果文件过大, 那么对内存的占用也是比较大的, 所以大文件不建议使用该方法

    public static void main(String[] args) {
    
    
        FileInputStream fis = null;
        FileOutputStream fos = null;
        try {
    
    
            // 创建文件字节输入流对象
            fis = new FileInputStream("/Users/angel/CODE/java-io-test/123.mov");
            // 创建字节输出流对象
            fos = new FileOutputStream("/Users/angel/CODE/java-io-test/456.MP4");
            // 创建一个缓冲区, 提高读写效率
            byte[] buff = new byte[fis.available()];    // 返回当前文件读取的预估大小的 int 值 文件如果过大不建议使用
            fis.read(buff);
            fos.write(buff);
            fos.flush();    // 将数据从内存中写入到磁盘中

        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                if (fis != null) {
    
    
                    fis.close();
                }
                if (fos != null) {
    
    
                    fos.close();
                }
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }
    }

1.4 通过字节缓冲流提高读写效率

Java 缓冲流本身并不具有IO流的读取与写入功能, 只是在别的流(节点流或其他处理流) 上加上缓冲功能提高效率, 就像是把别的流包装起来一样, 因此缓冲流是一种处理流(包装流)

当对文件或者其他数据源进行频繁的读写操作时, 效率比较低, 这时如果使用缓冲流就能够更高效的读写信息, 因为缓冲流是先将数据缓存起来, 然后当缓存区存满后或者手动刷新时再一次性的读取到程序或写入目的地

因此, 缓冲流还是很重要的, 我们在 IO 操作时记得加上缓冲流来提升性能

BufferedInputStream 和 BufferedOutputStream 这两个流是缓冲字节流, 通过内部缓存数组来提高操作流的效率

    public static void main(String[] args) {
    
    

        FileInputStream fis = null;
        FileOutputStream fos = null;
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;

        try {
    
    
            fis = new FileInputStream("/Users/angel/CODE/java-io-test/123.mov");
            bis = new BufferedInputStream(fis);

            fos = new FileOutputStream("/Users/angel/CODE/java-io-test/0.0.mp4");
            bos = new BufferedOutputStream(fos);
            // 缓冲流中的 Byte数组长度默认是8192
            int temp = 0;
            while ((temp = bis.read()) != -1) {
    
    
                bos.write(temp);
            }
            bos.flush();

        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                // 注意: 关闭流顺序 >>> 后开的先关闭
                if (bis != null) {
    
    
                    bis.close();
                }
                if (fis != null) {
    
    
                    fis.close();
                }
                if (bos != null) {
    
    
                    bos.close();
                }
                if (fos != null) {
    
    
                    fos.close();
                }
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }
    }

1.5 定义文件拷贝工具类

public static void main(String[] args) {
    
    
        copyFile("/Users/angel/CODE/java-io-test/abc.gif", "/Users/angel/CODE/java-io-test/aa.gif");
    }

    // 文件拷贝方法
    public static void copyFile (String src, String des) {
    
    
        BufferedInputStream bis = null;
        BufferedOutputStream bos = null;

        try {
    
    
            bis = new BufferedInputStream(new FileInputStream(src));
            bos = new BufferedOutputStream(new FileOutputStream(des));
            int temp = 0;
            while ((temp = bis.read()) != -1) {
    
    
                bos.write(temp);
            }
            bos.flush();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                if (bis != null) {
    
    
                    bis.close();
                }
                if (bos != null) {
    
    
                    bos.close();
                }
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }
    }

2. 文件字符流

前面的文件字节流可以处理所有的文件, 如果我们处理的是文本文件, 也可以使用文件字符流, 它以字符为单位进行操作

2.1 文件字符输入流

    public static void main(String[] args) {
    
    
        FileReader frd = null;
        try {
    
    
            // 创建文件字符输入流对象
            frd = new FileReader("/Users/angel/CODE/java-io-test/a.txt");
            int temp = 0;
            while ((temp = frd.read()) != -1) {
    
    
                System.out.println((char) temp);
            }


        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                if (frd != null) {
    
    
                    frd.close();
                }
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }

    }

2.2 文件字符输出流

    public static void main(String[] args) {
    
    

        FileWriter fw = null;
        FileWriter fw2 = null;
        try {
    
    
            // 创建字符输出流对象
            fw = new FileWriter("/Users/angel/CODE/java-io-test/hello.txt");
            fw.write("HelloWorld\r\n");
            fw.write("你好世界");

            fw.flush();

            fw2 = new FileWriter("/Users/angel/CODE/java-io-test/hello.txt", true); // 第二个参数是否追加 true追加 默认是false不追加
            fw2.write("何以解忧\r\n唯有暴富");  // 会把 fw 的覆盖掉
            fw2.flush();
        } catch (Exception e) {
    
    

            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                if (fw != null) {
    
    
                    fw.close();
                }
                if (fw2 != null) {
    
    
                    fw2.close();
                }
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }

    }

2.3 使用字符流实现文本文件的拷贝处理

    public static void main(String[] args) {
    
    
        //
        copy("/Users/angel/CODE/java-io-test/hello.txt", "/Users/angel/CODE/java-io-test/bb.txt");
    }

    public static void copy(String src, String bse) {
    
    
        FileReader fr = null;
        FileWriter fw = null;

        try {
    
    
            fr = new FileReader(src);
            fw = new FileWriter(bse);
            char[] buffer = new char[1024];
            int temp = 0;
            while ((temp = fr.read(buffer)) != -1) {
    
    
                fw.write(buffer, 0, temp);
            }

            fw.flush();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                if (fr != null) {
    
    
                    fr.close();
                }
                if (fw != null) {
    
    
                    fw.close();
                }
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }
    }

3. 字符缓冲流

BufferedReader/BufferedWriter 增加了缓存机制, 大大提高了读写文本文件的效率

3.1 字符输入缓冲流

BufferedReader 是针对字符输入流的缓冲流对象, 提供了更方便的按行读取的方法: readLine();在使用字符流读取文本文件时, 我们可以使用该方法以行为单位进行读取

    public static void main(String[] args) {
    
    

        FileReader fr = null;
        BufferedReader br = null;

        try {
    
    
            fr = new FileReader("/Users/angel/CODE/java-io-test/hello.txt");
            br = new BufferedReader(fr);

            String temp = "";
            while ((temp = br.readLine()) != null) {
    
        // 用null 判断
                System.out.println(temp);
            }

        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                if (br != null) {
    
    
                    br.close();
                }
                if (fr != null) {
    
    
                    fr.close();
                }
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }
    }

3.2 字符输出缓冲流

BufferedWriter 是针对字符输出流的缓冲流对象, 在字符输出缓冲流中可以使用newLine(); 方法实现换行处理

    public static void main(String[] args) {
    
    
        // /Users/angel/CODE/java-io-test/hello.txt
        FileWriter fw = null;
        BufferedWriter bw = null;

        try {
    
    
            fw = new FileWriter("/Users/angel/CODE/java-io-test/bwReader.txt");
            bw = new BufferedWriter(fw);
            bw.write("你好oldBoy");
            bw.write("你好世界");
            bw.newLine();   // 换行
            bw.write("何以解忧");
            bw.newLine();
            bw.write("唯有暴富");
            bw.flush();

        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                if (bw != null) {
    
    
                    bw.close();
                }
                if (fw != null) {
    
    
                    fw.close();
                }
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }
    }

3.3 通过字符缓冲流实现文本文件拷贝

    public static void main(String[] args) {
    
    
        copyFile("/Users/angel/CODE/java-io-test/bwReader.txt", "/Users/angel/CODE/java-io-test/newReader.txt");
    }

    // 基于字符缓冲流实现文件拷贝
    public static void copyFile(String src, String des) {
    
    
        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
    
    
            br = new BufferedReader(new FileReader(src));
            bw = new BufferedWriter(new FileWriter(des));
            String temp = "";
            while ((temp = br.readLine()) != null) {
    
    
                bw.write(temp);
                bw.newLine();
            }
            bw.flush();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                if (br != null) {
    
    
                    br.close();
                }
                if (bw != null) {
    
    
                    bw.close();
                }
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }
    }

3.4 通过字符缓冲流为文件中的内容添加行号

    public static void main(String[] args) {
    
    

        BufferedReader br = null;
        BufferedWriter bw = null;

        try {
    
    
            br = new BufferedReader(new FileReader("/Users/angel/CODE/java-io-test/newReader.txt"));
            bw = new BufferedWriter(new FileWriter("/Users/angel/CODE/java-io-test/newReader2.txt"));

            String temp = "";
            int i = 1;
            while ((temp = br.readLine()) != null) {
    
    
                bw.write(i + "." + temp);
                bw.newLine();
                i ++;
            }
            bw.flush();

        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                if (br != null) {
    
    
                    br.close();
                }
                if (bw != null) {
    
    
                    bw.close();
                }

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

4. 转换流

InputStreamReader/OutputStreamWriter 用来实现将字节流转化成字符流, 比如如下场景:

System.in 是字节流对象, 代表键盘的输入, 如果我们想按行接收用户的输入时, 就必须用到缓冲字符流BufferedReader 特有的方法 readLine(), 但是经过观察会发现在创建BufferedReader的构造方法的参数必须是一个Reader对象, 这时候我们的转换流InputStreamReader 就派上用场了

而System.out 也是字节流对象, 代表输入到显示器, 按行读取用户的输入后, 并且要将读取的一行字符串直接显示到控制台, 就需要用到字符流的write(String str) 方法, 所以我们要使用OutputStreamWriter 将字节流转换为字符流

4.1 通过转换流实现键盘输入屏幕输出

    public static void main(String[] args) {
    
    
        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
    
    
            br = new BufferedReader(new InputStreamReader(System.in));
            bw = new BufferedWriter(new OutputStreamWriter(System.out));
            while (true) {
    
    
                bw.write("请输入>>: ");
                bw.flush();
                String input = br.readLine();
                if ("exit".equals(input)) {
    
    
                    break;
                }
                bw.write("你输入的是: " + input);
                bw.newLine();
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                if (bw != null) {
    
    
                    bw.close();
                }
                if (br != null) {
    
    
                    br.close();
                }
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }

    }

4.2 通过字节流读取文本文件并添加行号

    public static void main(String[] args) {
    
    
        BufferedReader br = null;
        BufferedWriter bw = null;
        try {
    
    
                    // 字符缓冲流    字节流转为字符流的转换流对象        文件字节输入流
            br = new BufferedReader(new InputStreamReader(new FileInputStream("/Users/angel/CODE/java-io-test/hello.txt")));
            //   字符输出缓冲流  文件字节输出流转换为文件自负输出流的转换流   文件字节输出流
            bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("/Users/angel/CODE/java-io-test/hello2.txt")));

            String temp = "";
            int i = 1;
            while ((temp = br.readLine()) != null) {
    
    
                bw.write(i + "." + temp);
                bw.newLine();
                i ++;
            }
            bw.flush();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                if (br != null) {
    
    
                    br.close();
                }
                if (bw != null) {
    
    
                    bw.close();
                }
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }
    }

5. 字符输出流

在 Java 的IO流中专门提供了用于字符串输出的流对象PrintWriter, 该对象具有自动行刷新缓冲字符输出流, 特点是可以按行写出字符串, 并且可通过 println() 方法实现自动换行

    public static void main(String[] args) {
    
    
        BufferedReader br = null;
        PrintWriter pw = null;
        try {
    
    
            br = new BufferedReader(new InputStreamReader(new FileInputStream("/Users/angel/CODE/java-io-test/hello.txt")));
            pw = new PrintWriter("/Users/angel/CODE/java-io-test/hello4.txt");

            String temp = "";
            int i = 1;
            while ((temp = br.readLine()) != null) {
    
    
                pw.println(i + "." + temp);
                i ++;
            }
            pw.flush();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                if (br != null){
    
    
                    br.close();
                }
                if (pw != null) {
    
    
                    pw.close();
                }
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }
    }

6. 字节数组流

ByteArrayInputStream 和 ByteArrayOutputStream 经常用在需要流和数组之间转化的情况

6.1 字节数组输入流

说白了, FileInputStream 是吧文件当做数据源, ByteArrayInputStream 则是把内存中的"字节数组对象"当做数据源

    public static void main(String[] args) {
    
    
        byte[] arr = "abcdefg".getBytes();
        ByteArrayInputStream bis = null;
        StringBuilder sb = new StringBuilder();
        try {
    
    
            // 该构造方法的参数是一个字节数组 这个字节数组就是数据源
            bis = new ByteArrayInputStream(arr);
            int temp = 0;
            while ((temp = bis.read()) != -1) {
    
    
                sb.append((char) temp);
            }
            System.out.println(sb.toString());

        } finally {
    
    
            try {
    
    
                bis.close();
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }

    }

6.2 字节数组输出流

ByteArrayOutputStream 流对象是将流中的数据写入到字节数组中

    public static void main(String[] args) {
    
    
        ByteArrayOutputStream bos = null;
        try {
    
    
            StringBuilder sb = new StringBuilder();
            bos = new ByteArrayOutputStream();
            bos.write('a');
            bos.write('b');
            bos.write('c');
            byte[] arr = bos.toByteArray(); // toByteArray将输出流转换为数组
            for (byte b : arr) {
    
    
                sb.append((char) b);
            }
            System.out.println(sb);
        } finally {
    
    
            try {
    
    
                if (bos != null) {
    
    
                    bos.close();
                }
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }
    }

7. 数据流

数据流将"基本数据类型与字符串类型"作为数据源, 从而允许程序以与机器无关的方式从底层输入输出流中操作Java基本数据类型与字符串类型

DataInputStream 和 DataOutputStream 提供了可以存取与机器无关的所有Java数据类型

7.1数据输出流

    public static void main(String[] args) {
    
    
        DataOutputStream dos = null;
        try {
    
    
            dos = new DataOutputStream(new BufferedOutputStream(new FileOutputStream("/Users/angel/CODE/java-io-test/data.txt")));
            try {
    
    
                dos.writeChar('a');
                dos.writeInt(10);
                dos.writeDouble(Math.random());
                dos.writeBoolean(true);
                dos.writeUTF("你好世界");
                dos.flush();
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }

        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                if (dos != null) {
    
    
                    dos.close();
                }
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }
    }

7.2 数据输入流

    public static void main(String[] args) {
    
    
        DataInputStream dis = null;
        try {
    
    
            dis = new DataInputStream(new BufferedInputStream(new FileInputStream("/Users/angel/CODE/java-io-test/data.txt")));
            // 直接读取数据 注意: 读取的顺序一定要与写入的顺序一致 否则不能正确的读出
            System.out.println("char: " + dis.readChar());
            System.out.println("int: " + dis.readInt());
            System.out.println("double: " + dis.readDouble());
            System.out.println("boolean: " + dis.readBoolean());
            System.out.println("String: " + dis.readUTF());

        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                if (dis != null) {
    
    
                    dis.close();
                }
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }
    }

8. 对象流

对象的本质是用来组织和存储数据的, 对象本身也是数据, 那么, 能不能将对象存储到硬盘上的文件中呢? 能不能将对象通过网络传输到另一个电脑呢? 可以通过序列化和反序列化来实现这些需求

8.1 Java 中对象的序列化和反序列化

8.1.1 序列化和反序列化是什么

当两个进程远程通信时, 彼此可以发送各种类型的数据, 无论是何种类型的数据, 都会以二进制序列化的形式在网络上传送, 比如 我们可以通过http协议发送字符串信息; 我们也可以在网络上直接发送Java对象, 发送方需要把这个Java对象转换成为字节序列, 才能在网络上传送, 接收方则需要把字节序列再恢复为Java对象才能正常读取.

把Java对象转换为字节序列的过程称为对象的序列化, 把字节序列恢复为Java对象的过程称为对象的反序列化

对象序列化的作用有如下两种:

  • 持久化: 把对象的字节序列永久的保存到硬盘上, 通常存放在一个文件中
  • 网络通信: 在网络上传送对象的字节序列,比如: 服务器之间的数据通信、对象传递

8.1.2 序列化涉及的类和接口

  • ObjectOutputStream 代表对象输出流,它的 writeObject(Object obj) 方法可对参数指定的 obj 对象进行序列化,把得到的字节序列写到一个目标输出流中
  • ObjectInputStream 代表对象输入流,它的 readObject() 方法从一个源输入流中读取字节序列,再把它们反序列化为一个对象,并将其返回
  • 只有实现了 Serializable 接口的类的对象才能被序列化。Serializable 接口是一个空接口,只起到标记作用

8.2 操作基本数据类型

我们之前学到的数据流只能实现对基本数据类型和字符串类型的读写,并不能对Java对象进行读写操作(字符串除外),但是在对象流中除了能实现对基本数据类型进行读写操作以外,还可以对Java对象进行读写操作

8.2.1 写出基本数据类型数据

    public static void main(String[] args) {
    
    

        ObjectOutputStream oos = null;

        try {
    
    
            oos = new ObjectOutputStream(new BufferedOutputStream(new FileOutputStream("/Users/angel/CODE/java-io-test/abc5.txt")));
            oos.writeInt(10);
            oos.writeDouble(Math.random());
            oos.writeChar('a');
            oos.writeBoolean(true);
            oos.writeUTF("你好 coolGuy");
            oos.flush();
        }catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                if (oos != null) {
    
    
                    oos.close();
                }
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }
    }

8.2.2 读取基本数据类型数据

    public static void main (String[] args) {
    
    
        ObjectInputStream ois = null;

        try {
    
    
            ois = new ObjectInputStream(new BufferedInputStream(new FileInputStream("/Users/angel/CODE/java-io-test/abc5.txt")));
            // 必须要按照写入的顺序读取数据
            System.out.println("int " + ois.readInt());
            System.out.println("double " + ois.readDouble());
            System.out.println("char " + ois.readChar());
            System.out.println("boolean " + ois.readBoolean());
            System.out.println("String " + ois.readUTF());

        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                if (ois != null) {
    
    
                    ois.close();
                }
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }
    }

8.3 操作对象

8.3.1 将对象序列化到文件

ObjectOutputStream 可以将一个内存中的 Java 对象通过序列化的方式写入到磁盘的文件中。被序列化的对象必须要实现 Serializable 序列化接口,否则会抛出异常

8.3.1.1 创建对象
public class Users implements Serializable {
    
    
    private int userid;
    private String username;
    private String userAge;

    public Users(int userid, String username, String userAge) {
    
    
        this.userid = userid;
        this.username = username;
        this.userAge = userAge;
	 }
}
8.3.1.1 序列化对象
public static void main(String[] args) {
    
    
        ObjectOutputStream oos = null;

        try {
    
    
            oos = new ObjectOutputStream(new FileOutputStream("/Users/angel/CODE/java-io-test/abc6.txt"));
            Users users = new Users(1, "coolGuy", "18");
            oos.writeObject(users);

            oos.flush();

        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                if (oos != null) {
    
    
                    oos.close();
                }
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }
    }

8.3.2 将对象反序列化到内存中

public class ObjectInputStreamObjectTypeDemo {
    
    

    public static void main(String[] args) {
    
    
        ObjectInputStream ois = null;

        try {
    
    
            ois = new ObjectInputStream(new FileInputStream("/Users/angel/CODE/java-io-test/abc6.txt"));
            Users users = (Users)ois.readObject();
            System.out.println(users.getUserid() + "\t" + users.getUsername() + "\t" + users.getUserAge());

        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                if (ois != null) {
    
    
                    ois.close();
                }
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }
}

9. 随机访问流

RandomAccessFile 可以实现两个作用:

  1. 实现对一个文件做读和写的操作
  2. 可以访问文件的任意位置, 不像其他流只能按照先后顺序读取

在打开某些客户端软件时, 经常用到这个功能强大的可以"任意操作文件内容"的类, 比如, 软件的使用次数和使用日期, 可以通过本类访问文件中保存次数和日期的地方进行比对和修改. Java很少开发客户端软件, 所以在Java开发中这个类用的相对较少

学习这个流我们需要掌握三个核心方法:

  1. RandomAccessFile(String name, String mode) name 用来确定文件 mode取 r(读) 或 rw(可读可写), 通过 mode 可以确定流对文件的访问权限
  2. seek(long a) 用来定位流对象读写文件的位置, a 确定读写位置距离文件开头的字节个数
  3. getFilePointer() 获得流的当前读写位置
    public static void main(String[] args) {
    
    

        RandomAccessFile raf = null;
        try {
    
    
            raf = new RandomAccessFile("/Users/angel/CODE/java-io-test/abc7.txt", "rw");
            // 将若干数据写入到文件当中
            int[] arr = new int[]{
    
    10, 20, 30, 40, 50, 60, 70, 80, 90, 100};
            for (int i = 0; i < arr.length; i++) {
    
    
                raf.writeInt(arr[i]);
            }
            raf.seek(4); // 移动四个字节 到20;
            System.out.println(raf.readInt());

            // 隔一个读一个数据
            for (int i = 0; i < arr.length; i+=2) {
    
    
                raf.seek(i * 4L);
                System.out.print(raf.readInt() + "\t");
            }
            System.out.println();
            // 在第8个字节位置插入一个新的数据 45,替换之前的数据 30
            raf.seek(8);
            raf.writeInt(45);
            for (int i = 0; i < arr.length; i+=2) {
    
    
                raf.seek(i * 4L);
                System.out.print(raf.readInt() + "\t");
            }
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                if (raf != null) {
    
    
                    raf.close();
                }
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }
    }

10. File类在IO中的作用

当以文件作为数据源或目标时,除了可以使用字符串作为文件以及位置的指定以外,我们也可以使用File类指定

    public static void main(String[] args) {
    
    

        BufferedReader br = null;
        BufferedWriter bw = null;

        try {
    
    
            br = new BufferedReader(new FileReader(new File("/Users/angel/CODE/java-io-test/newReader2.txt")));
            bw = new BufferedWriter(new FileWriter(new File("/Users/angel/CODE/java-io-test/abc8.txt")));
            String temp = "";
            int i = 1;
            while ((temp = br.readLine()) != null) {
    
    
                bw.write(temp + ".."+ i);
                bw.newLine();
                i ++;
            }
            bw.flush();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            try {
    
    
                if (br != null) {
    
    
                    br.close();
                }
                if (bw != null) {
    
    
                    bw.close();
                }
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }
    }

五. Apache IO包

JDK中提供的文件操作相关的类, 但是功能都非常基础, 进行复杂操作时需要做大量编程工作. 实际开发中, 往往需要你自己动手编写相关的代码, 尤其在遍历目录时, 经常用到递归, 非常繁琐. Apache-commons 工具包中提供了 IOUtils/FileUtils, 可以让我们非常方便的对文件和目录进行操作.

Apache IOUtils 和 FileUtils 类库为我们提供了更加简单、功能更加强大的文件操作和IO流操作功能。 非常值得大家学习和使用

1. Apache 基金会介绍

Apache 软件基金会(也就是 Apache Software Foundation, 简称为 ASF),是专门为支持开源软件项目而办的一个非盈利性组织, 在它所支持的Apache 项目与自项目中,所发行的软件铲平都遵循 Apache 许可证(Apache License). 官方网址为: www.apache.org .

很多著名的Java开源项目社区都来源于这个组织。 比如 commpns、kafka、lucene、maven、shiro、struts 等技术,以及大数据技术中的: hadoop (大数据第一技术)、hbase、spark、storm、mahout 等

2. 下载与添加 commons-io 包

2.1 下载地址

https://commons.apache.org/proper/commons-io/download_io.cgi

2.2 添加 jar包

在 IntelliJ idea 中找到项目结构在库中添加 commons-io-2.11.0

3. FileUtils 的使用

FileUtils 类中常用方法:

打开 FileUtils 的 api 文档,我们抽出一些工作中比较常用的方法,进行总结

  • cleanDirectory: 清空目录,但不删除目录
  • contentEquals: 比较两个文件的内容是否相同
  • copyDirectory: 将一个目录内容拷贝到另一个目录,可以通过 FileFilter 过滤需要拷贝的文件
  • copyFile: 将一个文件拷贝到一个新的地址
  • copyFileToDirectory: 将一个文件拷贝到某个目录下
  • copyInputStreamToFile: 将一个输入流中的内容拷贝到某个文件
  • deleteDirectory: 删除目录
  • deleteQuietly: 删除文件
  • listFiles: 列出指定目录下的所有文件
  • openInputStream: 打开指定文件的输入流
  • readFileToString: 将文件内容作为字符串返回
  • readLines: 将文件内容按行返回到一个字符串数组中
  • size: 返回文件或目录的大小
  • write: 将字符串内容直接写到文件中
  • writeByteArrayToFile: 将字节数组内容写到文件中
  • writeLines: 将容器中的元素的 toString 方法返回的内容依次写入文件中
  • w riteStringToFile: 将字符串内容写到文件中

3.1 FileUitls 的使用一

    public static void main(String[] args) {
    
    
        
        try {
    
    
            String content = FileUtils.readFileToString(new File("/Users/angel/CODE/java-io-test/bb.txt"), "utf-8");
            System.out.println(content);

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

3.2 FileUitls 的使用二

    public static void main(String[] args) throws IOException {
    
    
        FileUtils.copyDirectory(new File("/Users/angel/CODE/java-io-test/a"), new File("/Users/angel/CODE/java-io-test/b"), new FileFilter() {
    
    
            // 在文件拷贝时的过滤条件
            @Override
            public boolean accept(File pathname) {
    
    
                // 过滤文件拷贝的条件
                return pathname.isDirectory() || pathname.getName().endsWith("html");
            }
        });
    }

4. IOUtils 的使用

打开IOUtils 的api文档,我们发现它的方法大部分都是重载的,所以,我们理解它的方法并不是难事。因此,对于方法的用法总结如下:

  • buffer 将传入的流进行包装,变成缓冲流。并可以通过参数指定缓冲大小
  • closeQueitly 关闭流
  • contentEquals 比较两个流中的内容是否一致
  • copy 将输入流中的内容拷贝到输出流中,并可以指定字符编码
  • copyLarge 将输入流中的内容拷贝到输出流中,适合大于 2G 的内容拷贝
  • linelterator 返回可以迭代每一行内容的迭代器
  • read 将输入流中的部分内容读到字节数组中
  • readFully 将输入流中的所有内容读入到字节数组中
  • readLine 读入输入流内容中的每一行
  • toBufferedInputStream,toBufferedReader 将输入转为带缓存的输入流
  • toByteArray、toCharArray 将输入流的内容转化为字节数组、字符数组
  • toString 将输入流或数组中的内容转化为字符串
  • write 向流里面写入内容
    public static void main(String[] args) throws Exception {
    
    

        String content = IOUtils.toString(new FileInputStream("/Users/angel/CODE/java-io-test/bb.txt"), "utf-8");
        System.out.println(content);
    }

六. 本章总结

  • 按流的方向分类
    • 输入流: 数据源到程序(InputStream、Reader读进来)
    • 输出流: 程序到目的地(OutputStream、Writer写出去)
  • 按流的处理数据单元分类
    • 字节流: 按照字节读取数据(InputStream、OutputStream)
    • 字符流: 按照字符读取数据(Reader、Writer)
  • 按流的功能分类
    • 节点流: 可以直接从数据源或目的地读写数据
    • 处理流: 不直接连接到数据源或目的地,是处理流的流,通过对其他流的处理提高程序的性能
  • IO 的四个基本抽象类: InputStream、OutputStream、Reader、Writer
  • InputStream 的实现类:
    • FileInputStream
    • ByteArrayInputStream
    • BufferedInputStream
    • DataInputStream
    • ObjectInputStream
  • OutputStream 的实现类:
    • FileOutputStream
    • ByteArrayOutputStream
    • BufferedOutputStream
    • DataOutputStream
    • ObjectOutputStream
    • PrintStream
  • Reader 的实现类:
    • FileReader
    • BufferedReader
    • InputStreamReader
  • Writer 的实现类
    • FileWriter
    • BufferedReader
    • OutputStreamReader
  • 把 Java 对象转换为字节序列的过程称为对象的序列化
  • 把字节序列恢复为 Java 对象的过程称为对象的反序列化

猜你喜欢

转载自blog.csdn.net/m0_51967587/article/details/119605059
今日推荐