Java流

JAVA流

流的基本概念

流(Stream)在Java中是对数据序列的抽象,用于处理输入输出(I/O)操作。它代表了从一个源(如文件、网络套接字、内存缓冲区)到另一个目的地的数据流动。流不是特指网络套接字,而是涵盖了所有I/O操作,包括文件、控制台、网络等。

在计算机体系中,数据在磁盘、内存和CPU之间流动时,流充当了桥梁角色。但更准确地说,流操作通常涉及:

  • (如磁盘文件)→ 内存(缓冲区)→ 程序(CPU处理)
  • 程序内存(缓冲区)→ 目的(如磁盘文件)

常见的流包括:

  • 标准输入/输出流System.in(标准输入),System.out(标准输出),System.err(标准错误输出)。例如 System.out.println使用了标准输出流。
  • 文件流:用于读写文件,如 FileInputStream, FileOutputStream, FileReader, FileWriter
  • 缓冲流:在基础流之上增加缓冲功能,提高效率,如 BufferedReader, BufferedWriter
  • 数据流:用于读写基本数据类型,如 DataInputStream, DataOutputStream
  • 对象流:用于序列化对象,如 ObjectInputStream, ObjectOutputStream
  • 网络流:用于网络通信,如 Socket相关的流。

文件流操作示例

文件流主要分为字节流(用于处理二进制数据)和字符流(用于处理文本数据,处理字符编码)。

字节流文件复制

1
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
import java.io.*;

public class FileCopyByteStream {
public static void main(String[] args) {
String sourceFile = "1.txt";
String targetFile = "2.txt";
try {
copyByStream(sourceFile, targetFile);
} catch (IOException e) {
System.err.println("文件复制失败: " + e.getMessage());
e.printStackTrace();
}
}

/**
* 使用字节流复制文件(使用try-with-resources自动关闭资源)
* @param sourceDir 源文件路径
* @param targetDir 目标文件路径
* @throws IOException 当IO操作失败时抛出
*/
public static void copyByStream(String sourceDir, String targetDir) throws IOException {
// 使用try-with-resources确保流自动关闭
try (FileInputStream fis = new FileInputStream(sourceDir);
FileOutputStream fos = new FileOutputStream(targetDir)) {

byte[] buffer = new byte[8192]; // 使用8KB缓冲区(通常比1KB更高效)
int length;

while ((length = fis.read(buffer)) != -1) {
fos.write(buffer, 0, length);
}

System.out.println("文件复制完成!");
}
// 无需finally块 - try-with-resources自动处理关闭
}
}

字符流文件复制(改进版)

1
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
import java.io.*;
import java.nio.charset.StandardCharsets;

public class FileCopyCharStream {
public static void main(String[] args) {
String sourceFile = "1.txt";
String targetFile = "2.txt";
try {
copyByCharStream(sourceFile, targetFile);
} catch (IOException e) {
System.err.println("文件复制失败: " + e.getMessage());
e.printStackTrace();
}
}

/**
* 使用字符流复制文本文件(显式指定字符编码)
* @param sourceDir 源文件路径
* @param targetDir 目标文件路径
* @throws IOException 当IO操作失败时抛出
*/
public static void copyByCharStream(String sourceDir, String targetDir) throws IOException {
// 使用StandardCharsets常量
try (FileReader fis = new FileReader(sourceDir, StandardCharsets.UTF_8);
FileWriter fos = new FileWriter(targetDir, StandardCharsets.UTF_8)) {

char[] buffer = new char[8192];
int length;

while ((length = fis.read(buffer)) != -1) {
fos.write(buffer, 0, length);
}

// flush操作会在close()中自动调用,通常无需显式调用
System.out.println("文件复制完成!");
}
}
}

缓冲流的使用(改进版)

缓冲流(Buffered Stream)通过减少底层系统的调用次数来提高I/O效率。它们确实在基础流之上添加了缓冲层,但更准确的说法是:缓冲流在内存中创建缓冲区,减少了对物理设备(如硬盘)的直接访问次数

1
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
import java.io.*;
import java.nio.charset.StandardCharsets;

public class FileCopyBufferedStream {
public static void main(String[] args) {
String sourceFile = "1.txt";
String targetFile = "2.txt";
try {
copyByBufferedStream(sourceFile, targetFile);
} catch (IOException e) {
System.err.println("文件复制失败: " + e.getMessage());
e.printStackTrace();
}
}

/**
* 使用缓冲流复制文件(更高效的IO操作)
* @param sourceDir 源文件路径
* @param targetDir 目标文件路径
* @throws IOException 当IO操作失败时抛出
*/
public static void copyByBufferedStream(String sourceDir, String targetDir) throws IOException {
// 基础流在try-with-resources中创建,缓冲流包装它们
try (FileInputStream fis = new FileInputStream(sourceDir);
FileOutputStream fos = new FileOutputStream(targetDir);
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos)) {

byte[] buffer = new byte[8192];
int length;

while ((length = bis.read(buffer)) != -1) {
bos.write(buffer, 0, length);
}

System.out.println("文件复制完成!");
}
}
}

某些缓冲流(如 BufferedReader)支持标记位置和重置功能。

使用NIO API进行文件复制(现代方法)

Java NIO (New I/O) 提供了更简洁高效的文件操作方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import java.io.IOException;
import java.nio.file.*;

public class FileCopyNIO {
public static void main(String[] args) {
Path sourcePath = Paths.get("1.txt");
Path targetPath = Paths.get("2.txt");

try {
Files.copy(sourcePath, targetPath, StandardCopyOption.REPLACE_EXISTING);
System.out.println("文件复制完成!");
} catch (IOException e) {
System.err.println("文件复制失败: " + e.getMessage());
e.printStackTrace();
}
}
}

NIO是新型的封装好的文件操作方法。

重要注意事项与最佳实践

资源管理总是使用try-with-resources来自动关闭流,避免资源泄漏。

不要依赖手动调用 close(),因为异常可能导致资源无法关闭。

字符编码: 处理文本文件时总是明确指定字符编码(如UTF-8)。

避免使用 FileReaderFileWriter的默认构造函数,因为它们使用平台默认编码,可能导致跨环境问题。

缓冲区大小: 缓冲区大小(如8KB)通常比小缓冲区(如1KB)性能更好,但应通过测试确定最佳大小。

对于大文件,可以考虑使用更大的缓冲区(如64KB或128KB)。

flush操作: 输出流在关闭前会自动flush,通常不需要显式调用 flush()

如果需要确保数据立即写入(如实时日志),可以显式调用 flush()

性能考量: 对于大文件,NIO的 Files.copy()通常是最佳选择。

缓冲流对于多次小量读写操作最有效。