目录
前言
在本篇博客中,将主要介绍 的是文件的概念、文件的路径、Java中 文件系统的操作(主要介绍 File类 的各种属性与方法)~
一、文件
1.1 文件的概念File
我们平时所说的文件,一般都是存储在计算机硬件上面的文件,可以是 文本文档、图片、程序等等,一般是以 .jpg 、 .txt 、 .mp3 、 .mp4 、 .pptx 等文件格式 ~
而在计算机中的概念,目录(也就是 文件夹)也是文件的一种,称为 目录文件 ~
1.2 文件的路径
之前代码里写的变量是放在内存中的,现在的文件是放在硬盘上的~
每个文件在硬盘上都有一个具体的“路径”
文件的路径 一般分为两种:一种是 绝对路径,一种是相对路径 ~
绝对路径:以 C: D: 盘符开头 的路径 ~
相对路径:以当前所在目录为基准,以 . 或者 .. 开头的路径(需要先明确一个基准路径,来找到目标的目录)
目录和目录之间,可以用 \ 分割,也可以使用 / 分割;
但是更推荐使用 / 的,因为 \ 在字符串里面表示 转义字符,要想表示分割,就需要 \\,还不如直接用 / 来的更好 !!!
来列举一个例子,以帮助大家更好的理解 绝对路径 和 相对路径 ~
假设 A同学 想要去图书馆看书,但是由于学校非常的大,绕来绕去的就把 A同学 绕晕了 ~
此时,A同学看到 学校大门口旁边有个 警卫,于是就跑过去问路 ~
警卫告诉A同学说:
从校门口出发->沿着这条路一直往前走->走到行政楼后->右转沿着这条路走到池塘附近左转->到了孔子广场再左转就到了
那么,此时 绝对路径 的内容都是固定的,无论 A同学在哪里,这条路经都是不变的 ~
而所谓的相对路径,其实就是相对的(以 A同学自身的位置为基准的):
当 A同学在校门口的时候,相对路径就是:从校门口出发->沿着这条路一直往前走->走到行政楼后->右转沿着这条路走到池塘附近左转->到了孔子广场再左转就到了
当 A同学在行政楼的时候,相对路径就是:右转沿着这条路走到池塘附近左转->到了孔子广场再左转就到了
当 A同学在孔子广场的时候,相对路径就是:左转就到了
在使用 Idea 运行程序的时候,当前的工作目录(取为基准路径)就是项目所在的目录 ~
1.3 文件类型
word文本文档、图片、exe程序、视频、音频、源代码、动态库这些不同的文件,整体可以归纳到两类中。
- 文本文件(存的是文本,字符串):字符串是由字符构成,每个字符都是通过一个数字来表示的,这个文本文件里存的数据一定是合法的字符,都是在你指定字符编码的码表之内的数据
- 二进制文件(存的是二进制数据,不一定是字符串了):没有任何限制,可以存储任何你想要的数据
如何区分文件是文本还是二进制?
直接用记事本打开,乱码说明是二进制,没乱说明是文本的~
二、 文件系统操作
在之前所介绍过的 冯诺依曼体系结构中,我们已经知道,文件是存储在硬盘上的!
复习一下:硬盘(外存) 和 内存的 4 个区别(重点掌握):
- 内存存储空间小,硬盘存储空间大
- 内存访问速度快,硬盘访问速度慢
- 内存成本高,硬盘成本低
- 内存掉电数据丢失,硬盘掉电数据还在
文件也是被操作系统所管理,在操作系统内核中 有一个专门的模块 —— 文件系统(大概会以某种方式来管理硬盘上的文件) ~
在 Java 中针对 文件系统/文件 进行了一系列的封装,我们可以根据 Java API 对文件进行一些操作,专门提供了 File类,以表示一个文件(File里一个静态变量就是跟着/或者\.跟着系统走的)
然后,就可以基于 File类,来完成一些关于文件的操作 ~
- 针对文件系统操作(文件的创建、删除、重命名)
- 针对文件内容操作(文件的读和写)
2.1 File类的属性
2.2 File类的构造方法
parent 表示当前文件所在目录 child 表示自身文件名
像 d:/cat.jpg 这个文件名,parent 就是 d:/ child就是cat.jpg
2.3 File类的方法
2.3.1 File类的常用获取方法
2.3.2 File类的常用判断方法
2.3.3 文件的创建与删除
2.3.4 其他的常用方法
2.4 代码示例演示
2.4.1 演示一:演示File类的一些常见用法
package file;
import java.io.File;
import java.io.IOException;
//演示 file类 的一些常见用法
public class Demo1 {
public static void main(String[] args) throws IOException {
File file = new File("d:/test.txt");
System.out.println("获取到父路径:" + file.getParent());
System.out.println("获取到文件名:" + file.getName());
System.out.println("获取到路径" + file.getPath());
System.out.println("获取到绝对路径:" + file.getAbsolutePath());
System.out.println("获取到修饰过的路径:" + file.getCanonicalPath());
}
}
运行结果:
package file;
import java.io.File;
import java.io.IOException;
//演示 file类 的一些常见用法
public class Demo1 {
public static void main(String[] args) throws IOException {
File file = new File("./test.txt");
System.out.println("获取到父路径:" + file.getParent());
System.out.println("获取到文件名:" + file.getName());
System.out.println("获取到路径" + file.getPath());
System.out.println("获取到绝对路径:" + file.getAbsolutePath());
System.out.println("获取到修饰过的路径:" + file.getCanonicalPath());
}
}
运行结果:
2.4.2 演示二:文件的创建及其相关属性
package file;
import java.io.File;
import java.io.IOException;
public class Demo2 {
public static void main(String[] args) throws IOException {
//前面没有写 ./ ,但是也相当于是 相对路径,即 相当于是有 ./ (./ 可以省略)
File file = new File("HelloWorld.txt");
System.out.println("当前目录下,是否存在该文件:" + file.exists());
System.out.println("判断其是否是目录:" + file.isDirectory());
System.out.println("判断其是否是文件:" + file.isFile());
System.out.println("--------------------------------------------------");
//现在可以创建一个文件
file.createNewFile();
System.out.println("当前目录下,是否存在该文件:" + file.exists());
System.out.println("判断其是否是目录:" + file.isDirectory());
System.out.println("判断其是否是文件:" + file.isFile());
}
}
运行结果:
此时,我们可以看见,程序运行结束后,创建了一个新的文件:
同时,当找到绝对路径去观察时,就会发现真的创建了一个文件:
2.4.3 演示三:文件的删除
第一种删除方法:delete 方法(直接删除)
package file;
import java.io.File;
public class Demo3 {
public static void main(String[] args) {
//文件删除
File file = new File("HelloWorld.txt");
file.delete();
System.out.println(file.exists());
}
}
运行结果:
我们可以看见,"HelloWorld.txt"文件就真的被删除了 ~
打开绝对路径的时候,文件确实被删除了 ~
第二种删除方法:deleteOnExit 方法(程序退出之后再删除,可以用来创建一些临时文件)
偷偷摸摸的又运行了一下 Demo2,以创建出文件 ~
我们可以看见,当运行到打印 文件是否存在 的时候,仍然是文件存在的 ~
当程序运行结束之后,文件才不存在被删除了 ~
同时,当打开绝对路径的时候,确实发现文件没有了 ~
2.4.4 演示四:创建目录
如果我们创建的是单级目录的话,可以使用 mkdir 方法:
import java.io.File;
public class Demo4 {
//创建目录
public static void main(String[] args) {
File file = new File("test");
System.out.println(file.exists());
System.out.println(file.isDirectory());
System.out.println("---------------------");
file.mkdir();
System.out.println(file.exists());
System.out.println(file.isDirectory());
}
}
运行结果:
此时,可以观察到左边,真的创建了一个目录:
当打开绝对路径去观察时,也会发现真的出现了一个目录(文件夹):
当我们想要创建的是多级目录时,使用的就需要是 mkdirs 方法(如果继续使用 mkdir 方法,就会发现它是错误的)~
package file;
import java.io.File;
public class Demo4 {
//创建目录
public static void main(String[] args) {
File file = new File("test/aa/1");
System.out.println(file.exists());
System.out.println(file.isDirectory());
System.out.println("---------------------");
//file.mkdir();
//创建多级目录的时候需要用的是 mkdirs 方法
file.mkdirs();
System.out.println(file.exists());
System.out.println(file.isDirectory());
}
}
运行结果:
同时,可以观察到左边创建了目录:
当打开绝对路径去观察时,也会发现创建了多级目录:
2.4.5 演示五:文件重命名
package file;
import java.io.File;
public class Demo5 {
//文件重命名
public static void main(String[] args) {
File file1 = new File("./test1.txt");
File file2 = new File("./test2.txt");
file1.renameTo(file2);
}
}
运行结果:
我们可以很清楚的看见,文件名 test1.txt 运行程序之后,重命名为了 test2.txt ~
三、文件内容的操作
关于文件读写的操作,主要分为这几大块:打开文件、关闭文件、读文件、写文件 ~
其中,读写文件 是关键操作 ~
但是,在读写文件之前,必须要打开文件;在使用完毕之后,必须要关闭文件 ~
在 Java中,关于文件读写,提供了几组相关的类(是父类,还有许多其他子类继承):
- 第一组:InputStream类、OutputStream类 ——> 字节流(以字节为单位的流,用来操作二进制文件的)
- 第二组:Reader类、Writer类 ——> 字符流(以字符为单位的流,用来操作文本文件的)
关于 "流",是计算机中一种常见的概念 ~
比如说,如果想接住 100ml的水 ~
可以一次接 100ml,一次就搞定;可以一次接 50ml,两次就搞定;可以一次接 10ml,十次就搞定 ......
类似于水流,可以随心所欲的控制 读写的数据量 ~
如:想 读/写 100字节的数据 ~
可以一次 读/写100字节,一次就搞定;可以一次 读/写50字节,两次就搞定;可以一次 读/写10字节,十次就搞定 ......
于是,我们就把这种 读/写 方式,称为 面向流的读/写方式 ~
3.1 读文件
3.1.1 使用字节流读文件
我们可以在 Idea 上输入 InputStream类,点击 Ctrl + 左键,便可以发现,InputStream 是一个抽象类,不可以直接 new,需要使用 new InputStream 子类的方式来进行实例化:
方法:
关于 InputStream类 的常用方法有下面几个:
需要注意的是,要在打开之后 一定记得要关闭文件!!!
—— 需要释放资源(内存、文件描述符表)
—— 如果打开文件之后,忘记关闭,造成的结果非常大的(文件资源泄露,就像是定时炸弹一样,并不是马上就泄露完,这就非常的麻烦了)~
一个进程,会使用PCB来描述(此时 只考虑单线程)~
PCB 里面有很多属性,文件描述符表 就是其中的一个属性,它可以看作是一个数组(顺序表),其中的每个元素 都对应着当前打开的文件,这个数组的下标 就称为 "文件描述符" ~
每次打开文件,都会在文件描述符表中 占据一个位置,每次关闭文件,都会释放一个位置;由于文件描述符表的长度是存在上限的,所以如果一个线进程一直在打开文件,没有释放文件,此时就会导致 后续进程在打开的时候失败!!!
代码示例:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class Demo6 {
//使用一下 InputStream类
public static void main(String[] args) throws IOException {
// 1.打开文件
//打开文件成功,就得到了 InputStream对象,
//后续针对文件的操作,都是通过 InputStream 来展开的
// (就相当于是 空调遥控器 可以操控空调,内存中的 InputStream对象 可以操控硬盘里面的 test2.txt文件)
//像 inputStream 这一类的 “遥控器”,在计算机中 我们通常称为:句柄(Handler)
InputStream inputStream = new FileInputStream("./test2.txt");
// 2.读取文件
while (true) {
int b = inputStream.read();
if(b == -1) {
//此时,文件读完了
break;
}
System.out.println(b);
}
// 3.关闭文件
//关闭文件,以释放 内存资源 和 文件描述符表
inputStream.close();
}
}
同时,我在 test2.txt 文件下输入了:hello
运行结果:
由于都是按照字节读取的,读取出来的值 都是字节,所以从上到下,分别是 :h e l l o 的ASCII码值~
-- 当然,我们也可以采用其他的 read方法 来读取数据
-- 这里可以把 2.读取文件 的代码改成:
byte[] b = new byte[1024];
int len = inputStream.read(b);
System.out.println(len);
for (int i = 0; i < len; i++) {
System.out.println(b[i]);
}
此时,运行的结果是:
3.1.2 使用字符流读文件
需要注意的是,在上面的 test2.txt 文件中 存储的是英文状态的 "hello",但我们把它改成中文状态的 "你好" 时,那结果可就不一样了 ~
运行结果:
说明:
当前格式 是 utf-8 的格式编码,它的汉字一般是 三个字节一个汉字 ~
如果想要直观的获取 中文,那就需要去手动的转换(如下所示):
-- 这里可以把 2.读取文件 的代码改成:
byte[] b = new byte[1024];
int len = inputStream.read(b);
//把 0到len 这一段数据构成一个字符串,并且指定构造字符集的格式编码 —— utf-8
String s = new String(b,0,len,"utf-8");
System.out.println(s);
运行结果:
通过字节流,读取文本数据的时候,虽然可以读取到,但是要想真正还原成原始的版本,还是比较麻烦的,需要手动处理~
因此就可以使用 字符流 来解决上述问题(字符流 就相当于已经在底层 已经完成了里面的转换了)~
代码示例:
package file;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
public class Demo7 {
//使用字符流读文件
public static void main(String[] args) throws IOException {
Reader reader = new FileReader("test2.txt");
char[] buffer = new char[1024];
int len = reader.read(buffer);
for (int i = 0; i < len; i++) {
System.out.print(buffer[i]);
}
//关闭资源
reader.close();
}
}
运行结果:
3.1.3 使用Scanner读取文件(推荐)
字符流来处理文本是比较方便的,但是 这里还有一种更为简单的方法,可以使用 Scanner 来读取文本文件 ~
Scanner scanner = new Scanner(System.in);
//以前我们使用的是这个样子的,可以从键盘上来输入数据
//其中,System.in 中的 in 就是 inputStream
//如果直接把 System.in 换成 inputStream
//需要 InputStream inputStream = new FileInputStream("文本");
//就可以去读自己写的文件
代码示例:
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;
public class Demo8 {
//使用 Scanner 来读取文本文件
public static void main(String[] args) throws IOException {
InputStream inputStream = new FileInputStream("test2.txt");
Scanner scanner = new Scanner(inputStream);
String s = scanner.next();
System.out.println(s);
inputStream.close();
}
}
运行结果:
需要注意的是,其实在上述代码中,涉及到了 read 操作,这就会引出 IOException异常,此时代码就会无法进行,后面的 关闭文件(close ) 的操作就不会被运行 ~
所以可以使用 try ... catch ... finally ... 的操作来避免的 ~
这里就不做过多演示了 ~
但是,最后就会发现,这个看起来就很啰嗦 ~
所以,就可以改成这个样子了 :
这样的操作叫做 try with resource(有资源的 try)~
同时,这类写法 会在 try 实现结束之后,自动调用 inputStream 的 close方法(当然,并不是所有的类都可以这样放在try括号里面,要求这个类实现 Closeable接口,才可以这么做)~、
这种写法是比较推荐的写法~
3.2 写文件
字节流:OutputStream / FileOutputStream
字符流:Writer / FileWriter
以及,和 Scanner 相对的 PrintWriter 操作
OutputStream类 的方法和上面的 InputStream类 的方法差不多,这里就不做过多介绍了,感兴趣的铁铁们可以自己去看看官方文档 ~
3.2.1 使用字节流写文件
package file;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class Demo10 {
public static void main(String[] args) {
try(OutputStream outputStream = new FileOutputStream("test2.txt")) {
//写文件
outputStream.write('h');
outputStream.write('e');
outputStream.write('l');
outputStream.write('l');
outputStream.write('o');
}catch (IOException e) {
e.printStackTrace();
}
}
}
当我们运行这段代码时,再去查看 text2.txt 文档时,就会发现:
如果想写一个字符串,那么就可以这样做:
package file;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
public class Demo10 {
public static void main(String[] args) {
try(OutputStream outputStream = new FileOutputStream("test2.txt")) {
//写文件
String s = "bulabula";
outputStream.write(s.getBytes());
}catch (IOException e) {
e.printStackTrace();
}
}
}
此时,运行程序后,再去看看 test2.txt 文件,就会发现:
需要注意的是,每一次 写文件 的操作都会清空原有的文件,然后再去重新写 ~
3.2.2 使用字符流写文件
package file;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
public class Demo11 {
public static void main(String[] args) {
try(Writer writer = new FileWriter("test2.txt")) {
writer.write("hello world");
}catch (IOException e) {
e.printStackTrace();
}
}
}
此时,运行程序之后,再次看看 test2.txt 文件,就会发现:
3.2.3 使用PrintWriter写文件
package file;
import java.io.*;
public class Demo12 {
public static void main(String[] args) {
try (OutputStream outputStream = new FileOutputStream("test2.txt")) {
//此处的 PrintWriter 的用法 和 System.out 有相似之处
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.println("I LOVE YOU !");
printWriter.flush();
}catch (IOException e) {
e.printStackTrace();
}
}
}
此时,程序运行结束之后,再来看看 test2.txt 文件:
四、代码案例
4.1 文件查找功能
扫描指定目录,并找到名称中包含指定字符的 所有普通文件(不包含目录),并且后续询问用户是否要删除该文件 ~
package file;
import java.io.File;
import java.io.IOException;
import java.util.Scanner;
public class Demo13 {
// 实现一个递归遍历文件, 并询问删除.
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要扫描的路径: ");
String rootPath = scanner.next();
//输入的路径不存在
File root = new File(rootPath);
if (!root.exists()) {
System.out.println("您输入的路径不存在, 无法进行扫描!");
return;
}
System.out.println("请输入要删除的文件名(或者一部分): ");
String toDelete = scanner.next();
// 准备进行递归, 通过递归的方式, 来找到所有的文件.
// 找到所有的文件之后, 再尝试进行删除
scanDir(root, toDelete);
}
//rootDir 从哪个路径开始扫描 toDelete 要删除的文件
public static void scanDir(File rootDir, String toDelete) {
// 加上个日志, 看一下这里当前递归的过程.
try {
System.out.println(rootDir.getCanonicalPath());
} catch (IOException e) {
e.printStackTrace();
}
//列出扫描目录有哪些文件
File[] files = rootDir.listFiles();
if (files == null) {
// 空目录, 直接返回
return;
}
//遍历每一个文件
for (File f : files) {
if (f.isDirectory()) {
// 是目录, 就进行递归
scanDir(f, toDelete);
} else {
// 普通文件
tryDelete(f, toDelete);
}
}
}
public static void tryDelete(File f, String toDelete) {
// 看看当前文件名是否包含了 toDelete, 如果包含, 就删除, 否则就啥都不干
if (f.getName().contains(toDelete)) {
try {
System.out.println("是否要删除文件(Y/n): " + f.getCanonicalPath());
Scanner scanner = new Scanner(System.in);
String choice = scanner.next();
if (choice.equals("Y")) {
f.delete();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
4.2 复制普通文件
基本思路:
把文件1 复制成 文件2,就是 把文件1 里面的内容 都按照字节读取出来,写入到 文件2 中 ~
package file;
import java.io.*;
import java.util.Scanner;
public class Demo14 {
// 实现复制文件的功能
public static void main(String[] args) {
// 准备工作
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要复制的文件路径: ");
String srcPath = scanner.next();
File srcFile = new File(srcPath);
if (!srcFile.exists()) {
System.out.println("要复制的文件不存在!");
return;
}
if (!srcFile.isFile()) {
System.out.println("要复制的不是普通文件!");
return;
}
System.out.println("请输入要复制到的目标路径: ");
String destPath = scanner.next();
File destFile = new File(destPath);
if (destFile.exists()) {
System.out.println("要复制到的目标已经存在! ");
return;
}
// 进行拷贝工作
try (InputStream inputStream = new FileInputStream(srcFile)) {
try (OutputStream outputStream = new FileOutputStream(destFile)) {
byte[] buf = new byte[1024];
while (true) {
int len = inputStream.read(buf);
if (len == -1) {
// 拷贝完成
break;
}
outputStream.write(buf, 0, len);
}
}
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("复制完成!");
}
}
4.3 获取含有指定字符串的普通文件
package file;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;
public class Demo15 {
// 遍历目录, 看某个输入的词是否在文件名或者文件内容中存在.
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要搜索的目录:");
String rootPath = scanner.next();
File rootFile = new File(rootPath);
if (!rootFile.exists()) {
System.out.println("要扫描的目录不存在!");
return;
}
if (!rootFile.isDirectory()) {
System.out.println("要扫描的路径不是目录!");
return;
}
System.out.println("请输入要搜索的词:");
String toFind = scanner.next();
// 递归遍历目录
scanDir(rootFile, toFind);
}
private static void scanDir(File rootFile, String toFind) throws IOException {
File[] files = rootFile.listFiles();
if (files == null) {
return;
}
for (File f : files) {
if (f.isDirectory()) {
scanDir(f, toFind);
} else {
tryFindInFile(f, toFind);
}
}
}
// 判定 toFind 是否是文件名 或者 是文件内容的一部分
private static void tryFindInFile(File f, String toFind) throws IOException {
// 是不是文件名的一部分
if (f.getName().contains(toFind)) {
System.out.println("找到文件名匹配的文件: " + f.getCanonicalPath());
return;
}
// 是不是文件内容的一部分
try (InputStream inputStream = new FileInputStream(f)) {
// 把文件内容整个的都读出来
StringBuilder stringBuilder = new StringBuilder();
Scanner scanner = new Scanner(inputStream);
while (scanner.hasNextLine()) {
stringBuilder.append(scanner.nextLine());
}
// 读取完毕
if (stringBuilder.indexOf(toFind) >= 0) {
System.out.println("找到文件内容匹配的文件: " + f.getCanonicalPath());
return;
}
}
}
}