缓冲流
# 缓冲流
缓冲流,也称为高效流,是对四大基本的文件流(FileInputStream
、FileOutputStream
、FileReader
、FileWriter
)的增强,主要通过设置内置的缓冲区来提高读写数据的效率。缓冲流根据处理的数据类型可以分为字节缓冲流和字符缓冲流。
- 字节缓冲流:
BufferedInputStream
,BufferedOutputStream
- 字符缓冲流:
BufferedReader
,BufferedWriter
缓冲流的基本原理,是在创建流对象时,会创建一个内置的默认大小的缓冲区数组,通过缓冲区读写,减少系统IO次数,从而提高读写的效率。
# 1. 构造方法
public BufferedInputStream(InputStream in)
:创建一个 新的缓冲输入流。public BufferedOutputStream(OutputStream out)
: 创建一个新的缓冲输出流。
构造举例,代码如下:
// 使用FileInputStream创建字节缓冲输入流
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("bis.txt"));
// 使用FileOutputStream创建字节缓冲输出流
BufferedOutputStream bos = new BufferedOutputStream(new FischolarutputStream("bos.txt"));
2
3
4
public BufferedReader(Reader in)
:创建一个 新的缓冲输入流。public BufferedWriter(Writer out)
: 创建一个新的缓冲输出流。
构造举例,代码如下:
// 使用FileReader创建字符缓冲输入流
BufferedReader br = new BufferedReader(new FileReader("br.txt"));
// 使用FileWriter创建字符缓冲输出流
BufferedWriter bw = new BufferedWriter(new FileWriter("bw.txt"));
2
3
4
# 2. 缓冲流与普通流效率对比测试
查询API,缓冲流读写方法与基本的流是一致的,我们通过复制大文件(375MB),测试它的效率。
本示例通过复制一个大文件(比如375MB的文件),来比较使用普通的字节流和使用缓冲流在处理大量数据时的效率差异。
# 2.1 不使用缓冲流复制大文件
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class BufferedIONoBuffer {
public static void main(String[] args) throws IOException {
long startTime = System.currentTimeMillis(); // 记录开始时间
FileInputStream fis = new FileInputStream("jdk8.exe"); // 创建文件输入流
FileOutputStream fos = new FileOutputStream("copy_no_buffer.exe"); // 创建文件输出流
byte[] buffer = new byte[1024]; // 设置一个缓冲区
int len;
while ((len = fis.read(buffer)) != -1) {
fos.write(buffer, 0, len); // 读取并写入数据
}
fos.close(); // 关闭输出流
fis.close(); // 关闭输入流
long endTime = System.currentTimeMillis(); // 记录结束时间
System.out.println("不使用缓冲流复制文件所需时间: " + (endTime - startTime) + " 毫秒");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
在这个示例中,我们通过FileInputStream
和FileOutputStream
进行数据的读取和写入操作,每次读取1KB的数据后即写入文件,直至文件全部复制完成,在不使用缓冲流的情况下花费了870毫秒。
# 2.2 使用缓冲流复制大文件
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class BufferedIOUseBuffer {
public static void main(String[] args) throws IOException {
long startTime = System.currentTimeMillis(); // 记录开始时间
BufferedInputStream bis = new BufferedInputStream(new FileInputStream("jdk8.exe")); // 创建缓冲输入流
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("copy_use_buffer.exe")); // 创建缓冲输出流
byte[] buffer = new byte[1024]; // 设置一个缓冲区
int len;
while ((len = bis.read(buffer)) != -1) {
bos.write(buffer, 0, len); // 读取并写入数据
}
bos.close(); // 关闭输出流
bis.close(); // 关闭输入流
long endTime = System.currentTimeMillis(); // 记录结束时间
System.out.println("使用缓冲流复制文件所需时间: " + (endTime - startTime) + " 毫秒");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
在这个示例中,我们通过BufferedInputStream
和BufferedOutputStream
来创建缓冲流,使用同样大小的缓冲区进行数据的读取和写入操作,花费了256毫秒。
对比结论
通过对比两种方法复制相同文件所需的时间,可以明显看到使用缓冲流(即高效流)进行文件复制的效率远高于直接使用基本的文件流。这主要得益于缓冲流内部使用的缓冲区技术,减少了对磁盘I/O操作的次数,从而显著提高了处理大量数据时的性能。
# 3. 字符缓冲流特有方法
字符缓冲流提供了一些特有的方法,使得读写文本文件时更加方便。下面通过两个示例,分别展示BufferedReader
和BufferedWriter
的特有方法的使用。
# 3.1 BufferedReader的readLine方法
readLine()
方法可以非常方便地读取文本文件中的每一行数据。此方法会自动处理换行符,不将换行符包含在返回的字符串中。当读取到文件末尾时,返回null
。
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class BufferedIOLine {
public static void main(String[] args) {
// 使用BufferedReader读取文本文件中的每一行
try (BufferedReader br = new BufferedReader(new FileReader("in.txt"))) {
String line;
while ((line = br.readLine()) != null) {
System.out.println(line); // 打印读取的每一行
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 3.2 BufferedWriter的newLine方法
newLine()
方法用于在文本文件中写入一个新的行分隔符,行分隔符的具体字符序列由系统属性定义(如Windows中是\r\n
,Linux中是\n
)。这样就不需要关心不同平台的行分隔符差异。
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class BufferedIOLine {
public static void main(String[] args) {
// 使用BufferedWriter在文本文件中写入数据并换行
try (BufferedWriter bw = new BufferedWriter(new FileWriter("out.txt"))) {
bw.write("程"); // 写入单个字符
bw.newLine(); // 写入换行符
bw.write("序");
bw.newLine();
bw.write("员");
bw.newLine(); // 再次写入换行符
} catch (IOException e) {
e.printStackTrace();
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
通过上述两个示例,我们可以看到BufferedReader
的readLine
方法和BufferedWriter
的newLine
方法的使用,极大地简化了文本文件的读写操作。这些特有方法是字符缓冲流相比普通字符流的优势之一。
# 6.4 流的关闭顺序
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
public class IOCloseDemo {
public static void main(String[] args) {
FileWriter fw = null;
BufferedWriter bw = null;
try {
// 创建FileWriter对象,指向"d:/1.txt"
fw = new FileWriter("d:/1.txt");
// 创建BufferedWriter对象,包装FileWriter对象,提高写入效率
bw = new BufferedWriter(fw);
// 使用BufferedWriter对象写入字符串"hello"
bw.write("hello");
/*
* 正确的关闭顺序是先关闭外层流(BufferedWriter),再关闭内层流(FileWriter)。
* 关闭外层流同时会将缓冲区内容刷新到文件,然后再关闭内层流,确保数据的完整性。
*/
bw.close(); // 先关闭BufferedWriter,自动刷新缓冲区内容到文件
fw.close(); // 再关闭FileWriter,释放与文件的连接资源
} catch (IOException e) {
e.printStackTrace();
} finally {
/*
* 在finally块中再次确保所有流都被关闭。
* 这是一种额外的预防措施,以防try块中的代码发生异常导致流没有正确关闭。
*/
try {
if (bw != null) {
bw.close();
}
if (fw != null) {
fw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
关闭顺序的原则和解释
- 外层流先关闭:首先关闭外层流(如
BufferedWriter
),因为关闭外层流在关闭前会自动刷新缓冲区的内容到文件中,确保数据的完整性。如果先关闭内层流(如FileWriter
),那么外层流的关闭动作会尝试刷新缓冲区到已经关闭的内层流,导致IOException
。 - 内层流后关闭:在外层流关闭之后,再关闭内层流。内层流负责与文件系统的直接交互,关闭它会释放系统资源。
- finally中再次检查关闭:在
finally
代码块中再次进行流的关闭操作,是一种防御式编程的做法。即使正常逻辑中已经关闭了流,但如果在关闭之前发生了异常,可能会导致流没有正确关闭。在finally
中检查并关闭流,可以确保无论是否发生异常,资源都能被正确释放。