Java IO 流的使用

416 阅读7分钟

这是我参与8月更文挑战的第24天,活动详情查看:8月更文挑战

IO 流简介

输入和输出(Input/Output) 简称 IOJava 中将数据的输入输出抽象为流,流是一组有顺序的,单向的,有起点和终点的数据集合。

流的分类

按照流的流向来分,可以分为输入流和输出流。

  • 输入流:只能从中读取数据,而不能向其写入数据。
  • 输出流:只能向其写入数据,而不能从中读取数据。
  • Java 的输入流主要由 InputStreamReader 作为基类,而输出流则主要由 OutputStreamWriter 作为基类。它们都是一些抽象基类,无法直接创建实例。

按照操作的数据单元不同,可以分为字节流和字符流。

  • 字节流和字符流的用法几乎完全一样,区别在于字节流和字符流所操作的数据单元不同。
  • 字节流操作的数据单元是 8 位的字节,而字符流操作的数据单元是 16 位的字符。
  • 字节流主要由 InputStreamOutputStream 作为基类,而字符流则主要由 ReaderWriter 作为基类。

按照流的角色来分,可以分为节点流和处理流。

  • 可以从/向一个特定的 IO 设备(如磁盘、网络)读/写数据的流,称为节点流,节点流也被称为低级流Low Level Stream)。当使用节点流进行输入/输出时,程序直接连接到实际的数据源,和实际的输入/输出节点连接。
  • 处理流则用于对一个已存在的流进行连接或封装,通过封装后的流来实现数据读/写功能。处理流也被称为高级流。当使用处理流进行输入/输出时,程序并不会直接连接到实际的数据源,没有和实际的输入/输出节点连接。
  • Java 使用处理流来包装节点流是一种典型的装饰器设计模式,通过使用处理流来包装不同的节点流,既可以消除不同节点流的实现差异,也可以提供更方便的方法来完成输入/输出功能。因此处理流也被称为包装流

File 类

File 类是文件和目录路径名的抽象表示。它有如下一些常用方法。

exists()

  • 测试由此抽象路径名表示的文件或目录是否存在,存在返回 true,否则返回 false

createNewFile()

  • 检查文件是否存在,当文件不存在时,原子性地创建一个新空文件。
  • 返回布尔值,如果文件不存在且成功创建时返回 true,否则返回 false
  • 抛出一个 IOException 异常

delete()

  • 删除由此抽象路径名表示的文件或目录。 如果此路径名表示一个目录,则该目录必须为空才能被删除。
  • 当文件无法被删除的时候,delete 方法会抛出 IOException,这对于错误报告和诊断文件为什么不能删除非常有用。
  • 当成功删除文件或目录,返回 true,否则返回 false

isDirectory()

  • 测试由此抽象路径名表示的文件是否为目录。
  • 如果是目录,返回 true,否则返回 false

length()

  • 返回此抽象路径名表示的长度。如果此路径名表示目录,则返回值未指定。
  • 返回文件长度,类型为 long,如果文件不存在,返回 0L

listFiles()

  • 如果抽象路径名表示的是一个目录,则返回由 File 对象组成的数组;如果表示的是一个文件,则返回 null

mkdir()

  • 创建以此抽象路径名命名的目录。
  • 如果成功创建目录,返回 true,否则返回 false

renameTo()

  • 接收一个 File 对象作为参数,将原文件重命名为指定的文件。
  • 如果重命名成功,返回 true,失败则返回 false
import java.io.File;
import java.io.IOException;
import java.util.Arrays;

public class IOTest {
    public static void main(String[] args) {
        File file = new File("test");
        // 文件是否存在
        boolean exists = file.exists();
        System.out.println("exists: " + exists);
        // 创建文件
        if (!exists) {
            try {
                boolean created = file.createNewFile();
                System.out.println("created: " + created);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        // 查看文件长度
        long len = file.length();
        System.out.println("length: " + len);
        // 重命名
        boolean rename = file.renameTo(new File("hello"));
        System.out.println("rename: " + rename);
        // 删除文件
        boolean delete = file.delete();
        System.out.println("delete: " + delete);
        // 判断是否为文件夹
        File dir = new File("dir");
        boolean isDir = dir.isDirectory();
        System.out.println("dir: " + isDir);
        if (isDir) {
            File[] files = dir.listFiles();
            System.out.println(Arrays.toString(files));
        }
        /*
        exists: false
        created: true
        length: 0
        rename: false
        delete: true
        dir: true
        [dir\a, dir\b, dir\subdir]
        */
    }
}

FileInputStream 读取二进制文件

FileInputStream 继承自 InputStream,它从文件中获取输入字节流。

FileInputStream 主要用于读取二进制字节流,如果想要读取字符流,应该使用 FileReader

常用的构造方法:

  • FileInputStream(File file):接收一个 File 对象
  • FileInputStream(String name):接收一个表示文件路径的字符串

常用的方法:

  • read():从输入流中读取一个字节的数据,返回一个 int 类型的数,如果读取到文件末尾,则返回 -1
  • read(byte[] b):接收一个字节数组,从输入流中读取 b.length 个字节的数据,放入字节数组中。返回一个 int 类型的数,表示读取的总字节数,当到达文件末尾时返回 -1
  • close():关闭文件输入流,释放与文件输入流相关的所有系统资源。此方法没有返回值。
import java.io.FileInputStream;
import java.io.IOException;

public class FileInputStreamTest {
    public static void main(String[] args) {
        try {
            FileInputStream fis = new FileInputStream("hello");
            int i = fis.read();
            System.out.println(i); // 97
            i = fis.read();
            System.out.println(i); // 98
            i = fis.read();
            System.out.println(i); // 99
            i = fis.read();
            System.out.println(i); // -1
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

InputStreamReader 读取文本文件

InputStreamReader 是字节流到字符流的桥梁,它读取字节并使用指定的字符集将它们解码成字符。 它使用的字符集可以通过名称指定,也可以显式给出,或者可以使用平台的默认字符集。

每次调用 InputStreamReaderread() 方法,都会从字节流中读取一个或更多字节。为了使字节高效地转换成字符,可能会提前读取当前 read() 操作需要的更多字节。

为了使性能最佳,可以使用 BufferedReaderInputStreamReader 包装起来。比如:

BufferedReader in = new BufferedReader(new InputStreamReader(System.in));

常用方法:

  • getEncoding():返回一个字符串,表示当前输入流使用的字符编码,比如 "UTF8"
  • read():读取一个单独的字符(类型为 int),如果到达流的末尾则返回 -1,会抛出 IOException 异常。
  • read(char[] cbuf, int offset, int length):读取流到目标数组的一部分。cbuf 为目标数组,offset 指定从哪里开始存储字符,length 为读取的最多字符。返回读取的字符数,如果到达流末尾,返回 -1
  • close():关闭流,并释放与之关联的所有系统资源。当流被关闭后,再调用 read()ready()mark()reset() 或者 skip() 都会抛出 IOException 异常。关闭一个之前已经关闭的流则不会有任何效果。

示例代码:

hello 中存着 你好啊 三个字。

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Arrays;

public class InputStreamReaderTest {
    public static void main(String[] args) throws IOException {
        FileInputStream fileInputStream = new FileInputStream("hello");
        InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);
        System.out.println(inputStreamReader.getEncoding()); // UTF8
        int c = inputStreamReader.read();
        System.out.println(c); // 20320
        char[] charArr = new char[10];
        int readed = inputStreamReader.read(charArr, 0, 10);
        System.out.println(Arrays.toString(charArr)); // [好, 啊, , , , , , , , ]
        System.out.println(readed); // 2
        fileInputStream.close();
    }
}

BufferedReader 读取文本文件

BufferedReader 从字符输入流中读取文本,它会缓冲字符,以便为字符、数组和行提供高效读取。可以指定缓冲大小,但是默认大小对于绝大多数用途都是足够的。

通常 Reader 做出的读取请求都会导致相应底层的字节流和字符流做出一次读取请求。因此推荐使用 BufferedReader 将任何开销大的 Reader 包装起来(比如 FileReaderInputStreamReader)。

示例:

BufferedReader in = new BufferedReader(new FileReader("foo.in"));

如果没有缓冲,每次调用 read() 或者 readLine() 将导致字节从文件中读取,转换成字符,然后再返回,这样子开销很大。

示例代码:

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;

public class BufferedReaderTest {
    public static void main(String[] args) throws IOException {
        FileInputStream fileInputStream = new FileInputStream("hello");
        InputStreamReader inputStreamReader = new InputStreamReader(fileInputStream);
        BufferedReader bufferedReader = new BufferedReader(inputStreamReader);
        String line = bufferedReader.readLine();
        System.out.println(line); // 你好
        int c = bufferedReader.read();
        System.out.println(c);
    }
}

参考资料

  • 疯狂 Java 讲义
  • Oracle Java tutorial
  • JDK 8 手册