Java IO 教程

Java IO 流(Stream)

Java IO 流是你可以读取或写入的数据流。正如 Java IO 概述中提到的,数据流通常连接到数据源或数据目标,如文件或网络连接。

流不像数组那样有读取或写入数据的索引概念,通常也不能像数组或使用 RandomAccessFile 的文件那样在流中来回移动,数据流只是一个连续的数据流。

一些数据流实现(如 PushbackInputStream)允许将数据推回数据流,以便以后再次读取。但你只能推回有限的数据量,而且不能像数组那样随意遍历数据。数据只能按顺序访问。

Java IO 流通常基于字节(byte)或字符(char)。基于字节的流通常被称为 “流”,如 InputStream 或 OutputStream。除了 DataInputStream 和 DataOutputStream 能读写 int、long、float 和 double 值外,这些流一次只能读写一个原始字节。

基于字符的数据流通常称为“读取器”或“写入器”。基于字符的流可以读/写字符(如 Latin1 或 UNICODE 字符)。

InputStream

java.io.InputStream 类是所有 Java IO 输入流的基类,如下图:

e62cbc6b3413ec3c8d31c14837cd8beb_1735198578783-7db883a3-f4df-493a-80f8-c85b2ff4abee_x-oss-process=image%2Fformat%2Cwebp.png

如果你正在编写一个需要从流中读取输入数据的组件,请尽量使我们的组件依赖 InputStream,而不是它的任何子类(如 FileInputStream)。这样做能让你的代码处理所有类型的输入流,而不是只处理具体的子类。

不过,仅依赖 InputStream 并不总是可行的。如果需要向流推回数据,就必须依赖于 PushbackInputStream,也就是说,你的流变量必须是这种类型。否则,你的代码将无法调用 PushbackInputStream 上的 unread() 方法。

从 InputStream 读取数据时,通常需要调用 read() 方法。read() 方法会返回一个 int,其中包含所读取字节的字节值。如果没有数据可读,read() 方法通常会返回-1;

示例:

package com.hxstrive.java_io.inputstream;

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

/**
* 读取文件示例
* @author hxstrive.com
*/
public class FileInputStreamDemo2 {

   public static void main(String[] args) {
       try(FileInputStream input = new FileInputStream("D:\\input.txt");
           BufferedInputStream inputBuffered = new BufferedInputStream(new FileInputStream("D:\\input.txt"))) {

           // 参数实际类型为 FileInputStream
           byte[] bytes = readFile(input);
           System.out.println(new String(bytes)); // Hello Java IO

           // 参数实际类型为 BufferedInputStream
           bytes = readFile(new BufferedInputStream(inputBuffered));
           System.out.println(new String(bytes)); // Hello Java IO
       } catch (Exception e) {
           e.printStackTrace();
       }
   }

   // 参数使用 InputStream 类型,可以读取任何输入流
   public static byte[] readFile(InputStream input) throws IOException {
       byte[] buffer = new byte[input.available()];
       int len = input.read(buffer);
       return Arrays.copyOf(buffer, len);
   }

}

OutputStream

java.io.OutputStream 类是所有 Java IO 输出流的基类。如下图:

e798bf7f56efa99880b4677a9a4dce5d_1735202056671-6f7a1d3a-d7a4-407d-b1bd-f58351dd95d0_x-oss-process=image%2Fformat%2Cwebp.png

如果你正在编写一个需要将输出写入流的组件,请尽量确保该组件依赖 OutputStream,而不是它的某个具体子类。

示例:

package com.hxstrive.java_io.outputstream;

import java.io.*;

/**
* 写文件示例
* @author hxstrive.com
*/
public class FileOutputStreamDemo2 {

   public static void main(String[] args) {
       File file1 = new File("D:\\output.txt");
       File file2 = new File("D:\\output2.txt");
       try(FileOutputStream output = new FileOutputStream(file1);
           BufferedOutputStream outputBuffered = new BufferedOutputStream(new FileOutputStream(file2));
           FileInputStream input = new FileInputStream(file1);
           FileInputStream input2 = new FileInputStream(file2)) {

           // 写出数据
           writeFile(output, "hello world 1");
           writeFile(outputBuffered, "hello world 2");

           // 读取数据,验证结果
           System.out.println(readFile(input)); // hello world 1
           System.out.println(readFile(input2)); // hello world 2

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

   // 写出数据,接受所有的输出流
   public static void writeFile(OutputStream output, String content) throws IOException {
       output.write(content.getBytes());
       output.flush(); // 强制刷新缓冲区,将数据写出
   }

   // 读取文件内容,返回字符串
   public static String readFile(InputStream input) throws IOException {
       byte[] buffer = new byte[input.available()];
       int len = input.read(buffer);
       return new String(buffer, 0, len);
   }

}
如果我们将上述代码 writeFile() 改为:
// 写出数据,接受所有的输出流
public static void writeFile(FileOutputStream output, String content) throws IOException {
   output.write(content.getBytes());
   output.flush(); // 强制刷新缓冲区,将数据写出
}

那么,writeFile(outputBuffered, "hello world 2") 语句就会出现类型错误。

组合流

你可以将流组合成链,实现更高级的输入和输出操作。例如,从文件中逐个字节读取数据的速度很慢。从磁盘读取一个较大的数据块,然后逐个字节遍历该数据块,速度会更快。要实现缓冲,可以将 InputStream 封装在 BufferedInputStream 中。

示例:

package com.hxstrive.java_io.inputstream;

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

/**
* 读取文件示例
* @author hxstrive.com
*/
public class FileInputStreamDemo3 {

   public static void main(String[] args) throws Exception {
       File file = new File("D:\\input.txt");
       // 文件输入流
       FileInputStream fileInputStream = new FileInputStream(file);
       // 文件输入流读取慢,操作不方便,所以使用缓冲流
       BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);

       byte[] bytes = new byte[1024];
       int len;
       while ((len = bufferedInputStream.read(bytes)) != -1) {
           System.out.println(Arrays.toString(Arrays.copyOf(bytes, len)));
           //[72, 101, 108, 108, 111, 32, 74, 97, 118, 97, 32, 73, 79]
       }

       // 关闭流,释放资源,不要忘了
       // 如果使用 try-with-resources 语句,将自动释放,不需手动操作
       fileInputStream.close();
       bufferedInputStream.close();
   }

}

缓冲也可应用于 OutputStream,从而将写入磁盘(或底层流)的数据分批写入较大的块。这也能提供更快的输出。这可以通过缓冲输出流来实现。

缓冲只是通过组合流实现的效果之一。你还可以将 InputStream 包裹在 PushbackStream 中。这样,你就可以将数据推回流中,以便稍后重新读取。这有时在解析过程中非常方便。或者,你也可以使用 SequenceInputStream 将两个 InputStream 合二为一。

将输入流和输出流组合成链还可以实现其他一些效果。你甚至可以编写自己的流类来封装 Java 自带的标准流类。这样,你就可以创建自己的效果或过滤器。

说说我的看法
全部评论(
没有评论
关于
本网站专注于 Java、数据库(MySQL、Oracle)、Linux、软件架构及大数据等多领域技术知识分享。涵盖丰富的原创与精选技术文章,助力技术传播与交流。无论是技术新手渴望入门,还是资深开发者寻求进阶,这里都能为您提供深度见解与实用经验,让复杂编码变得轻松易懂,携手共赴技术提升新高度。如有侵权,请来信告知:hxstrive@outlook.com
公众号