文件操作
关于文件,自然不用多说。狭义上的文件。针对硬盘这种持久化存储的I/O设备,当我们想要进行数据保存时,往往不是保存成一个整体,而是独立成一个个的单位进行保存,这个独立的单位就被抽象成文件的概念,就类似办公桌上的一份份真实的文件一般。
文件除了有数据内容之外,还有一部分信息,例如文件名、文件类型、文件大小等并不作为文件的数据而存在,我们把这部分信息可以视为文件的元信息。
文件的路径分为绝对路径和相对路径
以盘符开头的路径为绝对路径,如:D:\program\qq\Bin\QQ.exe
以 . 或 … 开头的路径为相对路径,如: .\test.txt
Java中的文件操作
Java 中通过 java.io.File 类来对一个文件(包括目录)进行抽象的描述。
注意,有 File 对象,并不代表真实存在该文件
1 File概述
我们先来看看 File 类中的常见属性、构造方法和方法
属性
修饰符及类型 | 属性 | 说明 |
---|---|---|
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() | 判断用户是否对文件有可写权限 |
1.1 代码示例
示例一:get系列
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());
}
![image-20220805204648916](https://i-blog.csdnimg.cn/blog_migrate/05d24db57dd95436f20c4d87388b62ba.png)
示例二:判断文件是否存在,判断文件、目录,创建文件
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());
示例三:删除文件
public static void main(String[] args) throws InterruptedException {
//文件删除
File file = new File("helloworld.txt");
//file.delete(); 立即删除
Thread.sleep(5000);
file.deleteOnExit();//程序退出时才删除(用来创建一些临时文件)
System.out.println(file.exists());
}
示例四:创建目录
//创建目录
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());
//创建多级目录
//File file = new File("test/aa/1");
//file.mkdirs();
}
示例五:文件重命名
//文件重命名
public static void main(String[] args) {
File file1 = new File("./test.txt");
File file2 = new File("./test2.txt");
file1.renameTo(file2);// 用 file2 的名字给 file1 命名
}
2 文件的读写操作——数据流
![image-20220805205420144](https://i-blog.csdnimg.cn/blog_migrate/99182bd8738236b8919c6ed2b6044714.png)
2.1 读文件
InputStream 概述
方法
修饰符及 返回值类 型 | 方法签名 | 说明 |
---|---|---|
int | read() | 读取一个字节的数据,返回 -1 代表已经完全读完了 |
int | read(byte[] b) | 最多读取 b.length 字节的数据到 b 中,返回实际读到的数 量;-1 代表以及读完了 |
int | read(byte[] b, int off, int len) | 最多读取 len - off 字节的数据到 b 中,放在从 off 开始,返 回实际读到的数量;-1 代表以及读完了 |
void | close() | 关闭字节流 |
InputStream 只是一个抽象类,要使用还需要具体的实现类。关于 InputStream 的实现类有很多,基
本可以认为不同的输入设备都可以对应一个 InputStream 类,我们现在只关心从文件中读取,所以使
用 FileInputStream
read的不同使用方式
利用FileInputStream进行读取文件
示例一
我们为了能让close
一定被执行,这样做
public static void main(String[] args) {
//使用一下 InputStream
InputStream inputStream = null;
try {
//1.打开文件
inputStream = new FileInputStream("./test2.txt");
//2.读取文件
// while(true){
// int b = inputStream.read();
// if(b == -1){
// //文件读完了
// break;
// }
// System.out.println(b);
// }
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]);
// }
String s = new String(b,0,len,"utf8");
System.out.println(s);
} catch (IOException e ){
e.printStackTrace();
} finally {
//3.关闭文件
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
![image-20220805210346592](https://i-blog.csdnimg.cn/blog_migrate/3a260232dfa4efd494957cdfee132846.png)
但是在finally里还有个try-catch 会让代码很冗杂
我们这样做
public static void main(String[] args) {
try(InputStream inputStream = new FileInputStream("test2.txt")){
//读文件
byte[] b = new byte[1024];
int len = inputStream.read(b);
String s = new String(b,0,len,"utf8");
System.out.println(s);
}catch (IOException e){
e.printStackTrace();
}
}
代码二更为推荐
示例二:
当文件中的内容为 hello
![image-20220805210416261](https://i-blog.csdnimg.cn/blog_migrate/551b46c57cac69957912b71c0d003e81.png)
使用Reader字符流读取文件
当文件中的内容变成了汉字:你好
代码:
//使用字符流读一下文件
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.println(buffer[i]);
}
reader.close();
}
我们还有更简单的方法
利用 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();
}
2.2 写文件
字节流:OutputStream/FileOutputStream
字符流:Writer/FileWriter
和 Scanner相对的,还可以使用 PrintWriter
来简化针对字符流的写入操作
OutputStream
概述
方法
修饰 符及 返回 值类 型 | 方法签名 | 说明 |
---|---|---|
void | write(int b) | 写入要给字节的数据 |
void | write(byte[] b) | 将 b 这个字符数组中的数据全部写入 os 中 |
int | write(byte[] b, int off, int len) | 将 b 这个字符数组中从 off 开始的数据写入 os 中,一共写 len 个 |
void | close() | 关闭字节流 |
void | flush() | 重要:我们知道 I/O 的速度是很慢的,所以,大多的 OutputStream 为 了减少设备操作的次数,在写数据的时候都会将数据先暂时写入内存的 一个指定区域里,直到该区域满了或者其他指定条件时才真正将数据写 入设备中,这个区域一般称为缓冲区。但造成一个结果,就是我们写的 数据,很可能会遗留一部分在缓冲区中。需要在最后或者合适的位置, 调用 flush(刷新)操作,将数据刷到设备中 |
OutputStream
同样只是一个抽象类,要使用还需要具体的实现类。我们现在还是只关心写入文件中,
所以使用 FileOutputStream
利用 OutputStreamWriter 进行字符写入
一个字符一个字符地写
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();
}
}
写入一个字符串
public static void main(String[] args) {
try(OutputStream outputStream = new FileOutputStream("test2.txt")){
String s = "hello java";
outputStream.write(s.getBytes());
}catch (IOException e){
e.printStackTrace();
}
}
我们发现,每次写的时候都会把原本文件中的内容清空
![image-20220806104128254](https://i-blog.csdnimg.cn/blog_migrate/747dc2f978d496e8e6213f8792d37199.png)
是因为这行代码
使用Writer进行字符流写入
示例
public static void main(String[] args) {
try(Writer writer = new FileWriter("test2.txt")){
writer.write("hello world");
}catch (IOException e){
e.printStackTrace();
}
}
利用 PrintWriter 进行字符写入
public static void main(String[] args) {
try (OutputStream outputStream = new FileOutputStream("test2.txt")){
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.print("hello");
printWriter.flush();
}catch (IOException e){
e.printStackTrace();
}
}
我们发现,与上面的方法不同的是 多了个 printWriter.flush();
解释:PrintWriter是自带了缓冲区的,所谓的缓冲区其实就是一块内存空间
那么缓冲区里的内容什么时候会被刷新到硬盘中呢?
- 缓冲区满了时
- 显式调用
flush
(“冲水操作”)
所以在使用PrintWriter
时,别忘了要 flush
哦
3 小程序练习
我们学会了文件的基本操作 + 文件内容读写操作,接下来,我们实现一些小工具程序,来锻炼我们的能力
3.1 文件查找并删除
扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要
删除该文件
如何遍历目录?
"递归"的把这里所有的文件(子目录中的文件)都能够访问到
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);
}
/**
* 扫描目录
* @param rootDir
* @param 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();
}
}
}
}
上述过程其实就是一个 “深度优先遍历/搜索”
3.2 复制一个普通文件
普通文件,即:不是目录文件
把文件1复制成文件2
把文件1里的内容都按照字节读取出来,写入到文件2中
//实现复制文件的功能
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("复制完成!");
}
可以这样想象,在读文件的时候,在文件对象内部,有一个"光标",通过这个"光标"表示当前文件读到哪个位置了。
每次读操作,都会让光标往后移动, 直到文件末尾,再继续读,就会读到一个特殊的字符, EOF
3.3 文件深层查找(名称/内容)
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;
}
}
}
}
注:我们现在的方案性能较差,所以尽量不要在太复杂的目录下或者大文件下实验