【java基础】java IO流

284 阅读9分钟

Java IO流 是很重要的一个部分,虽然实际应用中我们更多是使用commons.io.FileUtils这样的工具包,但了解java io流体系对于我们处理一些特殊情形或碰到问题时还是很有必要的。

本文对Java IO流体系进行了全面系统的介绍。

内容包括Java IO流体系基础;java.io和nio(new io)相关API;并通过实例代码演示如何访问文件和目录,如何读取和写入数据等。

1 基础

IO即input/output,是以内存为中心,input是指将数据从外部读入内存,output是指将数据从内存输出到外部。

为了应对多种场景(缓冲、字节、字符、按行读写等),java io类库的设计者设计了众多的类,java io类库包含众多的类。按照不同的分类方式 ,java io流可以分为

  • 数据流向,可以分为输入流输出流;
  • 操作数据划分,可以分为字节流字符流;字节流接口 inputStream、outputStream 字符流接口 Reader/Writer
  • 按io流的角色划分,可以分为节点流处理流; image-20211212100227109

2 操作文件

2.1 java.io.file

file代表的是文件路径,既能表示一个文件,也能表示目录。file的构造方法如下:

import java.io.*;
File file = new File("c:\desktop")

提示:

当构造一个File对象时候,即使传入的文件或目录不存在,代码也不会出错。因为构造一个File对象,并不会发生任何磁盘操作,只有当调用某些方法时,才会进行磁盘操作。

file对象常用的操作如下:

  • 获取路径:getPath()/getAbsolutePath()/getCanonicalPath()
  • 获取目录的文件和子目录:list()/listFiles()
  • 实际创建文件/目录/删除文件:createNewFile()/delete()/deleteOnExit()/mkdir()

java 7 中新添加了Path和Files这两个类,便于对路径和文件进行更加便捷操作,以改善java.io 包中操纵文件时需要写较多冗余的代码的情况。

2.1 java.nio.path

Path类主要用于对路径进行拼接等操作。

java.nio.paths类是构建Path的工厂类,Paths.get()方法接受一个或多个字符串,并将它们用默认的路径分隔符连接起来。构建Path可以通过绝对路径和相对路径两种方式,以根路径开始的路径是绝对路径,否则就是相对路径。

Path absolutePath = Paths.get("C:\test.text")
Path relativePath = Paths.get("test.txt");

Path类有常用方法有以下几个:

  • resolve() 组合或解析路径

  • getParent()获取父路径

  • getRoot()获取根路径

    • toFile() 从该路径创建一个File对象

2.2 java.nio.files

Files类提供了众多操作文件的快捷方法:

读写文件:

  • static byte[] readAllBytes(Path path)
  • static List readAllLines(Path path, Charset charset)
  • static InputStream newInputStream(Path path,opentios...)
  • static BufferReader newBufferReader(Path path, Charset charset)

创建文件和目录:

  • static Path createFile(Path path ,FileAttribute attr)
  • static Path createDirectory
  • static Path createTempFiles 创建临时文件,会在程序结束后自行删除

复制移动和删除文件:

  • static Path copy(Path from ,Path to, CopyOption options)
  • static Path move(Path from, Path to, CopyOption options)
  • static void delete()

3 输入与输出

JAVA IO类库字节流和字符流的常用类的继承层次结构图:

image-20211128211249795

3.1 字节流

JAVA IO类库设计使用InputStreamOutputStream这两个抽象基类作为整个IO类库的基础,我们在实际使用中,更多的是使用这两个基类的子类,以便针对不同的数据类型提供更有用的接口。

3.1.1 inputStream

常见的字节输入流包括FileInputStream、BufferedInputStream等,他们的继承关系如下:

image-20211212110522454

  • InputStream 是字节输入流的抽象基类,它包含以下几个函数:

    • read(byte[] b):从流中读取b的长度个字节的数据存储到b中,返回结果是读取的字节个数(当再次读时,如果返回-1说明到了结尾,没有了数据)
      • read(byte[] b, int off, int len):从流中从off的位置开始读取len个字节的数据存储到b中,返回结果是实际读取到的字节个数(当再次读时,如果返回-1说明到了结尾,没有了数据)
      • close():关闭流,释放资源。
  • FileInputStream主要用来操作文件输入流,它除了可以使用基类定义的函数外,它还实现了基类的read()函数(无参的):

    • read():从流中读取1个字节的数据,返回结果是一个int。
  • BufferedInputStream,继承自FilterInputStream流,属于处理流。主要通过缓冲提高读取的效率。

BufferedInputStream原理:

普通的读是从硬盘里面读,而BufferedInputStream类初始化时会创建一个较大的byte数组,一次性从底层输入流中读取多个字节来填充byte数组,当程序读取一个或多个字节时,可直接从byte数组中获取,当内存中的byte读取完后,会再次用底层输入流填充缓冲区数组,从而减少与磁盘的IO次数,提高读取效率。

3.1.2 outputStream

OutputStream类与InputStream类基本对应,常见的字节输出流如下:

image-20211212110619183

  • OutputStream是字节输出流的基类, OutputStream作为基类,给它的基类定义了几个通用的函数:

      • write(byte[] b):将b的长度个字节数据写到输出流中。
      • write(byte[] b,int off,int len):从b的off位置开始,获取len个字节数据,写到输出流中。
      • flush():主要用于缓冲流,可以强迫输出流(或缓冲的流)发送数据。( 参考: blog.csdn.net/caomiao2006…
      • close():关闭流,释放系统资源。
  • FileOutputStream是用于写文件的输出流,它除了可以使用基类定义的函数外,还实现了OutputStream的抽象函数write(int b):

      • write(int b):将b转成一个字节数据,写到输出流中。
  • BufferedOutputStream像上面那个BufferedInputStream一样,都可以提高效率。它除了可以使用基类定义的函数外,它还实现了OutputStream的抽象函数write(int b):

      • write(int b):将b转成一个字节数据,写到输出流中。

3.2 字符流

InputSream和OutputStream是面向字节流的,为了兼容Unicode与面向字符的I/O功能,java 1.1 增加了ReaderWriter类。

扩展:

字节流处理的最基本的单元是Unicode码元,字符流工作本质实际上也是基于字节的:

  • 输出字符流:把要写入文件的字符序列(实际上是Unicode码元序列)转为指定编码方式下的字节序列,然后再写入到文件中;
  • 输入字符流:把要读取的字节序列按指定编码方式解码为相应字符序列(实际上是Unicode码元序列)从而可以存在内存中。

ReaderWriter类的继承与层次结构与字符流基本类似,部分类结构如下图所示:

image-20211211122831290

3.2.1 Reader

  • Reader是一个抽象基类,不能实例化,主要可以用于接口化编程,它定义了以下几个方法:

    • read() :读取单个字符,返回结果是一个int,需要转成char;到达流的末尾时,返回-1
    • read(char[] cbuf):读取cbuf字符数组长度个字符并填充进cbuf字符数组,返回结果是读取的字符数,到达流的末尾时,返回-1
    • close() :关闭流,释放占用的系统资源。
  • InputStreamReader 是一个转换流,可以把InputStream中的字节数据流根据字符编码方式转成字符数据流。

        public static void InputStreamReaderTest() throws IOException {
            InputStreamReader inputStreamReader = new InputStreamReader(
                    new FileInputStream(""),"utf8"
            );
            // 读入单个字节
            char ch= (char)inputStreamReader.read();
        }
    
  • FileReader 继承自InputStreamReader类,可以把FileInputStream中的字节数据转成根据字符编码方式转成字符数据流。FileReader类的源码如下图: image-20211211143725692

  • BufferedReader 类主要用于对数据进行缓冲,提高读取效率。它除了可以使用基类定义的函数,它自己还实现了以下函数:

    • read(char[] cbuf, int offset, int length) :从offset位置开始,读取length个字符到cbuf中,返回结果是实际读取的字符数,到达流的末尾时,返回-1
    • readLine() :读取一个文本行,以行结束符作为末尾,返回结果是读取的字符串。如果已到达流末尾,则返回 null

    BufferedReader使用如下:

        public static void BufferedReaderTest() throws IOException {
            BufferedReader in =new BufferedReader(
                    new FileReader("")
            );
            System.out.println((char)in.read());
            // 读取行
            String line = in.readLine();
            in.close();
        }
    

3.2.2 Writer

常见的字符输出流有:Writer、OutputStreamWriter、FileWriter、BufferedWriter

  • Writer是字符输出流的抽象基类, ,它定义了以下几个函数
    • write(char[] cbuf) :往输出流写入一个字符数组。
    • write(int c) :往输出流写入一个字符。
    • write(String str) :往输出流写入一串字符串。
    • write(String str, int off, int len) :往输出流写入字符串的一部分。
    • close() :关闭流,释放资源。 【这个还是抽象的,写出来是说明有这个关闭功能】
    • flush():刷新输出流,把数据马上写到输出流中。 【这个还是抽象的,写出来是说明有这个关闭功能】
  • OutputStreamWriter可以使我们直接往流中写字符串数据,它里面会帮我们根据字符编码方式来把字符数据转成字节数据再写给输出流,它相当于一个中介\桥梁。
  • FileWriter与OutputStreamWriter功能类似,我们可以直接往流中写字符串数据,FileWriter内部会根据字符编码方式来把字符数据转成字节数据再写给输出流。
  • BufferedWriter比FileWriter还高级一点,它利用了缓冲区来提高写的效率。它还多出了一个函数:
    • newLine() :写入一个换行符。

3.3 处理流( filter 装饰器模式)

为了使得java IO类库能够实现多种不同功能的组合,Java 设计者了采用装饰器模式,FilterInputStream类就是一个装饰器类的基类。

使用filter模式的一个例子如下:

// 构造一个InputStream
InputStream file = new FileInputStream("test.gz");
// 增加缓冲功能
InputStream buffered = new BufferedInputStream(file);
// 增加处理gzip压缩功能
InputStream gzip = new GZIPInputStream(buffered);

FilterInputStream的部分子类包括:

  • DataInputStream
  • BufferedInputStream
  • CheckedInputStream
  • HttpInputStream

4 java io流的典型使用方式(重要)

4.1 一次性读取文件

以FileInputStream为例演示如何读取一个文件:

public void readFile() throws IOException {
    // 创建一个FileInputStream对象,注意创建完成后只是创建了一个流,并不会直接读取数据
    InputStream input = new FileInputStream("src/readme.txt");
    for (;;) {
        int n = input.read(); // 反复调用read()方法,直到返回-1
        if (n == -1) {
            break;
        }
        System.out.println(n); // 打印byte的值
    }
    input.close(); // 关闭流
}

4.2 缓冲读取文件

采用缓冲可以有效提高效率,降低磁盘与内存之间IO交换次数。

利用缓冲区一次读取多个字节的代码如下:

public void readFile() throws IOException {
   InputStream input = new FileInputStream("src/readme.txt")
    // 定义1000个字节大小的缓冲区:
    byte[] buffer = new byte[1000];
    int n;
    while ((n = input.read(buffer)) != -1) { // 读取到缓冲区
        System.out.println("read " + n + " bytes.");
    }
}

注意:

实际上BufferedInputStream缓冲流实现原理跟上述代码也是类似的。

4.3 写入文件

4.4 try with resource 结束流

在我们编写IO流程序时候,经常需要在程序结束出手动关闭流。try-with-resources语句保证了每个声明了的资源在语句结束的时候都会被关闭。

static String readFirstLineFromFile(String path) throws IOException {
    try (BufferedReader br =
                  new BufferedReader(new FileReader(path))) {
        return br.readLine();
    }
}

参考:

廖雪峰Java教程

缓存字节流BufferedInputStream使用及原理解析

Java:字节流和字符流(输入流和输出流)