目录
(1)扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要删除该文件
(3)扫描指定目录,并找到名称或者内容中包含指定字符的所有普通文件(不包含目录)
1.文件概述
(1)狭义和广义上的文件
狭义文件:硬盘上存储的普通文件和目录
(常见的比如.jpg
,.txt
,.mp3
,.mp4
这些, 还有放置这些格式文件的文件夹, 像这些文件都是存储在硬盘上的格式文件或者目录(文件夹))广义文件:计算机上的软件设备和硬件资源
(2)文件的分类
- 文本文件(存放的是文本, 字符串), 由字符构成, 都是指定字符集编码表里的数据.
- 二进制文件(存放的是二进制数据), 可以存放任何想存放的数据.
(3)文件的路径
每个文件, 在硬盘上都有一个具体的 “路径”, 文件的路径包括两种,
- 一种是绝对路径, 以盘符(C:或者D:)开头
- 另一种是相对路径, 以.或..开头的路径(.表示当前目录..当前目录的上一级目录), 相对路径是基于当前所在目录(工作目录)来说的, 使用/(在windows上也可以使用\, 但是更建议使用的是/, 使用\在字符串中容易被解析为转义字符)来分割不同的目录级别.
举例说明:
以 D:\Program Files\Java\jdk1.8.0_361\bin 目录为例,以该路径为工作目录,假设我们要在bin目录下找到jconsole文件,
- 则使用相对路径表示为:./jconsole.exe(.就表示当前所在目录的路径)
- 绝对路径:D:\Program Files\Java\jdk1.8.0_361\bin\jconsole.exe
还是以 D:\Program Files\Java\jdk1.8.0_361\bin 目录为工作目录, 我们要表示与bin文件夹在同一目录中的src.zip文件,
- 我们可以使用..表示工作目录的父路径 D:\Program Files\Java\jdk1.8.0_361\bin, 该文件的相对路径为 ../src.zip,
- 绝对路径为 D:\Program Files\Java\jdk1.8.0_361\bin\src.zip.
2.文件系统操作
针对文件系统的操作, 主要是文件/目录的创建和删除, 文件的重命名等.
Java标准库中提供了一个
File
类, 能够完成对某一路径上的文件进行操作.
(1)File类属性
File
类位于import java.io
包下, 该类相当于一个抽象的文件路径, 能够在指定的路径中进行文件的创建, 删除, 修改文件等, 但是不能对文件的内容进行操作.
(2)File类构造方法
(3)File类获取操作
import java.io.File;
import java.io.IOException;
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());
System.out.println("==========");
//通过相对路径字符串创建文件对象, .表示当前目录
File file2 = new File("./test.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());
System.out.println("==========");
//..表示当前目录的上一级目录
File file3 = new File("../test.txt");
System.out.println(file3.getParent());
System.out.println(file3.getName());
System.out.println(file3.getPath());
System.out.println(file3.getAbsolutePath());
System.out.println(file3.getCanonicalPath());
}
}
注意:
- 当我们使用相对路径创建File对象时,此处通过IDEA运行程序,此时基准路径就是 idea打开的这个项目所在的路径
- 因为..表示的是当前目录的上一级目录,所以当获取修饰过的绝对路径时
(4)File类的判断操作
import java.io.File;
import java.io.IOException;
public class Demo2 {
public static void main(String[] args) throws IOException {
File file = new File("./test.txt");
//判断文件是否存在
System.out.println(file.exists());
//判断File对象的路径是否是绝对路径
System.out.println(file.isAbsolute());
//判断文件是否是一个普通文件
System.out.println(file.isFile());
//判断文件是否是一个目录(文件夹)
System.out.println(file.isDirectory());
//判断文件是否是一个隐藏文件
System.out.println(file.isHidden());
//判断文件是否能够执行/读/写
System.out.println(file.canExecute());
System.out.println(file.canRead());
System.out.println(file.canWrite());
}
}
(5)文件的创建和删除
创建 :
import java.io.File;
import java.io.IOException;
public class Demo3 {
public static void main(String[] args) throws IOException {
File file = new File("./test1.txt");
//创建文件夹
if (!file.exists()) {
file.createNewFile();
System.out.println("创建文件成功");
}
//创建目录
file = new File("./temp");
file.mkdir();
System.out.println("创建目录成功");
//创建多个目录
file = new File("./aaa/bbb/ccc");
file.mkdirs();
System.out.println("创建多个文件夹成功");
}
}
删除:
import java.io.File;
public class Demo4 {
public static void main(String[] args) {
//删除操作
File file = new File("./test1.txt");
file.delete();
}
}
import java.io.File;
import java.util.Scanner;
public class Demo5 {
public static void main(String[] args) {
File file = new File("./test.txt");
file.deleteOnExit();
System.out.println("执行删除完毕");
Scanner scanner = new Scanner(System.in);
scanner.next();
}
}
只有当scanner输入后才会真正删除,存在的意义,就是可以用来构造"临时文件"
比如使用word 创建一个文档.打开“显示隐藏文件"
在你 word文档的同级目录下,打开word文档后就有一个隐藏文件,名字带有一些奇怪符号的.一旦你把现在编辑的文档关闭了这个隐藏文件就自动消失了这个文件中,就保存了你当前正在修改的,还没有真正保存的内容,程序异常关闭,此时,临时文件不会消失,就可以通过这个文件,还原出你正在编辑的内容.
(6)其它常用方法
public static void main(String[] args) {
File file = new File(".");
System.out.println(Arrays.toString(file.list()));
}
public static void main1(String[] args) {
File file = new File("./src");
System.out.println(Arrays.toString(file.list()));
}
public static void main1(String[] args) {
File file = new File("./src");
System.out.println(Arrays.toString(file.listFiles()));
}
public static void main1(String[] args) {
File file = new File(".abc");
boolean ok = file.mkdir();
System.out.println(ok);
}
public static void main1(String[] args) {
File file = new File(".abc/def/ghk");
boolean ok = file.mkdirs();
System.out.println(ok);
}
移动文件,就是修改文件所在的路径.
文件路径的修改,也可以视为是一种"重命名"
public static void main(String[] args) {
File srcFile = new File("./test");
File destFile = new File("./rename");
boolean ok = srcFile.renameTo(destFile);
System.out.println(ok);
}
//实现了文件的移动
public static void main(String[] args) {
File srcFile = new File("./aaa/bbb");
File destFile = new File("./bbb");
boolean ok = srcFile.renameTo(destFile);
System.out.println(ok);
}
(7)遍历获取目录下所有文件路径
import java.io.File;
import java.util.Arrays;
public class Demo6 {
private static void scan(File curentDir) {
//1.判断是否是根目录
if (!curentDir.isDirectory()) {
return;
}
//2.列出当前目录中包含的内容
File[] files = curentDir.listFiles();
if (files == null || files.length == 0) {
return;
}
//3.打印当前目录
System.out.println(curentDir.getAbsolutePath());
//4.遍历目录中所有的内容,并进行判定
for (File f: files) {
if (f.isFile()) {
//是普通文件直接打印文件路径
System.out.println(f.getAbsolutePath());
}else {
scan(f);
}
}
}
public static void main(String[] args) {
File file = new File("./");
scan(file);
}
3.文件内容操作
(1)IO流类和对象
针对文件内容的读写, Java标准库中提供了两组类和接口, 分别用来针对两类文件的内容进行读写
- 字节流对象:可以针对二进制文件进行读写,读写数据的基本单位就是字节
实现的类:InputStream(硬盘中的数据拿到内存里) OutputStream(内存里的数据写到硬盘里)- 字符流对象:可以针对文本文件进行读写,读写数据的基本单位就是字符
(字符流内部做的工作会更多一些,会自动的查询码表,把二进制数据,转换成对应的字符)
比如,就想读取某个文件中的前10个汉字.使用字符流非常方便的实现,直接读10个字符就可以了
实现的类:Reader Writer上述四个类都是抽象类,我们在使用时使用这几个实现类来创建实例对象即可, 分别对应的是
FileInputStream
,FileOutputStream
,FileReader
和FileWriter
类.
理解一个字符几个字节?
一个字符占用的字节数取决于所使用的字符编码系统。
在大多数情况下,英文字符在ASCII或UTF-8编码下占用1个字节,而在UTF-16或UTF-32编码下则可能占用更多。而对于其他语言或特殊字符,则可能根据编码的不同而占用不同的字节数。
理解输入输出方向 ?
方向是人为定义的,把内存中的数据,放到硬盘上.如果站在内存视角就是输出,站在硬盘视角就是输入
后面但凡谈到输入输出,都是以cpu视角来谈的.数据远离cpu就是输出,数据靠近cpu就是输入
(2)文件读操作
读字节
FileInputStream的构造方法:
public static void main(String[] args) throws FileNotFoundException { InputStream inputStream = new FileInputStream("./test.txt"); }
- 可以指定绝对路径,也能指定相对路径.还可以指定File 对象
- 这个异常,是IOException的子类.(IOException中的特殊情况)
- 此处隐含了一个操作,"打开文件",针对文件进行读写,务必需要先打开.(操作系统,基本要求)
import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; public class Demo9 { public static void main(String[] args) { InputStream inputStream = null; try { inputStream = new FileInputStream("./test.txt"); } catch (IOException e) { e.printStackTrace(); } finally { try { inputStream.close(); } catch (IOException e) { throw new RuntimeException(e); } } } }
import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; public class Demo8 { public static void main(String[] args) { try(InputStream inputStream = new FileInputStream("./test.txt")) { }catch (IOException e) { e.printStackTrace(); } } }
- Java提供了try with的写法,try(),括号中必须是实现了Closeble接口的类
- 该写法不必写finally,也不必写close,这里括号中创建的资源可以是多个,try的{}执行完毕,最终都会自动执行close
InputStream读文件(从文件读到内存)涉及的方法:
- 无参版本的read()方法:该方法的返回值是读取到的一个字节的数据,那它返回值为什么设为int呢?目的是为了处理文件读完了的情况,我们知道使用这个方法文件读完了的情况下返回值是-1,如果使用byte类型作为返回值会出现一个问题,byte的表示范围是-128-127,当返回值为-1时无法区分文件是读完了还是读到的字节值就是-1,这会存在歧义,而如果将读到一个字节的内容放在更大范围的int内存中,此时读到的byte范围内的值就可以在int范围内表示0-255这样的值,此时返回-1就表示到了文件末尾
这个方法的返回值在0-255之间或者 是 -1,那么我们要去接收这个返回的数据,并且是没有歧义的,就需要满足这个类型的数据范围要包括-1 以及 0-255的
import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; public class Demo9 { public static void main(String[] args) { try(InputStream inputStream = new FileInputStream("./test.txt")) { while(true) { int b = inputStream.read(); if (b == -1) { break; } System.out.printf("0x%x\n",b); } }catch (IOException e) { e.printStackTrace(); } } }
import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; public class Demo10 { public static void main(String[] args) { try(InputStream inputStream = new FileInputStream("./test.txt")){ //此处的buffer可以理解成缓冲区(通过某个内存空间暂存某个数据) byte[] buffer = new byte[1024];//预期字节数为1024 //n返回值表示实际读到的字节数目 int n = inputStream.read(buffer); System.out.println(n); }catch (IOException e) { e.printStackTrace(); } } }
理解输入型参数和输出型参数:
生产夹芯板
原材料:铁皮+泡沫+胶(输入的内容)
产品:夹芯板(输出的内容)
读字符
FileReader的构造方法:
Reader读文件涉及的方法:
import java.io.FileReader; import java.io.IOException; import java.io.Reader; public class Demo12 { public static void main(String[] args) { try(Reader reader = new FileReader("./test.txt")) { while(true) { int a = reader.read(); if (a == -1) { return; } char ch = (char)a; System.out.println(ch); } } catch (IOException e) { e.printStackTrace(); } } }
注意:我们所创建的test.txt文件默认是UTF-8代码,但Java中的char是2字节,当使用char表示这里的汉字的时候不再使用utf8而是使用unicode(该编码一个字符默认2个字节)
使用字符流读取数据的过程,Java标准库内部就自动针对数据的编码进行转码了.
import java.io.FileReader; import java.io.IOException; import java.io.Reader; public class Demo13 { public static void main(String[] args) { try(Reader reader = new FileReader("./test.txt")) { char[] buffer = new char[1024]; int n = reader.read(buffer); System.out.println(n); for (int i = 0; i < n; i++) { System.out.println(buffer[i]); } } catch (IOException e) { e.printStackTrace(); } } }
为什么要close关闭文件?
打开文件,其实是在该进程(PCB进程控制块)的文件描述符表(描述了该进程都要操作哪些文件.文件描述符表,可以认为是一个数组,数组的每个元素就是一个struct file对象(Linux内核)每个结构体就描述了对应操作的文件的信息.数组的下标,就称为“文件描述符")中,创建了一个新的表项.
每次打开一个文件,就相当于在数组上,占用了一个位置.
而在系统内核中,文件描述符表数组,是固定长度&不可扩容的.
除非主动调用close,关闭文件,此时,才释放出空间.
如果代码里一直打开,不去关闭,就会使这里的资源越来越少,把数组搞满了,后续再打开文件就会打开失败.
这个问题称为"文件资源泄露"
"内存泄露"
C进阶,动态内存管理.malloc申请内存.free释放.
没释放,就会内存泄露.
(3)文件写操作
写字节
FileOutputStream的构造方法:
OutputStream写文件涉及的方法:
import java.io.FileOutputStream; import java.io.IOException; public class Demo11 { public static void main(String[] args) { try(FileOutputStream outputStream = new FileOutputStream("./test.txt")) { outputStream.write(0xe4); outputStream.write(0xbd); outputStream.write(0xa0); outputStream.write(0xe5); outputStream.write(0xa5); outputStream.write(0xbd); } catch (IOException e) { e.printStackTrace(); } } }
注意:这里的写操作会将之前的内容覆盖
FileOutputStream outputStream = new FileOutputStream("./test.txt",true)
byte[] buffer = new byte[] { (byte)0xe4, (byte)0xbd, (byte)0xa0, (byte)0xe5, (byte)0xa5, (byte)0xbd }; outputStream.write(buffer);
byte[] buffer = new byte[] { (byte)0xe4, (byte)0xbd, (byte)0xa0, (byte)0xe5, (byte)0xa5, (byte)0xbd }; outputStream.write(buffer, 1, 3);
InputStream / OutputStream 读写数据就是按照字节来操作的.如果要读写字符的话(中文)
此时,就需要程序员手动的来区分出哪几个字节是一个字符,再确保把这几个字节作为整体来写入,否则就会出现以上状况
写字符
FileWriter的构造方法:
Writer写文件涉及的方法:
import java.io.FileWriter; import java.io.IOException; import java.io.Writer; public class Demo14 { public static void main(String[] args) { try(Writer writer = new FileWriter("./test.txt")) { writer.write("hello world"); }catch (IOException e) { e.printStackTrace(); } } }
此时的写操作也会发生覆盖,可以显性加上true来表示追加
Writer writer = new FileWriter("./test.txt",true)
(4)Scanner搭配流对象进行读取
Scanner scanner = new Scanner(System.in);
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) { try(InputStream inputStream = new FileInputStream("./test.txt")) { Scanner scanner = new Scanner(inputStream); while (scanner.hasNextInt()) { System.out.println(scanner.nextInt()); } }catch (IOException e) { e.printStackTrace(); } } }
4.文件操作案例
(1)扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要删除该文件
import java.io.File;
import java.util.Scanner;
public class Demo16 {
private static void scan(File currentFile,String key) {
//不是目录则直接返回
if (!currentFile.isDirectory()) {
return;
}
//获取所有文件
File[] files = currentFile.listFiles();
if (files == null || files.length == 0) {
return;
}
for (File f: files) {
if (f.isFile()) {
//是普通文件则删除
doDelete(f,key);
} else {
//针对目录继续进行递归处理
scan(f,key);
}
}
}
private static void doDelete(File f, String key) {
if (!f.getName().contains(key)) {
//文件中不包含指定关键字
return;
}
//提示用户是否确认要删除
Scanner scanner = new Scanner(System.in);
System.out.println(f.getAbsolutePath() + "是否确认要删除 Y/n");
String choice = scanner.next();
if (choice.equals("Y") || choice.equals("y")) {
f.delete();
}
}
public static void main(String[] args) {
//1.指定搜索目录
System.out.println("请输入要搜索的路径:");
Scanner scanner = new Scanner(System.in);
//2.获取路径
String rootPath = scanner.next();
File rootFile = new File(rootPath);
//3.判断路径是否存在 不存在则直接返回 存在则输入删除文件名字的关键字
if (!rootFile.isDirectory()) {
System.out.println("输入的路径不存在");
return;
}
System.out.println("请输入要删除的文件名字的关键字");
String key = scanner.next();
//4.递归进行查找
scan(rootFile,key);
}
}
(2)进行普通文件的复制,复制到当前目录
import java.io.*;
import java.util.Scanner;
public class Demo17 {
public static void main(String[] args) {
//1。输入文件路径并作校验
Scanner scanner = new Scanner(System.in);
System.out.println("请输入源文件路径:");
String srcPath = scanner.next();
File srcFile = new File(srcPath);
if (!srcFile.isFile()) {
System.out.println("源文件路径有误");
return;
}
System.out.println("请输入目标文件的路径:");
String destPath = scanner.next();
File destFile = new File(destPath);
if (!destFile.getParentFile().isDirectory()) {
System.out.println("目标文件的路径有误");
return;
}
//2.执行复制过程
try(InputStream inputStream = new FileInputStream(srcFile);
OutputStream outputStream = new FileOutputStream(destFile)) {
while (true) {
byte[] buffer = new byte[1024];
int n = inputStream.read(buffer);
System.out.println("n = " + n);
if (n == -1) {
break;
}
outputStream.write(buffer,0,n);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
(3)扫描指定目录,并找到名称或者内容中包含指定字符的所有普通文件(不包含目录)
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.Scanner;
public class Demo18 {
//扫描指定目录,并找到名称或者内容中包含指定字符的所有普通文件(不包含目录)
public static void main(String[] args) {
//1.获取要搜索的文件路径并判断正误
Scanner scanner = new Scanner(System.in);
System.out.println("请输入搜索路径:");
String rootPath = scanner.next();
File rootFile = new File(rootPath);
if (!rootFile.isDirectory()) {
System.out.println("要搜索的路径有误");
return;
}
System.out.println("请输入要搜索的查询词");
String key = scanner.next();
//2.进行递归搜索判断
scan(rootFile,key);
}
private static void scan(File rootFile, String key) {
if (!rootFile.isDirectory()) {
return;
}
File[] files = rootFile.listFiles();
if (files == null || files.length == 0) {
return;
}
for(File f : files) {
if (f.isFile()) {
//是普通文件则进行查询关键字操作
doSearch(f,key);
} else {
scan(f,key);
}
}
}
private static void doSearch(File f, String key) {
StringBuilder stringBuilder = new StringBuilder();
try(Reader reader = new FileReader(f)) {
char[] buffer = new char[1024];
while(true) {
int n = reader.read(buffer);
if (n == -1) {
break;
}
String s = new String(buffer,0,n);
stringBuilder.append(s);
}
}catch (IOException e) {
e.printStackTrace();
}
if (stringBuilder.indexOf(key) == -1) {
System.out.println("未找到");
}
System.out.println("找到了" + f.getAbsolutePath());
}
}
注意:此处这里的代码逻辑,效率是非常低的.每次查询,都会涉及到大量的硬盘IO操作.
尤其是硬盘上可能有一些大的文件.
这种思路,不能适应频繁查询场景,也不能适应目录中文件数目特别多,特别大的场景
像搜索引擎中,进行搜索的过程
也就是在文件中查找内容是否被包含的过程.
搜索出来的结果其实就是一些html文件.
这些html文件里面一定是包含你的查询词(或者和你的查询词有关联)
搜索引擎这样的场景,不能通过上述"遍历文件"方式实现的