网站公告

本次更新:没啥更新的

“钱塘江上潮信起,今日方知我是我”

Skip to content

字节流 ​

字数: 0 字 时长: 0 分钟

我们必须明确一点:一切文件(文本、视频、图片)的数据都是以二进制形式存储的,传输时也是如此。

所以,字节流可以传输任意类型的文件数据。


1. 字节输出流(OutputStream) ​

java.io.OutputStream 是字节输出流的超类(父类),它定义了一些字节输出流共有的方法:

  1. close():关闭此输出流,并释放与此流相关联的系统资源。
  2. flush():刷新此输出流,并强制缓冲区中的字节写入目的地。
  3. write(byte[] b):将 b.length 个字节从指定的字节数组写入此输出流。
  4. write(byte[] b, int off, int len):从指定字节数组的 off 偏移位置开始,写入 len 个字节。

2. FileOutputStream 类 ​

OutputStream 有很多子类,我们从最简单的一个子类 FileOutputStream 开始。

看名字就知道,它是文件输出流,用于将数据写入文件。

2.1 FileOutputStream 的构造方法 ​

2.1.1 使用文件名创建 FileOutputStream ​

java
String fileName = "example.txt";

FileOutputStream fos =
        new FileOutputStream(fileName);

以上代码使用文件名 "example.txt" 创建一个 FileOutputStream 对象,并将数据写入该文件。

如果文件不存在,则创建一个新文件;如果文件已经存在,则覆盖原有文件。

2.1.2 使用 File 对象创建 FileOutputStream ​

java
File file =
        new File("example.txt");

FileOutputStream fos =
        new FileOutputStream(file);

FileOutputStream 的使用示例:

java
FileOutputStream fos = null;

try {

    fos =
            new FileOutputStream(
                    "example.txt"
            );

    fos.write(
            "kerwim".getBytes()
    );

} catch (
        IOException e
) {

    e.printStackTrace();

} finally {

    if (
            fos != null
    ) {

        try {

            fos.close();

        } catch (
                IOException e
        ) {

            e.printStackTrace();
        }
    }
}

以上代码创建了一个 FileOutputStream 对象,将字符串 "kerwim" 写入到 example.txt 文件中,并在最后关闭输出流。


2.2 FileOutputStream 写入字节数据 ​

使用 FileOutputStream 写入字节数据主要通过 write() 方法:

java
write(int b)

write(byte[] b)

write(byte[] b, int off, int len)

其中:

java
write(byte[] b, int off, int len)

表示:

从 off 索引开始,写入 len 个字节。

2.2.1 写入单个字节 ​

使用:

java
write(int b)

每次可以写入一个字节。

代码如下:

java
// 使用文件名称创建流对象
FileOutputStream fos =
        new FileOutputStream(
                "fos.txt"
        );

// 写出数据
fos.write(97);
fos.write(98);
fos.write(99);

// 关闭资源
fos.close();

字符 a 的 ASCII 值为 97,字符 b 的 ASCII 值为 98,字符 c 的 ASCII 值为 99。

所以上面的代码也可以写成:

java
// 使用文件名称创建流对象
FileOutputStream fos =
        new FileOutputStream(
                "fos.txt"
        );

// 写出数据
fos.write('a');
fos.write('b');
fos.write('c');

// 关闭资源
fos.close();

当使用:

java
write(int b)

写出一个字节时,参数 b 表示要写出的字节的整数值。

由于一个字节只有 8 位,因此最终真正写入的是参数 b 的低 8 位。

例如:

text
-1
    ↓
低 8 位
    ↓
255


256
    ↓
低 8 位
    ↓
0

在 Java 中,整型数据在内存中以二进制补码形式表示。

当一个 int 类型的数据传递给:

java
write(int b)

时,只会写入该整数的低 8 位,高 24 位会被忽略。

例如:

text
0x12345678

对应的二进制可以表示为:

text
0001 0010 0011 0100 0101 0110 0111 1000

调用:

java
write(0x12345678);

时,真正写入的是低 8 位:

text
0111 1000

高 24 位:

text
0001 0010 0011 0100 0101 0110

会被忽略。

0111 1000 对应的十进制数是:

text
120

而 ASCII 码中:

text
120 → x

因此:

java
write(120);
write('x');
write(0x12345678);

最终写出的结果其实都是字符:

text
x

我们来验证一下:

java
FileOutputStream fos5 = null;

try {

    fos5 =
            new FileOutputStream(
                    "example.txt"
            );

    fos5.write(120);
    fos5.write('x');
    fos5.write(0x12345678);

} catch (
        IOException e
) {

    e.printStackTrace();

} finally {

    if (
            fos5 != null
    ) {

        try {

            fos5.close();

        } catch (
                IOException e
        ) {

            e.printStackTrace();
        }
    }
}

来看一下结果:

image-20250321205532167

果然是 3 个 x。


2.2.2 写入字节数组 ​

使用:

java
write(byte[] b)

代码示例:

java
// 使用文件名称创建流对象
FileOutputStream fos =
        new FileOutputStream(
                "fos.txt"
        );

// 字符串转换为字节数组
byte[] b =
        "kerwim is good".getBytes();

// 写入字节数组数据
fos.write(b);

// 关闭资源
fos.close();

2.2.3 写入指定长度的字节数组 ​

使用:

java
write(byte[] b, int off, int len)

代码示例:

java
// 使用文件名称创建流对象
FileOutputStream fos =
        new FileOutputStream(
                "fos.txt"
        );

// 字符串转换为字节数组
byte[] b =
        "abcde".getBytes();

// 从索引 2 开始写入 2 个字节
// 索引 2 是 c,两个字节就是 cd
fos.write(
        b,
        2,
        2
);

// 关闭资源
fos.close();

2.3 FileOutputStream 实现数据追加、换行 ​

在上面的代码示例中,每次运行程序都会创建新的输出流对象,于是文件中的数据也会被清空。

如果想保留目标文件中的数据,并继续追加新数据,该怎么办?

以及如何实现换行?

其实很简单。

FileOutputStream 还提供了另外两个构造方法。

2.3.1 使用文件名和追加标志创建 ​

java
String fileName =
        "example.txt";

boolean append =
        true;

FileOutputStream fos =
        new FileOutputStream(
                fileName,
                append
        );

以上代码使用文件名 "example.txt" 和追加标志创建一个 FileOutputStream 对象,并将数据追加到文件末尾。

如果文件不存在,则创建新文件;如果文件已经存在,则在文件末尾追加数据。

2.3.2 使用 File 对象和追加标志创建 ​

java
File file =
        new File(
                "example.txt"
        );

boolean append =
        true;

FileOutputStream fos =
        new FileOutputStream(
                file,
                append
        );

这两个构造方法的第二个参数都是 boolean 类型:

text
true
    ↓
追加数据

false
    ↓
不追加,清空原有数据

实现数据追加:

java
// 使用文件名称创建流对象
FileOutputStream fos =
        new FileOutputStream(
                "fos.txt",
                true
        );

// 字符串转换为字节数组
byte[] b =
        "abcde".getBytes();

// 写入数据
fos.write(b);

// 关闭资源
fos.close();

多次运行代码,就会发现数据在不断追加。


2.3.3 换行 ​

在 Windows 系统中,换行符通常是:

text
\r\n

具体代码如下:

java
// 使用文件名称创建流对象
FileOutputStream fos =
        new FileOutputStream(
                "fos.txt",
                true
        );

// 字符串转换为字节数组
byte[] b =
        "abcde".getBytes();

// 写入数据
fos.write(b);

// 关闭资源
fos.close();

在 macOS 系统中,换行符通常是:

text
\n

具体代码如下:

java
String filename =
        "example.txt";

FileOutputStream fos =
        new FileOutputStream(
                filename,
                true
        );

String content =
        "kerwim is good\n";

fos.write(
        content.getBytes()
);

fos.close();

回车符 \r 和换行符 \n 是计算机中常见的控制字符,用于表示一行的结束或换行操作。

在不同的操作系统和编程语言中,它们的使用方式可能有所不同。

Windows 系统中通常使用:

text
\r\n

表示一行结束。

在文本文件中,每行末尾通常由一个回车符和一个换行符组成。

这是因为早期的打印机和终端设备需要回车符和换行符的组合,来完成一行的结束和换行操作。

在 Windows 中,文本编辑器和命令行终端等工具都支持使用:

text
\r\n

表示一行结束。

而在 macOS 和 Linux 系统中,通常只使用:

text
\n

表示一行结束。

在文本文件中,每行末尾通常只有一个换行符。

这是因为早期 Unix 系统中的终端设备只需要换行符,就可以完成一行结束和跨行操作。

在 macOS 和 Linux 中,文本编辑器和终端等工具都支持使用:

text
\n

表示一行结束。

在编程语言中,也经常使用回车符和换行符进行字符串操作。

例如,在 Java 中:

text
\r

表示回车符:

text
\n

表示换行符。

通过输入输出流进行文件读写时,也需要注意回车符、换行符和操作系统之间的差异。


3. 字节输入流(InputStream) ​

java.io.InputStream 是字节输入流的超类(父类),它定义了一些字节输入流共有的方法:

  1. close():关闭此输入流,并释放与此流相关的系统资源。
  2. int read():从输入流读取数据的下一个字节。
  3. read(byte[] b):从输入流中读取数据到字节数组中,返回值表示本次读取到的字节数量;如果读取到文件末尾,则返回 -1。

4. FileInputStream 类 ​

InputStream 有很多子类,我们从最简单的一个子类 FileInputStream 开始。

看名字就知道,它是文件输入流,用于从文件中读取数据。

4.1 FileInputStream 的构造方法 ​

4.1.1 FileInputStream(String name) ​

java
FileInputStream(String name)

创建一个 FileInputStream 对象,并打开指定名称的文件进行读取。

文件名由 name 参数指定。

如果文件不存在,会抛出:

text
FileNotFoundException

4.1.2 FileInputStream(File file) ​

java
FileInputStream(File file)

创建一个 FileInputStream 对象,并打开指定 File 对象所表示的文件进行读取。

代码示例如下:

java
// 创建一个 FileInputStream 对象
FileInputStream fis =
        new FileInputStream(
                "test.txt"
        );

// 读取文件内容
int data;

while (
        (data = fis.read()) != -1
) {

    System.out.print(
            (char) data
    );
}

// 关闭输入流
fis.close();

4.2 FileInputStream 读取字节数据 ​

4.2.1 读取单个字节 ​

read() 方法会读取一个字节,并返回它的整数表示。

如果已经到达文件末尾,则返回:

text
-1

如果读取过程中发生错误,则会抛出:

text
IOException

代码示例如下:

java
// 创建一个 FileInputStream 对象
FileInputStream fis =
        new FileInputStream(
                "test.txt"
        );

// 读取文件内容
int data;

while (
        (data = fis.read()) != -1
) {

    System.out.print(
            (char) data
    );
}

// 关闭输入流
fis.close();

4.2.2 使用字节数组读取 ​

read(byte[] b) 方法会从输入流中最多读取 b.length 个字节,并将读取到的数据保存到字节数组 b 中。

代码示例如下:

java
// 创建一个 FileInputStream 对象
FileInputStream fis =
        new FileInputStream(
                "test.txt"
        );

// 读取文件内容到缓冲区
byte[] buffer =
        new byte[1024];

int count;

while (
        (count = fis.read(buffer)) != -1
) {

    System.out.println(
            new String(
                    buffer,
                    0,
                    count
            )
    );
}

// 关闭输入流
fis.close();

4.3 字节流 FileInputStream 复制图片 ​

原理很简单:

text
图片文件
    ↓
FileInputStream
    ↓
读取字节数据
    ↓
FileOutputStream
    ↓
写入新的图片文件

代码示例如下:

java
// 创建 FileInputStream 对象,读取原始图片文件
FileInputStream fis =
        new FileInputStream(
                "original.jpg"
        );

// 创建 FileOutputStream 对象,写入复制后的图片文件
FileOutputStream fos =
        new FileOutputStream(
                "copy.jpg"
        );

// 创建缓冲区数组,用于存储读取的数据
byte[] buffer =
        new byte[1024];

int count;

// 读取原始图片文件
// 并将数据写入复制后的图片文件
while (
        (count = fis.read(buffer)) != -1
) {

    fos.write(
            buffer,
            0,
            count
    );
}

// 关闭输入流和输出流
fis.close();
fos.close();

上面的代码创建了一个 FileInputStream 对象来读取原始图片文件,并创建一个 FileOutputStream 对象,将数据写入复制后的图片文件。

然后使用 while 循环不断读取原始图片文件中的字节,并将读取到的数据写入新的图片文件。

最后关闭输入流和输出流,释放资源。


5. 小结 ​

InputStream 是字节输入流的抽象类,它定义了读取字节数据的方法,例如:

java
read()

read(byte[] b)

read(byte[] b, int off, int len)

OutputStream 是字节输出流的抽象类,它定义了写入字节数据的方法,例如:

java
write(int b)

write(byte[] b)

write(byte[] b, int off, int len)

这两个抽象类是整个字节流体系的基础。

text
字节流
│
├── InputStream
│       ↓
│   字节输入流
│
└── OutputStream
        ↓
    字节输出流

FileInputStream 是从文件中读取字节数据的流,它继承自 InputStream。

FileOutputStream 是将字节数据写入文件的流,它继承自 OutputStream。

这两个类都是字节流中最常用的实现类之一。

Noctiflorous Garden · MIT License