认识文件
文件和目录
文件也就是我们电脑上保存的一些内容,类似图片,视频等等,针对硬盘这种持久化存储的IO设备来说,我们要存储一些数据的时候,不会所有不同的打包为一个整体,而是通过文件的形式来一个个分类的存储起来,然后随着我们文件的增加,我们会用一个文件夹(目录)来存储相同的文件,并且里面的文件是以树这样的数据结构存储起来的
文件路径
所谓路径就是文件存储的位置,我们一般分为相对路径和绝对路径,就以我上次写的AVL树的代码的位置来举例

**相对路径:**相对路径都是有一个基准路径的,就比如,我们的基准路径是C:\java\java-learning,那么这个文件的相对路径就是./AVL,简单介绍一下这里路径的写法:我们每个位置都是通过/的方式来分格的,至于你用/(斜杠)还是\(反斜杠),在windows系统之下都是可以的,不过我们更推荐/(斜杠)来书写,所以相对路径是根据你给的基准路径来定义的,在我们用Java来写文件的时候,你写相对路径的话,基准路径是你当前Java代码所放的位置
**绝对路径:**绝对路径就是我们图片上问价的完整路径
文件分类
根据文件存储数据的不同,我们可以简单分为文本文件 和二进制文件,怎么区分呢,我们可以通过记事本打开,如果一眼过去里面是乱码的话,就是二进制文件
在Java中操作文件
简单理解一下为什么我们要在Java中通过代码来操作问价,直接在电脑自带的此电脑中对文件进行修改删除之类的不是更加方便吗,但是如果我们操作的是很多的文件,并且每个文件的操作还不同的情况下,我们通过此电脑来一个一个的操作会相当的耗费时间,所以我们用代码来操作会更加的方便,每一个操作系统封装并且提供的文件操作的api是不同的,但是Java官方对这些api再次进行了封装,所以我们会更加快速的进行操作
File概述
| 属性 | 类型 | 作用 |
|---|---|---|
File.pathSeparator |
static String |
获取当前操作系统的路径列表分隔符 ,Windows 下一般是 ; |
File.pathSeparatorChar |
static char |
获取当前操作系统的路径列表分隔符 ,以 char 类型返回 |
| 构造方法 | 作用 | 示例 |
|---|---|---|
File(String pathname) |
根据一个完整路径创建 File 对象,可以是相对路径或绝对路径 |
new File("./test.txt") |
File(String parent, String child) |
根据"父目录路径 + 子文件/目录路径"创建 File 对象 |
new File("./data", "test.txt") |
File(File parent, String child) |
根据"父目录的 File 对象 + 子文件/目录路径"创建 File 对象 |
new File(parent, "test.txt") |
| 方法 | 返回值 | 作用 |
|---|---|---|
getParent() |
String |
获取父目录路径 |
getName() |
String |
获取文件或目录名称 |
getPath() |
String |
获取创建 File 对象时使用的路径 |
getAbsolutePath() |
String |
获取绝对路径 |
getCanonicalPath() |
String |
获取规范化后的绝对路径,会处理 .、.. 等 |
exists() |
boolean |
判断文件或目录是否真实存在 |
isDirectory() |
boolean |
判断是否为目录 |
isFile() |
boolean |
判断是否为普通文件 |
createNewFile() |
boolean |
创建一个新的空文件,创建成功返回 true |
delete() |
boolean |
立即删除文件或空目录,成功返回 true |
deleteOnExit() |
void |
JVM 退出时再删除该文件 |
list() |
String[] |
获取目录下所有文件/目录的名称 |
listFiles() |
File[] |
获取目录下所有文件/目录对应的 File 对象 |
mkdir() |
boolean |
创建单级目录 |
mkdirs() |
boolean |
创建多级目录,不存在的父目录也会一起创建 |
renameTo(File dest) |
boolean |
重命名或移动文件 |
canRead() |
boolean |
判断当前程序是否有读取权限 |
canWrite() |
boolean |
判断当前程序是否有写入权限 |
接下来我们用代码来演示一下

java
File file= new File("./test.txt");
System.out.println(file.getParent());
System.out.println(file.getName());
System.out.println(file.getAbsoluteFile());
System.out.println(file.getCanonicalFile());
System.out.println(file.exists());
System.out.println(file.isDirectory());
System.out.println(file.isFile());
System.out.println(file.delete());

为什么这里获取父亲路径的时候是一个点:因为我们给到的是一个相对路径,我们是以当前代码的路径作为基准路径,然后给到的路径是./test.txt,在执行的时候,会将我们传入的这个分为两个部分,一个是父亲的路径,一个是文件名,所以父亲路径自然是一个点,在我们执行完之后,这个目录底下的test.txt文件就消失了,通过下面的代码就又回来了
java
System.out.println(file.createNewFile());
java
File file= new File("./test");
System.out.println(file.mkdir());
这个mkdir跟mkdirs是创建一个文件夹也就是目录,并且会根据你给的路径的后缀进行判断,如果你传入./test.txt会创建失败,因为这是一个文件不是文件夹
java
File file= new File("C:/java/java-learning");
String[] list = file.list();
for (String s : list) {
System.out.println(s);
}
File[] files=file.listFiles();
for (File f : files) {
System.out.println(f.getName());
}

文件的读写(IO)
我们在文件读写的时候会用到一种流的概念,就相当于水流,我们读文件可以根据自己想要的量来进行流入以及流出
文件
████████████
里面存放很多数据
↓
流(Stream)
↓
Java程序
在Java标准库中,我们的流分为了两大类,一类是字符流(文本文件),一类是字节流(二进制文件),对于字符流 ,Java提供了两个抽象类:Reader(读文件),Writer(写文件),对于字节流,提供了:InputStream(写),OutputStream(读),当然我们要使用的时候需要new对应的子类
| 类 | 常用方法 | 作用 |
|---|---|---|
Reader |
read() |
读取一个字符 |
read(char[] cbuf) |
读取多个字符到字符数组中 | |
close() |
关闭字符输入流 | |
Writer |
write() |
写入字符、字符数组或字符串 |
flush() |
刷新缓冲区,将数据写出 | |
close() |
关闭字符输出流 | |
InputStream |
read() |
读取一个字节 |
read(byte[] b) |
读取多个字节到字节数组中 | |
close() |
关闭字节输入流 | |
OutputStream |
write() |
写入一个或多个字节 |
flush() |
刷新缓冲区,将数据写出 | |
close() |
关闭字节输出流 |
java
//读取文件内容,一个字节一个字节的读取,这里的n就表示我文件里的数据的对应ASCII码的数值,当读取到-1的时候表示文件已经读取完了
public static void main4(String[] args) throws IOException {
InputStream inputStream=new FileInputStream("C:/java/java-learning/DocumentAndIO/test.txt");
try {
while (true) {
int b = inputStream.read();
if (b == -1) {
break;
}
System.out.println(b);
}
} catch (IOException e) {
throw new RuntimeException(e);
} finally {
if (inputStream != null) {
inputStream.close();
}
}
}
//try with resource可以自动close
public static void main5(String[] args) throws IOException {
try(InputStream inputStream=new FileInputStream("C:/java/java-learning/DocumentAndIO/test.txt")){
while (true) {
int n = inputStream.read();
if (n == -1) {
break;
}
System.out.println(n);
}
}catch (IOException e){
e.printStackTrace();
}
}
//byte[]是个输出型参数,这个输出型是站在函数的角度来说,也就是read这个方法将文件里面的内容读给byte数组进行输出,并且n表示读到了多少个参数,当
//n=-1的时候表示文件已经读完了
public static void main6(String[] args) {
try(InputStream inputStream=new FileInputStream("C:/java/java-learning/DocumentAndIO/test.txt")){
byte[] bytes = new byte[1024];
while (true) {
int n= inputStream.read(bytes);
System.out.println(n);
if (n == -1) {
break;
}
for (int i = 0; i < n; i++) {
System.out.println(bytes[i]);
}
}
}catch (IOException e){
e.printStackTrace();
}
}
//从文件当前读取到的位置继续往后读,最多读 len 个字节,然后从 bytes[off] 开始存。
//完整理解一下下面的代码,inputStream.read(bytes,off,len)不是每次都读同一个,而是跟我们打字最后有一个光标一样,是记录我们读到哪里的,所以
//我们读一个光标就往后挪一个,所以我们的读取是每次往后读一个,然后存储在bytes[off]里,并且每一次读取都把上一次的进行覆盖了,所以每一次while循环都是读取的下一个数据
//所以最终输出的结果是1 65 1 66 1 67 1 68 -1
public static void main7(String[] args) {
try(InputStream inputStream=new FileInputStream("C:/java/java-learning/DocumentAndIO/test.txt")){
byte[] bytes = new byte[1024];
int off=1;
int len=1;
while (true) {
int n= inputStream.read(bytes,off,len);
System.out.println(n);
if (n == -1) {
break;
}
for (int i = off; i < off+n; i++) {
System.out.println(bytes[i]);
}
}
}catch (IOException e){
e.printStackTrace();
}
}
//OutputStream的write方法,将一个字节写入文件,如果文件不存在,会自动创建,如果文件存在,会覆盖
//flush相当于是一个缓冲区,你调用write方法的时候,会先将数据放在缓冲区里,暂时没有写进去,当积攒到一定数量的数据的时候,缓冲区会一次性的将数据写进去,这个一定数量取决于你调用的什么方法
//对应方法的数量是不一样的,但是你用flush就相当于即使没有到达数量,也直接就写进去
public static void main8(String[] args) throws IOException {
try (OutputStream outputStream=new FileOutputStream("C:/java/java-learning/DocumentAndIO/test.txt",true)){
outputStream.write('a');
outputStream.write(65);
outputStream.write(66);
outputStream.flush();
}catch (IOException e){
e.printStackTrace();
}
}
public static void main9(String[] args) throws IOException {
try (OutputStream outputStream=new FileOutputStream("C:/java/java-learning/DocumentAndIO/test.txt",true)){
byte[] bytes=new byte[]{'1','2','3','4'};
outputStream.write(bytes);
outputStream.flush();
}catch (IOException e){
e.printStackTrace();
}
}
//写中文
public static void main10(String[] args) {
try (OutputStream outputStream=new FileOutputStream("C:/java/java-learning/DocumentAndIO/test.txt",true)){
String s="你好";
byte[] bytes=s.getBytes("utf-8");
outputStream.write(bytes);
outputStream.flush();
}catch (IOException e){
e.printStackTrace();
}
}
//PrintWriter是打印流,可以方便的打印各种数据类型,并且自动刷新,不需要调用flush
public static void main11(String[] args) {
try (PrintWriter writer=new PrintWriter("C:/java/java-learning/DocumentAndIO/test.txt","utf-8")){
writer.println("文本类型");
}catch (IOException e){
e.printStackTrace();
}
}
public static void main12(String[] args) {
try (PrintWriter writer=new PrintWriter("C:/java/java-learning/DocumentAndIO/test.txt","utf-8")){
String name = "牛牛";
int age = 20;
double score = 95.5;
writer.printf("姓名:%s,年龄:%d,成绩:%.1f%n", name, age, score);
}catch (IOException e){
e.printStackTrace();
}
}
几个重要的问题我也在代码中标注了:
1:字节流的读方法是转成ASCII码中的数值来输出的
2:当我们读取到-1的时候就是读到结尾了
3:close的重要性:close是为了释放资源,你创建文件的时候,程序会向操作系统申请一个文件资源,如果文件一直打开不关闭的话,就无法打开新的文件,就会造成资源泄露(占着资源不释放)
4:我们用try with resource的写法,这样可以自动close
5:byte\[\]是个输出型参数,这个输出型是站在函数的角度来说,也就是read这个方法将文件里面的内容读给byte数组进行输出,并且n表示读到了多少个参数,怎么理解输出型参数,就相当于byte数组就是一个空盘子,你去文件里盛出来一些数据,所以byte数组主要是为了输出数据
6:inputStream.read(bytes,off,len),从文件当前读取到的位置继续往后读,最多读 len 个字节,然后从 bytesoff 开始存。
7:对于写方法中的flush的理解:
write()
↓
把数据写给输出流
flush()
↓
把缓冲区里还没送出去的数据立即推下去
close()
↓
处理剩余数据 + 关闭流
8:哪些写方法会默认清空数据:
| 写入方式 | 默认是否清空原内容 | 如何追加 |
|---|---|---|
FileOutputStream |
✅ 会 | new FileOutputStream(path, true) |
FileWriter |
✅ 会 | new FileWriter(path, true) |
PrintWriter(String fileName) |
✅ 会 | 本身没有简单的 true 参数,通常包装追加流 |
BufferedWriter |
看它包装的底层 Writer | 底层使用 FileWriter(path, true) |
OutputStreamWriter |
看它包装的底层 OutputStream | 底层使用追加模式 |
Files.write() |
默认 ✅ 会覆盖/截断已有文件 | 使用 StandardOpenOption.APPEND |
Files.writeString() |
默认 ✅ 会覆盖/截断已有文件 | 使用 StandardOpenOption.APPEND |
文件操作案例
案例一
根据用户所给的路径以及文件名来搜索以及判定删除
java
//通过用户输入来找文件以及是否要删除
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入文件路径:");
String path = scanner.nextLine();
File file = new File(path);
//判断路径是否存在
if (!file.isDirectory()) {
System.out.println("路径输入有误");
return;
}
System.out.println("请输入要删除的文件名:");
String fileName = scanner.nextLine();
//判断文件是否存在
if (fileName.isEmpty()) {
System.out.println("文件名不能为空");
return;
}
//进行查找以及删除
Search(file, fileName);
}
private static void Search(File file, String fileName) {
//先列出路径下的所有文件
File[] files = file.listFiles();
//判断是否为空
if (files == null) {
return;
}
//遍历
for (File f : files) {
if (f.isDirectory()) {
Search(f, fileName);
} else {
//看文件名是否包含用户输入的关键字
//然后进行删除
Delete(f, fileName);
}
}
}
private static void Delete(File f, String fileName) {
//再让用户进行输入,是否要删除
Scanner scanner = new Scanner(System.in);
if (f.getName().contains(fileName)) {
System.out.println("找到文件,文件名是:"+f.getName());
System.out.println("是否要删除?(y/n)");
String s = scanner.nextLine();
if (s.equals("y")) {
f.delete();
System.out.println("删除成功");
}else {
System.out.println("取消删除");
}
}
}

案例二
复制文件内容到目标文件
java
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入你要复制的文件路径:");
String pathfrom = scanner.nextLine();
File filefrom=new File(pathfrom);
//这个要检查复制的是否是文件
if (!filefrom.isFile()) {
System.out.println("路径输入有误");
return;
}
System.out.println("请输入你要复制到的文件路径:");
String pathto = scanner.nextLine();
//这个要判断父目录是否是目录
File fileto=new File(pathto);
if(!fileto.getParentFile().isDirectory()){
System.out.println("路径输入有误");
return;
}
Copy(filefrom,fileto);
}
private static void Copy(File filefrom, File fileto) {
//就是前面的读文件以及写文件
try (InputStream inputStream = new FileInputStream(filefrom);
OutputStream outputStream = new FileOutputStream(fileto)) {
byte[] bytes=new byte[1024];
while (true){
int n=inputStream.read(bytes);
if (n==-1){
break;
}
outputStream.write(bytes,0,n);
}
}catch (IOException e) {
e.printStackTrace();
}
System.out.println("复制成功");
}
案例三
根据关键字查找文件名以及文件内容
java
//查找内容,文件名以及文件内容
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入你要查找的文件路径:");
String path = scanner.nextLine();
File file = new File(path);
if (!file.isDirectory()) {
System.out.println("路径输入有误");
return;
}
System.out.println("请输入你要查找关键字:");
String word = scanner.nextLine();
if (word.isEmpty()) {
System.out.println("关键字不能为空");
return;
}
SearchWord(file, word);
}
private static void SearchWord(File file, String word) throws IOException {
File[] files = file.listFiles();
//判断是否为空
if (files == null) {
return;
}
//遍历
for (File f : files) {
if (f.isDirectory()) {
SearchWord(f, word);
}else if (f.isFile()) {
//用方法检查文件里面的内容
Check(f, word);
}
}
}
private static void Check(File f, String word) throws IOException {
//先判定文件名有没有包含
if (f.getName().contains(word)) {
System.out.println("文件名包含关键字:" + f.getName());
return;
}
//再判定文件内容有没有包含
try(Reader reader=new FileReader(f)){
StringBuilder stringBuilder=new StringBuilder();
char[] chars=new char[1024];
while (true){
int n= reader.read(chars);
if (n==-1){
break;
}
stringBuilder.append(chars,0,n);
}
if (stringBuilder.indexOf(word)>=0){
System.out.println("文件内容包含关键字:"+f.getAbsolutePath());
}else {
return;
}
}catch (IOException e) {
e.printStackTrace();
}
}

