文件内容的读写 IO —— 数据流
- 一、什么是数据流
- 二、字节流输入InputStream
- 2.1 InputStream 概述
- 2.2 FileInputStream 的使用
- 三、字节流输出OutputStream
- 3.1 OutputStream 概述
- 3.2 FileOutputStream 的使用
- 四、字符流输入 Reader
- 4.1 Reader 与 FileReader
- 4.2 利用 Scanner 进行字符读取
- 五、字符流输出 Writer
- 5.1 Writer 与 FileWriter
- 5.2 利用 PrintWriter 进行字符写入
- 六、内存泄漏问题
- 七、例题
一、什么是数据流
流:stream
把读写文件操作比喻成"水流",水的特点:流动起来绵延不断~~
输入还是输出,是以CPU/内存为中心来判断!!!
二、字节流输入InputStream
2.1 InputStream 概述
方法
返回值类型 | 方法签名 | 说明 |
---|---|---|
int | read() | 读取一个字节的数据,返回 -1 代表已经完全读完了 |
int | read(byte[] b) | 最多读取 b.length 字节的数据到 b 中,返回实际读到的数量;-1 代表以及读完了 |
int | read(byte[] b, int off, int len) | 最多读取 len - off 字节的数据到 b 中,从 off 开始放入,返回实际读到的数量;-1 代表以及读完了 |
void | close() | 关闭字节流 |
说明
InputStream 只是一个抽象类,要使用还需要具体的实现类。关于 InputStream 的实现类有很多,基本可以认为不同的输入设备都可以对应一个 InputStream 类,我们现在只关心从文件中读取,所以使用 FileInputStream。
2.2 FileInputStream 的使用
构造方法
签名 | 说明 |
---|---|
FileInputStream(File file) | 利用 File 构造文件输入流 |
FileInputStream(String name) | 利用文件路径构造文件输入流 |
代码示例
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class Demo {
public static void main(String[] args) throws IOException {
InputStream inputStream = new FileInputStream("./bbb.txt"); // 内容为"hello world"
while (true) {
int b = inputStream.read();
if (b == -1) {
break;
}
System.out.println(b);
}
inputStream.close();
}
}
运行结果:
三、字节流输出OutputStream
3.1 OutputStream 概述
方法
返回值类型 | 方法签名 | 说明 |
---|---|---|
void | write(int b) | 写入要给字节的数据 |
void | write(byte[] b) | 将 b 这个字符数组中的数据全部写入 os 中 |
int | write(byte[] b, int off, int len) | 将 b 这个字符数组中从 off 开始的数据写入 os 中,一共写 len 个 |
void | close() | 关闭字节流 |
void | flush() | 重要:我们知道 I/O 的速度是很慢的,所以,大多的 OutputStream 为了减少设备操作的次数,在写数据的时候都会将数据先暂时写入内存的一个指定区域里,直到该区域满了或者其他指定条件时才真正将数据写入设备中,这个区域一般称为缓冲区。但造成一个结果,就是我们写的数据,很可能会遗留一部分在缓冲区中。需要在最后或者合适的位置,调用 flush(刷新)操作,将数据刷到设备中。 |
说明
OutputStream 同样只是一个抽象类,要使用还需要具体的实现类。我们现在还是只关心写入文件中,所以使用 FileOutputStream。
3.2 FileOutputStream 的使用
构造方法
签名 | 说明 |
---|---|
FileOutputStream(File file) | 利用 File 构造文件输出流 |
FileOutputStream(String name) | 利用文件路径构造文件输出流 |
代码示例
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class Demo {
// 进行写文件
public static void main(String[] args) throws IOException {
OutputStream outputStream = new FileOutputStream("./bbb.txt");
outputStream.write(97);
outputStream.write(98);
outputStream.write(99);
outputStream.close();
}
}
四、字符流输入 Reader
4.1 Reader 与 FileReader
同样,Reader是一个抽象类,需要实现类FileReader!
方法
代码示例
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
public class Demo {
public static void main(String[] args) throws IOException {
// 使用字符流读文件
Reader reader = new FileReader("./bbb.txt");
while (true) {
int ret = reader.read();
if (ret == -1) {
break;
}
char ch = (char)ret;
System.out.println(ch);
}
reader.close();
}
}
4.2 利用 Scanner 进行字符读取
上述例子中,我们看到了对字符类型直接使用 InputStream 进行读取是非常麻烦且困难的,所以,我们使用一种我们之前比较熟悉的类来完成该工作,就是 Scanner 类。
构造方法
签名 | 说明 |
---|---|
Scanner(InputStream is, String charset) | 使用 charset 字符集进行 is 的扫描读取 |
代码示例
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;
public class Demo {
public static void main(String[] args) throws IOException {
// 使用 Scanner 读文本文件
InputStream inputStream = new FileInputStream("./bbb.txt");
Scanner scanner = new Scanner(inputStream);
while (scanner.hasNext()) {
System.out.println(scanner.next());
}
inputStream.close();
}
}
五、字符流输出 Writer
5.1 Writer 与 FileWriter
同样,Writer是一个抽象类,需要实现类FileWriter!
方法
代码示例
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
public class Demo {
public static void main(String[] args) throws IOException {
// 使用字符流来写文件.
Writer writer = new FileWriter("./bbb.txt");
writer.write("hello world");
writer.close();
}
}
5.2 利用 PrintWriter 进行字符写入
代码示例
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
public class Demo {
public static void main(String[] args) throws IOException {
// 针对写文本文件来说, 还可以使用 PrintWriter 来简化开发.
try (OutputStream outputStream = new FileOutputStream("./bbb.txt")) {
PrintWriter writer = new PrintWriter(outputStream);
writer.println();
writer.printf("a = %d\n", 10);
}
}
}
若没有写进文件中,可能是因为没有刷新缓冲区,此时可以调用 flush() 方法!!!
六、内存泄漏问题
客户端一般不害怕这种资源泄露问题 (客户端程序用一会儿就关了);服务器是害怕这种问题的 (服务器需要长期运行)!!!
一些代码中,如果 close() 前面代码抛出异常了,此时 close() 就执行不到了!因此我们通常要使用 finally 或 try with resources 结构来避免内存泄漏问题!!!:
把要关闭的对象写到 try() 里,当 try 结束,就会自动调用到对应对象的 close() 方法!
并且支持一个 () 放多个对象,多个对象的创建之间使用 ;
分隔就行了~~
七、例题
例一: 扫描指定目录,并找到名称中包含指定字符的所有普通文件 (不包含目录),并且后续询问用户是否要删除该文件
代码实现:
import java.io.File;
import java.io.IOException;
import java.util.Scanner;
public class Demo {
public static void main(String[] args) throws IOException {
// 1. 让用户输入了必要的信息
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要扫描的路径: ");
File rootDir = new File(scanner.next());
if (!rootDir.isDirectory()) {
System.out.println("您输入的目录不存在!");
return;
}
System.out.println("请输入要搜索的关键词: ");
String toDelete = scanner.next();
// 2. 遍历目录, 需要借助一个核心方法, listFiles()
scanDir(rootDir, toDelete);
}
// 借助这个方法进行递归遍历
private static void scanDir(File rootDir, String toDelete) throws IOException {
System.out.println("当前访问: " + rootDir.getCanonicalPath());
File[] files = rootDir.listFiles();
if (files == null) {
// 说明 rootDir 是一个空的目录~~
return;
}
// 如果目录非空, 则循环遍历每个元素.
for (File f : files) {
if (f.isDirectory()) {
scanDir(f, toDelete);
} else {
// 不是目录, 普通文件, 判定文件名是否符合要求, 是否要进行删除
checkDelete(f, toDelete);
}
}
}
private static void checkDelete(File f, String toDelete) throws IOException {
if (f.getName().contains(toDelete)) {
System.out.println("该单词" + toDelete + " 被 " + f.getCanonicalPath() + " 包含了, 是否要删除? (Y/N)");
Scanner scanner = new Scanner(System.in);
String choice = scanner.next();
if (choice.equals("Y") || choice.equals("y")) {
f.delete();
}
}
}
}
例二: 进行普通文件的复制
代码实现:
import java.io.*;
import java.util.Scanner;
public class Demo {
public static void main(String[] args) {
// 实现文件复制.
// 1. 先输入要复制哪个文件(源文件), 以及把这个文件复制到哪里去(目标文件)~~
Scanner scanner = new Scanner(System.in);
System.out.println("请输入源文件: ");
// srcFile 形如 d:/cat.jpg
File srcFile = new File(scanner.next());
System.out.println("请输入目标文件: ");
// destFile 形如 d:/cat2.jpg
File destFile = new File(scanner.next());
if (!srcFile.isFile()) {
System.out.println("输入的源文件有误!");
return;
}
if (!destFile.getParentFile().isDirectory()) {
System.out.println("输入的目标文件有误!");
return;
}
if (destFile.isDirectory()) {
System.out.println("输入的目标文件有误!");
return;
}
// 2. 打开源文件, 按照字节读取内容, 依次写入到目标文件中.
try (InputStream inputStream = new FileInputStream(srcFile);
OutputStream outputStream = new FileOutputStream(destFile)) {
// 读 src 的每个字节, 写入到 dest 中.
while (true) {
int ret = inputStream.read();
if (ret == -1) {
break;
}
outputStream.write(ret);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
例三: 扫描指定目录,并找到名称或者内容中包含指定字符的所有普通文件(不包含目录)
代码实现:
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;
public class Demo {
public static void main(String[] args) throws IOException {
// 1. 输入路径和要查询的关键词
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要扫描的路径: ");
File rootDir = new File(scanner.next());
System.out.println("请输入要查询的词: ");
String toFind = scanner.next();
// 2. 递归的扫描目录.
scanDir(rootDir, toFind);
}
private static void scanDir(File rootDir, String toFind) throws IOException {
File[] files = rootDir.listFiles();
if (files == null) {
return;
}
for (File f : files) {
if (f.isDirectory()) {
scanDir(f, toFind);
} else {
checkFile(f, toFind);
}
}
}
private static void checkFile(File f, String toFind) throws IOException {
// 1. 先检查文件名
if (f.getName().contains(toFind)) {
System.out.println(f.getCanonicalPath() + " 文件名中包含 " + toFind);
}
// 2. 再检查文件内容
try (InputStream inputStream = new FileInputStream(f)) {
StringBuilder stringBuilder = new StringBuilder();
Scanner scanner = new Scanner(inputStream);
// 这个地方需要按行读取了~~
while (scanner.hasNextLine()) {
stringBuilder.append(scanner.nextLine() + "\n");
}
if (stringBuilder.indexOf(toFind) > -1) {
System.out.println(f.getCanonicalPath() + " 文件内容包含 " + toFind);
}
}
}
}
注意:我们现在的方案性能较差,所以尽量不要在太复杂的目录下或者大文件下实验!!!