跳到主要内容Javajava
Java 文件操作与 IO 流
本文介绍了 Java 中文件操作的基本概念,包括绝对路径与相对路径的区别,File 类的常用方法如创建、删除、列出目录等。详细讲解了字节流 InputStream 和 OutputStream 以及字符流 Reader 和 Writer 的原理及使用方式,涵盖 FileInputStream、FileOutputStream、FileReader、FileWriter 的构造与读写操作。最后通过实战演练展示了递归扫描目录、文件复制及内容搜索的实现代码。
GRACE Grace0 浏览 文件
文件有两个概念,在广义来看就是操作系统上对硬件和软件资源抽象为文件。在狭义上来看,就是我们保存在硬盘上的文件。
在这里我们讨论的是狭义的文件,在外面的硬盘上的文件细分又可以分为二进制文件和文本文件。文本文件可以通过码表转换成现实生活中有意义的文字,而二进制文件则是我们看不懂的文件。
我们可以通过记事本打开一个文件,如果里面的乱码则说明这是二进制文件,如果你能看懂说明这是文本文件。其实文件本质上都是二进制文件,上面的文件只是根据文件是否表达人类语言的意义来进行划分的,在计算机里文件的存储都是以二进制的方式进行存储的。
- 绝对路径:就是文件的具体位置信息,没有省略
- 相对路径:
./text.txt 就在当前路径下的 text.txt;../text.txt 表示当前路径的上一个路径
Windows 操作系统路径表示支持斜杠 / 和反斜杠 \ 两种符号,其他操作系统只能使用斜杠 / 来对路径进行分割。
我们使用斜杠 / 来分割路径就可以了,反正 Windows 操作系统也能支持。
File
在 Java 中标准库给我们提供了可以操作文件或者目录的类 File。
注意 File 不仅仅可以操作文件,也可以操作目录。
下面是 File 的构造方法:
File 提供的方法:
import java.io.File;
import java.io.IOException;
public class Demo1 {
public static void main(String[] args) throws IOException {
File file = new File("./test.txt");
System.out.println(file.getPath());
System.out.println(file.getParent());
System.out.println(file.getAbsolutePath());
System.out.println(file.getCanonicalPath());
System.out.println(file.exists());
}
}
首先要注意即使没有存在 file 这个文件,File 在实例化的时候是没有问题的。
public static void main(String[] args) throws IOException {
File file = ();
System.out.println(file.createNewFile());
}
微信扫一扫,关注极客日志
微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
new
File
"./test.txt"
如果文件没有存在,会成功创建并且返回 true,如果文件已经存在就会创建失败并且返回 false。
public static void main(String[] args) throws IOException {
File file = new File("./test.txt");
file.createNewFile();
System.out.println(file.exists());
System.out.println(file.delete());
System.out.println(file.exists());
}
public static void main(String[] args) throws IOException {
File file = new File("./test.txt");
file.createNewFile();
System.out.println(file.exists());
file.deleteOnExit();
System.out.println(file.exists());
}
deleteOnExit() 这个是等到进程结束才删除这个文件。
public static void main(String[] args) {
File file = new File("./text.txt");
file.mkdir();
}
mkdir 是创建一级目录,如果涉及到多级目录的创建是无法完成的,多级目录的创建要使用 mkdirs() 方法。
public static void main(String[] args) {
File file = new File("./text.txt/2005/04/02");
file.mkdirs();
}
list() 和 listFile() 都是获取当前目录下的所有文件,只是返回值类型不同,list() 是返回 String 类型,listFile() 则是返回 File 类型。
public static void main(String[] args) {
File file = new File("D:/code/Java/java_study/J2024_11_2");
String[] list = file.list();
System.out.println(Arrays.toString(list));
File[] list2 = file.listFiles();
System.out.println(Arrays.toString(list2));
}
IO 流
字节流
字节流是以字节为单位的读取,主要用于二进制文件的读写。
字符流在 Java 中有两个类需要掌握,一个是 InputStream,另一个是 OutputStream。
首先我们要知道这两个类都是抽象类,是不能直接实例化的。
输入输出的定义是我们站在 CPU 的视角,数据往 CPU 来就是输入,数据从 CPU 离开就是输出。
因此输入流 InputStream 就是用来读取数据的,OutputStream 是用来写入数据的。
最后,我们知道文件的打开是会消耗资源的,当我们使用完文件之后我们需要关闭资源,避免资源泄漏,其实这也是因为操作系统在每次打开文件,就会占用文件描述表的一个位置,并且这个文件描述表是不会自动扩容的,所以能打开的文件有限,不能只打开不关闭文件,这样文件描述表在某一个时刻就会被消耗殆尽导致后续再打开文件的操作失败引发 BUG。
我们关闭文件除了可以使用 close 方法之外,我们可以直接在 try(打开文件的代码){},当出了 try 代码块就会自动关闭文件,这是因为 Java 的 IO 流的类实现了 Closable 接口。
既然我们不能直接实例化 InputStream 和 OutputStream 的话,我们可以使用 FileInputStream 和 FileOutputStream 来实例化(因为它们分别继承了 InputStream 和 OutputStream),听名字大家应该知道这两个类其实就是用来操作文件的。
FileInputStream
代码演示:首先在 test.txt 文件中写上 hello java
public static void main(String[] args) throws IOException {
try (InputStream inputStream = new FileInputStream("./test.txt")) {
int n = 0;
while (n != -1) {
n = inputStream.read();
System.out.println(n);
}
}
}
read(byte[] b) 演示:这个会首先填满 byte 数组,并且返回一个数值表示读取了多少个字节。
这种也叫做输出型参数,就是我们传入的引用参数到一个方法里面,这个方法会直接修改我们传入的引用类型参数,并且这个参数的修改也影响到方法外部的参数,这种参数我们称之为输出型参数。
public static void main(String[] args) throws IOException {
try (InputStream inputStream = new FileInputStream("./test.txt")) {
byte[] b = new byte[1024];
while (true) {
int n = inputStream.read(b);
if (n == -1) {
break;
}
for (int i = 0; i < n; i++) {
System.out.println(b[i]);
}
}
}
}
FileOutputStream
FileOutputStream 的构造方法和 FileInputStream 的参数是一样的。
如果文件不存在的话,FileOutputStream 会自动创建好文件,然后进行写操作。
public static void main(String[] args) throws IOException {
try (OutputStream outputStream = new FileOutputStream("./test.txt")) {
outputStream.write(65);
outputStream.write(66);
outputStream.write(67);
}
}
public static void main(String[] args) throws IOException {
try (OutputStream outputStream = new FileOutputStream("./test.txt")) {
byte[] b = {97, 98, 99};
outputStream.write(b);
}
}
当你进行写操作的时候,是直接覆盖之前的内容再进行写操作,如果你想要进行的是追加写的话,在构造方法中加上一个 true,这样就是追加写。
public static void main(String[] args) throws IOException {
try (OutputStream outputStream = new FileOutputStream("./test.txt", true)) {
byte[] b = {97, 98, 99};
outputStream.write(b);
}
}
字符流
字符流是以字符为单位进行存储的,主要用于文本文件的读写。
字符流一样给我们提供了两个抽象类 Reader 和 Writer,我们可以通过实例化 FileReader 和 FileWriter。
Reader
read() 是读取一个字符,read(char[] cbuf) 是将字符数组填充读取到的字符,read(CharBuffer target) 其中的 CharBuffer 是对 char[] 进行了封装。
read(char[] cbuf, int off, int len) 就是指定将读取到的字符填充到字符数组的哪个位置。
public static void main(String[] args) throws IOException {
try (Reader reader = new FileReader("./test.txt")) {
while (true) {
char[] b = new char[1];
int n = reader.read(b);
if (n == -1) {
break;
}
for (int i = 0; i < n; i++) {
System.out.println(b[i]);
}
}
}
}
在 Java 中使用 utf8 进行编码,明明一个中文占 3 个字节,为什么我用 char(两个字节) 就可以读取出来???
字符流读取到的是文件中原始的数据也就是 两个汉字占 6 个字节(utf8 编码),字符流会根据文件的编码方式进行解析,read() 一次就会读取到 3 个字节,返回的时候会针对这 3 个字节进行转码,也就是先将这个 3 个字节对照 utf8 码表查询到'你'这个汉字之后,read 又将'你'这个汉字在 unicode 这个码表进行查询得到 unicode 编码,这样就是两个字节,最后将这两个字节的数值返回到 char 变量中。
public static void main(String[] args) throws IOException {
try (Reader reader = new FileReader("./test.txt")) {
char[] arr = new char[2];
while (true) {
int n = reader.read(arr);
if (n == -1) {
break;
}
for (int i = 0; i < n; i++) {
System.out.println(arr[i]);
}
}
}
}
Writer
public static void main(String[] args) throws IOException {
try (Writer writer = new FileWriter("./test.txt")) {
writer.write("hello world");
}
}
public static void main(String[] args) throws IOException {
try (Writer writer = new FileWriter("./test.txt", true)) {
writer.write("hello world");
}
}
实战演练
扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要删除该文件。
在遍历文件的时候,我们只能遍历到一层的文件,如果该文件夹里面还有文件的话,我们需要进行递归才能获取到内部的文件。
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class Demo1 {
public static void main(String[] args) {
System.out.println("请输入指定目录:");
Scanner scan = new Scanner(System.in);
String str = scan.next();
File file = new File(str);
if (!file.isDirectory()) {
System.out.println("您输入的不是目录或者该目录不存在...");
return;
}
System.out.println("请输入指定的字符:");
String token = scan.next();
List<File> list = new ArrayList<>();
scanFile(file, token);
}
private static void scanFile(File file, String token) {
File[] ret = file.listFiles();
if (ret == null) {
return;
}
for (int i = 0; i < ret.length; i++) {
if (ret[i].isDirectory()) {
scanFile(ret[i], token);
} else {
delFile(ret[i], token);
}
}
}
private static void delFile(File file, String token) {
if (file.getName().contains(token)) {
System.out.println("该文件包含你指定的字符内容:" + file.getAbsoluteFile());
System.out.println("是否进行删除:Y / N");
Scanner scan = new Scanner(System.in);
String ret = scan.next();
if (ret.equals("Y")) {
file.delete();
}
}
}
}
import java.io.*;
import java.util.Scanner;
public class Demo2 {
public static void main(String[] args) throws IOException {
System.out.println("请输入你要复制的文件路径:");
Scanner scan = new Scanner(System.in);
String str = scan.next();
File file = new File(str);
if (!file.isFile()) {
System.out.println("你输入的不是文件或者该文件不存在...");
return;
}
System.out.println("请输入你要复制的路径:");
String end = scan.next();
File file2 = new File(end);
if (!file2.getParentFile().isDirectory()) {
System.out.println("要复制到的文件路径不存在...");
return;
}
byte[] b = new byte[1024];
try (InputStream inputStream = new FileInputStream(str); OutputStream outputStream = new FileOutputStream(end)) {
while (true) {
int n = inputStream.read(b);
if (n == -1) {
break;
}
outputStream.write(b, 0, n);
}
}
System.out.println("复制成功");
}
}
扫描指定目录,并找到名称或者内容中包含指定字符的所有普通文件(不包含目录)
import java.io.*;
import java.util.Scanner;
public class Demo3 {
public static void main(String[] args) throws IOException {
System.out.println("请输入你要指定的目录");
Scanner scan = new Scanner(System.in);
String str = scan.next();
File file = new File(str);
if (!file.isDirectory()) {
System.out.println("你输入的不是目录或者该目录不存在");
return;
}
System.out.println("请输入你要指定的内容:");
String token = scan.next();
scanDir(file, token);
}
public static void scanDir(File rootFile, String token) throws IOException {
File[] list = rootFile.listFiles();
for (File file : list) {
if (file.isDirectory()) {
scanDir(file, token);
} else {
checkFile(file, token);
}
}
}
private static void checkFile(File file, String token) throws IOException {
if (file.getName().contains(token)) {
System.out.println("文件名包含指定关键字:" + file.getAbsolutePath());
return;
}
Reader reader = new FileReader(file);
StringBuilder check = new StringBuilder();
char[] b = new char[1024];
while (true) {
int n = reader.read(b);
if (n == -1) {
break;
}
check.append(b);
}
if (check.toString().contains(token)) {
System.out.println("文件内容包含指定关键字:" + file.getAbsoluteFile());
}
}
}