在 Java API 中,可以从中读取一个字节序列的对象叫做 输入流,可以向其中写入一个字节序列的对象叫做 输出流。这些字节序列的来源地和目的地可以是文件、网络连接、内存块等。

抽象类 InputStreamOutputStream 是构成 输入/输出(I/O)类层次结构的基础。

由于面向字节的流不便处理 Unicode 字符,于是衍生出了字符流,抽象类 ReaderWriter 是构成字符流的基础。

输入流-输出流

分类

  1. 按流的来源地与目的地(读还是写)划分

输入流:从来源地读取数据;有 InputStreamReader

输出流:将数据写入目的地;有 OutputStreamWriter

  1. 按流的操作粒度划分

字节流:以字节为单元;有 InputStreamOutputStream

字符串:以字符为单位;有 ReaderWriter

-字节流字符流
输入流InputStreamReader
输出流OutputStreamWriter

Java I/O 流家族:

Java I/O流

Java IO 的基本用法

字节流

字节流对应的超类是 IntputStreamOutputStream,这两个类不能创建实例,平时使用是选用具体相应的子类来处理的,最常用的类是 FileIntputStreamFileOutputStream

1. IntputStream

InputStream

类详细说明:

ObjectInputStream:对象输入流,用来提供对 基本数据类型对象 的持久存储。

FileInputStream:文件输入流,用于对文件进行读取操作,很常用。

FilterInputStream:装饰者类,具体的装饰者继承该类,这些类都是处理类,用来对节点类进行封装,实现一些特殊功能。

BufferedInputStream:缓存流,内部会有一个缓冲区,用来存放字节,每次将缓存区存满再发送,而不是一个字节或多个字节这样发送,效率更高。

DataInputStream:数据输入流,用来装饰其他输入流。

ByteArrayInputStream:字节数组输入流,从字节数组(byte[])中进行以字节为单位的读取,将资源文件以字节的形式读取到该类中的字节数组中。

PipedInputStream:管道字节输入流,能实现多线程间的管道通信。

常用方法:

// 从数据中读入一个字节,并返回该字节
abstract int read()

// 读入一个字节数组,并返回实际读入的字节数,最多读入 b.length 个字节
int read(byte[] b)

// 读入一个字节数组,返回实际读入的字节数
int read(byte[] b, int off, int len)    

// 在输入流中跳过 n 个字节    
long skip(long n)

// 关闭这个输入流
void close()

2. OutputStream

OutputStream

各个类的说明请参考 InputStream,一个是输入,另一个是输出。

常用方法:

// 将指定的字节写入输出流
abstract void write(int b)

// 将字节数组写入输出流
write(byte[] b)

// 将某个范围的字节写入输出流
write(byte b[], int off, int len)

// 冲刷输出流,也就是将所有缓存的数据发送到目的地
void flush()

// 关闭这个输出流
void close()

由于 InputStreamOutputStreamReaderWriter 都实现了 Closeable 接口,我们可以使用 try-with-resource 语句,无需手动关闭流,流使用结束后会自动关闭。

因此,以下示例代码均使用 try-with-resource 语句。

(1)使用字节流(FileInputStream)读文件

public static void main(String[] args) {    
    File file = new File("R:\\lanweihong.txt");
    try (InputStream inputStream = new FileInputStream(file)) {
        byte[] bytes = new byte[(int)file.length()];
        // 读取数据
        int size = inputStream.read(bytes);
        System.out.println("Size:" + size);
        String text = new String(bytes);
        System.out.println("Text:" + text);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

(2)使用字节流(OutputStream)写文件

public static void main(String[] args) {    
    File file = new File("R:\\lanweihong.txt");
    try (OutputStream outputStream = new FileOutputStream(file)) {
        String text = "blog.lanweihong.com";
        // 输出到文件
        outputStream.write(text.getBytes(StandardCharsets.UTF_8));
    } catch (Exception e) {
        e.printStackTrace();
       }
}

(3)使用字节流拷贝文件

public static void main(String[] args) {    
    File sourceFile = new File("R:\\lanweihong.txt");
    File targetFile = new File("R:\\www.txt");
    try (InputStream inputStream = new FileInputStream(sourceFile);
        OutputStream outputStream = new FileOutputStream(targetFile)) {
        byte[] bytes = new byte[1024];
        int len;
        while ((len = inputStream.read(bytes)) != -1) {
            outputStream.write(bytes, 0, len);
        }
        outputStream.flush();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

(4)使用 BufferedInputStreamBufferedOutputStream

缓冲字节流是为高效率而设计的,真正的读写操作还是靠 FileOutputStreamFileInputStream

public static void main(String[] args) {    
    File sourceFile = new File("R:\\lanweihong.txt");
    File targetFile = new File("R:\\www.txt");
    try (InputStream inputStream = new FileInputStream(sourceFile);
        BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
        OutputStream outputStream = new FileOutputStream(targetFile);
        BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(outputStream)) {
        // 一次读取的字节数
        byte[] bytes = new byte[1024];
        int len = 0;
        // 读取
        while ((len = bufferedInputStream.read(bytes)) != -1) {
            // 写入
            bufferedOutputStream.write(bytes, 0, len);
        }
        bufferedOutputStream.flush();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

字符流

字符流对应的超类是 ReaderWriter,常用的子类有 FileReaderFileWriter

1. Reader

Reader

类详细说明:

InputStreamReader:从字节流到字符流的桥梁(InputStreamReader 构造器参数是 FileInputStream 的实例对象),它读取字节并使用指定的字符集将其编码为字符。

BufferedReader:从字符输入流中读取文本,设置一个缓冲区来提高效率。

FileReader:用于读取字符文件的便利类。

PipedReader:管道字符输入流,实现多线程间的管道通信。

CharArrayReader:从 Char 数组中读取数据的介质流。

StringReader:从String 中读取数据的介质流。

常用方法:

// 读取单个字符
void read()

// 将字符读入数组
int read(char[] cbuf)

// 将字符读入数组某个范围
abstract int read(char[] cbuf, int off, int len)

// 跳过字符
long skip(long n)

// 关闭输入流
abstract void close()

2. Writer

Writer

各个类的说明请参考 Reader,方向相反而已。

常用方法:

// 写入单个字符
void write(int c)

// 写入字符数组
void write(char[] cbuf)

// 写入字符数组的某一范围
abstract void write(char[] cbuf, int off, int len)

// 写入字符串
void write(String str)

// 写入字符串的某一范围
void write(String str, int off, int len)

// 将字符追加到 Writer
Writer append(CharSequence csq)

// 将字符的某一范围追加到 Writer
Writer append(CharSequence csq, int start, int end)

// 将字符追加到 Writer
Writer append(char c)

// 冲刷输出流,也就是将所有缓存的数据发送到目的地
abstract void flush()

// 关闭这个输出流
abstract void close()

(1)以下是 FileReaderFileWriter 读取和写入的例子:

public static void main(String[] args) {
    try (FileReader reader = new FileReader("R:\\reader.txt");
        FileWriter writer = new FileWriter("R:\\writer.txt")) {
        int read;
        // 读取
        while ((read = reader.read()) != -1) {
            // 写入
              writer.write(read);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

(2)使用 BufferedReaderBufferedWriter 读取和写入的例子:

public static void main(String[] args) {
    File sourceFile = new File("R:\\lanweihong.txt");
    File targetFile = new File("R:\\www.txt");
    try(BufferedReader reader = new BufferedReader(new FileReader(sourceFile));
        BufferedWriter writer = new BufferedWriter(new FileWriter(targetFile))) {
        String line;
        // 按行读取字符(不包括换行符)
        while ((line = reader.readLine()) != null) {
            // 写入字符
            writer.write(line);
            // 写入换行符
            writer.newLine();
        }
        writer.flush();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

转换流

转换流是什么?简单理解就是字节流转为字符流或字符流转为字节流,详细的可看 史上最骚最全最详细的IO流教程,没有之一!

转换流

1. InputStreamReader

InputStreamReader 是字节流到字符流的桥梁,它是 Reader 的子类。它读取字节,并使用指定的字符集将其解码为字符。字符集可由名称指定,也可接受平台默认字符集。

使用方法:

InputStreamReader isr = new InputStreamReader(new FileInputStream("R:\\lanweihong.txt"));
// 指定字符集
InputStreamReader isr2 = new InputStreamReader(new FileInputStream("R:\\lanweihong.txt"), StandardCharsets.UTF_8);

2. OutputStreamWriter

OutputStreamWriter 是字符流到字节流的桥梁,是 Writer 的子类。它使用指定的字符集将字符编码为字节。字符集可由名称指定,也可接受平台默认字符集。

使用方法:

OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("R:\\lanweihong.txt"));
// 指定字符集
OutputStreamWriter osw2 = new OutputStreamWriter(new FileOutputStream("R:\\lanweihong.txt"), StandardCharsets.UTF_8);

序列化

Java 提供了一种对象序列化的机制。可以用一个字节序列表示一个对象,该字节序列包含该对象的数据、类型、属性等信息。字节序列写到文件后,相当于在文件中持久保存了一个对象信息。

反之,该字节序列可以从文件中读取并转换为对象,对它进行反序列化。

序列化

如果想要类能够序列化和反序列化,则类必须实现 java.io.Serializable 接口。

1. ObjectInputStream

ObjectInputStream 反序列化流,将之前使用 ObjectOutputStream 序列化的原始数据恢复为对象。

新建 User 对象,其中 password 属性使用 transient 声明,表示该属性不可序列化,序列化时不会输出此属性信息。

public class User implements Serializable {
    private static final long serialVersionUID = 1948088770867387338L;

    private String username;

    /**
     * 使用 transient 声明表示该属性不可序列化,如平时开发开为确保密码安全,不进行序列化
     */
    private transient String password;

    private String email;

    // getter and setter...
}

对象序列化:

public static void main(String[] args) {
    User user = new User();
    user.setUsername("lanweihong");
    user.setPassword("123456");
    user.setEmail("986310747@qq.com");
    try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("R:\\user.txt"))) {
        // 写出对象
        objectOutputStream.writeObject(user);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

2. ObjectOutputStream

对象反序列化:

public static void main(String[] args) {
    try (ObjectInputStream objectInputStream = new java.io.ObjectInputStream(new FileInputStream("R:\\user.txt"))) {
        User user2 = (User)objectInputStream.readObject();
        System.out.println("username:" + user2.getUsername());
        System.out.println("password:" + user2.getPassword());
        System.out.println("email:" + user2.getEmail());
    } catch (Exception e) {
        e.printStackTrace();
    }
}

输出如下,由于 password 属性没有序列化,所以反序列化后获取为 null。

username:lanweihong
password:null
email:986310747@qq.com

参考文献

[1]. 高级Java工程师必备 —– 深入分析 Java IO (三)

[2]. 史上最骚最全最详细的IO流教程,没有之一!

[3]. 【Java基础-3】吃透Java IO:字节流、字符流、缓冲流

[4]. 《Java 核心技术 卷‖》

文章目录