👦有缘,才相遇,你好!我是hgway_hxz
❤️热爱Java,希望结识更多的小伙伴一起交流
🎉欢迎大家:👍点赞 💬评论 ⭐收藏 💖关注
✉️如果有任何问题欢迎大家在评论区讨论或者私信我
✏️命运因努力而精彩
文章目录
1.认识文件
1.1 文件的概念
我们先来认识狭义上的文件(file)。针对硬盘这种持久化存储的I/O设备,当我们想要进行数据保存时,往往不是保存成一个整体,而是独立成一个个的单位进行保存,这个独立的单位就被抽象成文件的概念,就类似办公桌上的一份份真实的文件一般。在计算机中,文件可能是一个广义的概念,不仅包含普通文件,还包含目录(文件夹)。
1.2 文件的分类
站在程序员的角度来看,文件可以分成两类
- 文本文件:里面存储的是字符(本质上来说,文本文件也是存字节的,但是文本文件中,相邻的字节之间有联系刚好构成一个一个的字符)
- 二进制文件:里面存储的字节(相邻的字节之间没啥联系)
如何知道这个文件是文本文件还是二进制文件呢?
用记事本打开,把这个文件拖进来,如果是乱码的话,这个文件就是二进制文件,不是乱码就是文本文件。
比如我们的.txt,.java等属于文本文件,但.doc,.ppt,.xls等Office办公软件一般属于二进制文件,这种软件保存的不是一个单纯的文件,而是一个富文本,里面包含各种格式化的信息(字体大小,颜色……)。
1.3文件的路径
- 绝对路径
绝对路径是以盘符开头的。例如D:\Program Files (x86)\Tencent\WeChat
- 相对路径
相对路径是以.或者…开头的,其中**.表示当前路径**,…表示当前路径的上一级路径(父目录),但必须要有一个基准路径,相对路径就是从基准路径出发的。
例如:
有一个路径D:\Tencent\WeChat,其中D:\Tencent目录下有个text.txt文件
如果以D:\Tencent为基准路径,要找到text.txt文件,相对路径就是./text.txt,此处的.表示当前的路径(基准路径)。
如果以D:\Tencent\WeChat为基准路径,要找到text.txt文件,相对路径就是…/text.txt,此处的…表示基准路径的上一级路径D:\Tencent,然后再从这个路径中寻找text.txt这个文件。
2.文件系统的操作
我们的计算机就是通过“文件资源管理器”来进行文件的操作的
这里我们可以创建目录/文件,删除目录/文件,重命名文件……
在Java中就提供了一个File类来文件上述的操作,这个File类就描述了一个文件/目录,通过这个对象就可以实现这个功能。
2.1File类中的常见属性,构造方法和方法。
属性
修饰符及类型 | 属性 | 说明 |
---|---|---|
static String | pathSeparator | 依赖于系统的路径分隔符,String 类型的表示 |
static char | pathSeparator | 依赖于系统的路径分隔符,char 类型的表示 |
构造方法
签名 | 说明 |
---|---|
File(File parent, String child) | 根据父目录 + 孩子文件路径,创建一个新的 File 实例 |
File(String pathname) | 根据文件路径创建一个新的 File 实例,路径可以是绝对路径或者 相对路径 |
File(String parent, String child) | 根据父目录 + 孩子文件路径,创建一个新的 File 实例,父目录用 路径表示 |
方法
修饰符及返回 值类型 | 方法签名 | 说明 |
---|---|---|
String | getParent() | 返回 File 对象的父目录文件路径 |
String | getName() | 返回 FIle 对象的纯文件名称 |
String | getPath() | 返回 File 对象的文件路径 |
String | getAbsolutePath() | 返回 File 对象的绝对路径 |
String | getCanonicalPath() | 返回 File 对象的修饰过的绝对路径 |
boolean | exists() | 判断 File 对象描述的文件是否真实存在 |
boolean | isDirectory() | 判断 File 对象代表的文件是否是一个目录 |
boolean | isFile() | 判断 File 对象代表的文件是否是一个普通文件 |
boolean | createNewFile() | 根据 File 对象,自动创建一个空文件。成功创建后返 回 true |
boolean | delete() | 根据 File 对象,删除该文件。成功删除后返回 true |
void | deleteOnExit() | 根据 File 对象,标注文件将被删除,删除动作会到 JVM 运行结束时才会进行 |
String[] | list() | 返回 File 对象代表的目录下的所有文件名 |
File[] | listFiles() | 返回 File 对象代表的目录下的所有文件,以 File 对象 表示 |
boolean | mkdir() | 创建 File 对象代表的目录 |
boolean | mkdirs() | 创建 File 对象代表的目录,如果必要,会创建中间目 录 |
boolean | renameTo(File dest) | 进行文件改名,也可以视为我们平时的剪切、粘贴操 作 |
boolean | canRead() | 判断用户是否对文件有可读权限 |
boolean | canWrite() | 判断用户是否对文件有可写权限 |
以一个参数的构造方法为例
示例一
public static void main(String[] args) throws IOException {
//构造方法中可以是绝对路径,也可以是相对路径,我们常用正斜杠/来写,因为用反斜杠\的话,会被当成转义字符,需要写成E:\\QQMusicCache\\111\\text.txt
File file = new File("e:/QQMusicCacha/111/text.txt");//绝对路径
System.out.println(file.getParent());//获取文件的父目录
System.out.println(file.getName());//获取文件名
System.out.println(file.getPath());//获取文件的路径(创建File对象时指定的路径)
System.out.println(file.getAbsolutePath());//获取文件的绝对路径
System.out.println(file.getCanonicalPath());//获取文件简化过的绝对路径
System.out.println("-----------------------");
File file2 = new File("./text.txt");//相对路径
System.out.println(file2.getParent());
System.out.println(file2.getName());
System.out.println(file2.getPath());
System.out.println(file2.getAbsolutePath());
System.out.println(file2.getCanonicalPath());
}
结果:
e:\QQMusicCacha\111
text.txt
e:\QQMusicCacha\111\text.txt
e:\QQMusicCacha\111\text.txt
E:\QQMusicCacha\111\text.txt
-----------------------
.
text.txt
.\text.txt
D:\Javacode\20220320\.\text.txt
D:\Javacode\20220320\text.txt
Process finished with exit code 0
我们看到File file2 = new File(“./text.txt”)并没有指定基准路径,我们怎么知道呢?
其实基准路径是由运行Java这个程序来确定的,不同的方式来运行java程序,基准路径就不一样,如果是通过IDEA的方式来运行Java程序,此时的基准路径就是当前Java项目所在的路径。
示例二
public static void main(String[] args) {
File file = new File("e:/QQMusicCache/111/text.txt");
System.out.println(file.exists());//文件是否存在
System.out.println(file.isDirectory());//是否是目录
System.out.println(file.isFile());//是否是普通文件
}
结果:
true
false
true
Process finished with exit code 0
示例三
普通文件的创建和删除
public static void main(String[] args) throws IOException {
File file = new File("e:/QQMusicCache/111/text.txt");
System.out.println(file.createNewFile());//创建一个空文件,成功返回true
System.out.println(file.exists());
System.out.println(file.delete());//删除一个文件,成功返回true
System.out.println(file.exists());
结果:
true
true
true
false
Process finished with exit code 0
实例四
目录的创建
public static void main(String[] args) {
File file = new File("./abc");
//一次只能创建一级目录,如果已有这个目录,就会返回false
System.out.println(file.mkdir());
System.out.println(file.isDirectory());
//想要一次创建多级目录,要用mkdirs()
File file2 = new File("./abc/bb/cc");
System.out.println(file2.mkdirs());
System.out.println(file2.isDirectory());
}
结果:
true
true
true
true
Process finished with exit code 0
3.文件内容的操作
我们可以打开文件,读文件,写文件,关闭文件,针对文件内容的读写,Java中提供了一组类。
根据文件内容,分成两个系列:
1.字节流对象,针对二进制文件,以字节为单位进行读写。
- 读InputStream,子类FileInputStream
- 写OutputStream,子类FileOutputStream
2.字符流对象,针对文本文件,以字符为单位进行读写。
- 读Reader,子类FileReader
- 写Writer,子类FileWriter
因为InputStream等这些类是抽象类,不能通过new对象创建实例,实际使用的是这些类的子类。
什么是流对象?
类似我们常说的水流,如果通过一个水龙头接100ml的水,我们可以1次接10ml,分10次接完,也可以1次接100ml,分1次接完
而我们通过这个流对象来读取100个字节的文件,我们可以1次读10个字节,分10次读完,也可以1次读100个字节,分1次读完
3.1 InputStream
InputSream方法
修饰符符及返回值类 型 | 方法 | 说明 |
---|---|---|
int | read() | 一次读取一个字节的数据,返回值是读到的这个字节,返回 -1 代表完全读完了 |
int | read(byte[] b) | 一次读取若干个字节的数据,再放到指定的数组中,返回值是读到字节数;返回-1 代表读完了 |
int | read(byte[] b, int off, int len) | 一次读取若干个字节的数据,再放到指定的数组中,返回值是读到字节数,从数组的off位置开始放置,最多能放len个元素;返回-1 代表读完了 |
void | close() | 关闭字节流 |
FileInputSt构造方法
构造方法 | 说明 |
---|---|
FileInputStream(File file) | 利用 File 构造文件输入流 |
FileInputStream(String name) | 可以是绝对路径,也可以是相对路径构造文件输入流 |
假设我的text.txt文件有abcd的内容.
read方法无参版本代码演示:
public static void main(String[] args) throws FileNotFoundException {
//创建对象,也就打开了这个文件
try (InputStream inputStream = new FileInputStream("e:/QQMusicCache/aaa/text.txt")) {
while (true) {
//无参数版本,一个一个字节地读,返回值是读到的字节
int len = inputStream.read();
if (len == -1) {
//文件读完了
return;
}
System.out.println(len);
}
} catch (IOException e) {
e.printStackTrace();
}
}
结果:
97
98
99
100
Process finished with exit code 0
我们读写文件后,都需要调用close方法回收资源,Java中提供了try with resource的语法,它会在try语句块执行完之后自动调用close,但是需要符合一定的条件才能放到try()里面,要求都实现Closeable这个接口,才能放到()里面,而流对象都实现了这个接口
read方法一个参数版本演示:
public static void main(String[] args) throws FileNotFoundException {
try (InputStream inputstream = new FileInputStream("e:/QQMusicCache/aaa/text.txt")) {
while (true) {
byte[] array = new byte[1024];
//一次读取若干个字节,读到的数据放到数组中,返回值是读到的字节数
int len = inputstream.read(array);
System.out.println(len);
if (len == -1) {
//文件读完了
return;
}
for (int i = 0; i < len; i++) {
System.out.println(array[i]);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
结果:
4
97
98
99
100
-1
Process finished with exit code 0
read方法三个参数版本代码演示:
public static void main(String[] args) throws FileNotFoundException {
try (InputStream inputStream = new FileInputStream("e:/QQMusicCache/aaa/text.txt")) {
while (true) {
byte[] array = new byte[1024];
//三个参数版本,一次读取若干个字节的数据,再放到指定的数组中,返回值是读到字节数,从数组的off位置开始放置,最多能放len个元素
int len = inputStream.read(array, 0, 2);
System.out.println(len);
if (len == -1) {
return;
}
for (int i = 0; i < len; i++) {
System.out.println(array[i]);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
结果:
2
97
98
2
99
100
-1
Process finished with exit code 0
3.2 OutputStream
方法
修饰符及返回值类型 | 方法 | 说明 |
---|---|---|
void | write(int b) | 写入要给字节的数据 |
void | write(byte[] b) | 将 b 这个字符数组中的数据全部写入 os 中 |
void | write(byte[] b, int off, int len) | 将 b 这个字符数组中从 off 开始的数据写入 os 中,一共写 len 个 |
void | close() | 关闭字节流 |
void | flush() | 重要:我们知道 I/O 的速度是很慢的,所以,大多的 OutputStream 为 了减少设备操作的次数,在写数据的时候都会将数据先暂时写入内存的 一个指定区域里,直到该区域满了或者其他指定条件时才真正将数据写 入设备中,这个区域一般称为缓冲区。但造成一个结果,就是我们写的 数据,很可能会遗留一部分在缓冲区中。需要在最后或者合适的位置, 调用 flush(刷新)操作,将数据刷到设备中 |
write(byte[] b) 代码演示:
public static void main(String[] args) throws FileNotFoundException {
try (OutputStream outputStream = new FileOutputStream("e:/QQMusicCache/aaa/text.txt")) {
byte[] array = {'a', 'b', 'c', 'd', 'e'};
outputStream.write(array);
} catch (IOException e) {
e.printStackTrace();
}
}
我们再打开这个文件:
其他方法也是类似,但是每次按照写方式打开文件,都会清空原来文件的内容,再从起始位置进行写入。
3.3Reader
read(char[] cubf)方法代码演示:
public static void main(String[] args) throws FileNotFoundException {
//以字符来读
try (Reader reader = new FileReader("e:/QQMusicCache/aaa/text.txt")) {
while (true) {
char[] array = new char[1024];
int len = reader.read(array);
if (len == -1) {
return;
}
for (int i = 0; i < len; i++) {
System.out.println(array[i]);
}
//以字符串的形式输出
String s = new String(array, 0, len);
System.out.println(s);
}
} catch (IOException e) {
e.printStackTrace();
}
}
3.4 Writer
write(String str)方法代码演示:
public static void main(String[] args) {
try (Writer writer = new FileWriter("e:/QQMusicCache/aaa/text.txt")) {
//以字符来写
writer.write("abcdefg");
} catch (IOException e) {
e.printStackTrace();
}
}
打开text.txt文件
字符流进行读写其实和字节流进行读写的方法都是类似的。
3.5文件操作的案例
案例一:扫描指定目录,找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要删除文件
public static void main(String[] args) {
//1.用户输入要扫描的目录和要删除的文件名
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要扫描的路径:");
String rootDirPath = scanner.next();
System.out.println("请输入要删除的文件名:");
String deleteName = scanner.next();
File rootDir = new File(rootDirPath);
if (!rootDir.isDirectory()) {
//判断用户输入的路径是否是目录
return;
}
//2.把这个目录下的所有普通文件和子目录都遍历一遍
//如果是普通文件,就查看普通文件的名是否包含要删除的文件名
//如果是目录,因为文件和目录是通过树形结构管理的,所以要递归遍历
scanDir(rootDir, deleteName);
}
private static void scanDir(File rootDir, String deleteName) {
//列出这个目录下有哪些内容
File[] files = rootDir.listFiles();
if (files == null) {
//这个目录为空
return;
}
for (File f : files) {
if (f.isFile()) {
//普通文件的情况
if (f.getName().contains(deleteName)) {
if (f.isFile()) {
//询问用户是否要删除这个文件
deleteFile(f);
}
}
} else if (f.isDirectory()) {
//目录的情况
scanDir(f, deleteName);
}
}
}
private static void deleteFile(File f) {
Scanner scanner = new Scanner(System.in);
try {
System.out.println("确认要删除 " + f.getCanonicalPath() + " Y/N");
String choice = scanner.next();
if (choice.equals("Y") || choice.equals("y")) {
System.out.println("删除成功");
f.delete();
} else {
System.out.println("取消删除");
}
} catch (IOException e) {
e.printStackTrace();
}
}
案例二:进行普通文件的复制,让用户指定两个文件路径,打开源路径的文件,读取里面的内容,再写入目标文件中。
public static void main(String[] args) throws FileNotFoundException {
//1.用户输入源路径和目标路径
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要复制源路径:");
String src = scanner.next();
System.out.println("请输入要复制目标路径:");
String dest = scanner.next();
File srcFile = new File(src);
if (!srcFile.isFile()) {
//判断文件是否存在
System.out.println("源路径输入错误");
return;
}
//此处不需要检查目标文件是否存在. 因为OutputStream写文件的时候能够自动创建不存在的文件.
//2.读取源文件,把里面的内容复制到目标文件中
try (InputStream inputStream = new FileInputStream(src)) {
//把inputStream读到的内容写入OutputStream中
try (OutputStream outputStream = new FileOutputStream(dest)) {
byte[] array = new byte[1023];
while (true) {
int len = inputStream.read(array);
if (len == -1) {
//文件读到了末尾
return;
}
//不需要把整个数组都写入,只把有效数据写入
outputStream.write(array, 0, len);
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
结果:
请输入要复制源路径:
e:/QQMusicCache/aaa/text.txt
请输入要复制目标路径:
e:/QQMusicCache/text.txt
Process finished with exit code 0
案例三:扫描指定目录,并找到名称或者内容中包含指定字符的所有普通文件(不包含目录)
public static void main(String[] args) {
//1.用户输入路径和要指定的字符
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要扫描的路径:");
String rootDirPath = scanner.next();
System.out.println("请输入要找的文件名:");
String findName = scanner.next();
//判断路径是否是目录
File rootDir = new File(rootDirPath);
if (!rootDir.isDirectory()) {
System.out.println("输入的路径有误");
return;
}
//2.把这个目录下的所有普通文件和子目录都遍历一遍
//如果是普通文件,就查看普通文件中是否包含有要指定字符的名字
//如果是目录,就递归遍历
scanDir(rootDir, findName);
}
private static void scanDir(File rootDir, String findName) {
//1.先列出rootDir里面都有哪些内容
File[] files = rootDir.listFiles();
//判断目录是否为空
if (files == null) {
return;
}
for (File f : files) {
//普通文件的情况
if (f.isFile()) {
if (f.getName().contains(findName)) {
try {
System.out.println(f.getCanonicalPath());
} catch (IOException e) {
e.printStackTrace();
}
}
} else if (f.isDirectory()) {
//目录的情况
scanDir(f, findName);
}
}
}
结果:
请输入要扫描的路径:
e:/QQMusicCache
请输入要找的文件名:
text.txt
E:\QQMusicCache\aaa\text.txt
E:\QQMusicCache\text.txt
Process finished with exit code 0