欢迎光临
我们一直在努力

Java文件操作和IO(三)——深入理解数据流与文件内容读写

目录

一、流的基本概念:数据的水管

1.1流的分类

二、字节流:一切数据的基石

2.1抽象基类:InputStream和OutputStream

2.2文件字节流:FileInputStream和FileOutputStream

2.3高效读写:使用缓冲区

三、字符流:处理文本的利器

3.1为什么需要字符流?

3.2使用Scanner读取文本

3.3使用PrintWriter写入文本

四、资源管理:try-with-resources

五、综合实践:三个实用小工具

5.1文件复制(字节流)

5.2扫描目录并删除指定文件

5.3按文件内容搜索(字符流)


一、流的基本概念:数据的水管

        在计算机中,流(Stream)被定义为数据在时间上和空间上的连续传输序列。可以把它想象成一根水管:

        输入流:数据从外部源流进程序,就像水从水龙头流入水杯。

        输出流:数据从程序流向外部,就像水杯里的水倒进水槽。

Java将所有的I/O抽象为流,无论数据源是文件、内存、网络还是其他设备,开发者都可以使用统一的API进行读写。

1.1流的分类

按传输的单位:

        字节流:以字节为单位,适合处理任何类型的数据(图像、音频、视频、文本等)。

        字符流:以字符为单位,专门处理文本数据,支持字符集编码。

按功能角色:

        节点流:直接连接数据源

        处理流:对节点流进行包装,增加额外功能

二、字节流:一切数据的基石

2.1抽象基类:InputStream和OutputStream

        java.io.InputStream是所有字节输入流的父类,定义了最基本的读取方法

方法 说明
int read() 读取一个字节(0~255),若到到达末尾返回-1
int read(byte[] b)

读取最多b.length个字节放入数组,返回实际读取的字节数;

末尾返回-1

int read(byte[] b,

int off ,int len)

读取最多len个字节放入b的off起始位置
void close 释放底层资源,必须调用

        java.io.OutputStream定义了写入方法:

方法 说明
void write(int b)

写入一个字节(int 的低8位)

void write(byte[] b) 写入整个字节数组

void write(byte[] b,

int off , int len)

写入数组从off开始的len个字节
void flush() 强制将缓冲区的数据写入底层设备
void close() 释放资源(通常先flush)

        当你调用write时,数据往往没有真正写在硬盘或网络上,而是被放在了缓冲区,只有缓冲区满了或者流被关闭了,数据才会真正被flush到目标设备上。

        出于防御型编程,建议在close前显式调用一次flush

2.2文件字节流:FileInputStream和FileOutputStream

        这两个类直接连接到文件,是典型的节点流。

构造方法

// 从 File 对象或路径字符串构造
FileInputStream fis = new FileInputStream("data.dat");
FileOutputStream fos = new FileOutputStream("out.dat");
// 第二个参数表示是否追加,默认 false 覆盖
FileOutputStream fosAppend = new FileOutputStream("out.dat", true);

2.3高效读写:使用缓冲区

        单字节读写效率极低(每次调用操作系统底层),通常我们使用固定大小的字节数组作为缓冲区,批量读写。

        读取模板:

try (InputStream is = new FileInputStream("source.dat")) {
byte[] buffer = new byte[1024]; // 1KB 缓冲区
int len;
while ((len = is.read(buffer)) != -1) {
// 处理 buffer 中 [0, len) 的数据
}
}

写入模板:

try (OutputStream os = new FileOutputStream("dest.dat")) {
byte[] buffer = …; // 准备数据
int len = …;
os.write(buffer, 0, len);
os.flush(); // 确保所有数据写入磁盘
}

三、字符流:处理文本的利器

3.1为什么需要字符流?

        字节流虽然通用,但直接处理文本会遇到编码问题。例如,中文字符在UTF-8中可能占3个字节,若用字节流著俄国字节读取,无法得到正确的字符。Java提供了字符流(Reader/Writer),它们以字符(16-bit Unicode)为单位,自动处理字节与字符之间额转换,前提是必须指定正确的字符集。

3.2使用Scanner读取文本

        在基础阶段,我们最熟悉的文本读取工具莫过于Scanner。它不仅能直接从标准输入读取,还可以从任何InputStream读取,并指定字符集

try (InputStream is = new FileInputStream("hello.txt");
Scanner scanner = new Scanner(is, "UTF-8")) {
while (scanner.hasNextLine()) {
String line = scanner.nextLine();
System.out.println(line);
}
}

这种方式简洁直观,适合读取结构化或按行分割的文本。但对于大文件,Scanner有一定的性能开销,此时可以考虑更底层的BufferReader。

3.3使用PrintWriter写入文本

        相比于FileOutputStream.write(byte[] ),PrintWriter提供了我们熟悉的print()、println()和printf()方法,支持格式化输出,且自动处理字符编码。

        通常的包装链:

try (OutputStream os = new FileOutputStream("output.txt");
OutputStreamWriter osw = new OutputStreamWriter(os, "UTF-8");
PrintWriter writer = new PrintWriter(osw)) {
writer.println("我是第一行");
writer.printf("数值:%d%n", 123);
writer.flush();
}

四、资源管理:try-with-resources

        Java 7引入的try-with-resources语法大大简化了资源释放代码。实现类AtuoCloseable接口的类(包括所有I/O流)都可以放在try后的括号中,系统会在try块结束时自动调用close(),无需我们显式写finally。

// 旧方式(易错)
InputStream is = null;
try {
is = new FileInputStream("test.txt");
// …
} catch (IOException e) {
e.printStackTrace();
} finally {
if (is != null) {
try {
is.close();
} catch (IOException e) { /* 忽略 */ }
}
}

// 新方式(推荐)
try (InputStream is = new FileInputStream("test.txt")) {
// …
} catch (IOException e) {
e.printStackTrace();
}

五、综合实践:三个实用小工具

5.1文件复制(字节流)

public static void copyFile(String srcPath, String destPath) throws IOException {
File src = new File(srcPath);
File dest = new File(destPath);
if (!src.exists() || !src.isFile()) {
throw new IllegalArgumentException("源文件不存在或不是普通文件");
}
if (dest.exists() && dest.isFile()) {
// 询问是否覆盖(此处省略交互)
}
try (InputStream is = new FileInputStream(src);
OutputStream os = new FileOutputStream(dest)) {
byte[] buffer = new byte[1024];
int len;
while ((len = is.read(buffer)) != -1) {
os.write(buffer, 0, len);
}
os.flush();
}
}

5.2扫描目录并删除指定文件

private static void scanDir(File dir, String token, List<File> result) {
File[] files = dir.listFiles();
if (files == null) return;
for (File f : files) {
if (f.isDirectory()) {
scanDir(f, token, result);
} else {
if (f.getName().contains(token)) {
result.add(f);
}
}
}
}

5.3按文件内容搜索(字符流)

private static boolean isContentContains(File file, String token) throws IOException {
StringBuilder sb = new StringBuilder();
try (InputStream is = new FileInputStream(file);
Scanner scanner = new Scanner(is, "UTF-8")) {
while (scanner.hasNextLine()) {
sb.append(scanner.nextLine());
sb.append("\\r\\n");
}
}
return sb.indexOf(token) != -1;
}

赞(0)
未经允许不得转载:171主机测评 » Java文件操作和IO(三)——深入理解数据流与文件内容读写
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址