一、文件的定义
狭隘的文件:指你的硬盘上的文件和目录.
广义的文件:泛指计算机中的硬件资源,操作系统中,把很多硬件设备和软件资源都抽象成了文件,按照文件的形式统一管理.比如网卡,操作系统也是把网卡抽象成了文件资源,所以说操作网卡其实和操作文件的方式是基本一样的.
而我们本章节只考虑狭义的文件.
二、路径
路径分为:绝对路径和相对路径
- 绝对路径:C:\Program Files\Java\jdk1.8.0_192
这样的路径就叫做绝对路径.
2.相对路径:以当前目录为基准,以.或者..开头(.有时候可以省略),找到指定路径.
当前目录在程序运行中被称为工作目录.
打开控制台,默认的工作路径就是:
如这个:想要定位111这个文件夹,我们就可以使用相对路径定位法.
此时工作目录就是"d:/tmp"
则111文件夹的相对路径就是:"./111".
但如果工作目录是"d:/tmp/222"
那么相对路径就是"../111"(注意:这里的".."指的是返回上一级目录)
三、文件的类型
现在的市面上有众多的文件:word、exe、图片、视频、音频......
这些不同的文件,可以归到两类的文件里去:
- 文本文件(存的是文本,字符串)
- 二进制文件(存的是二进制文件,不再是字符串了)
检验方式:直接用记事本打开文件,如果乱码了就是二进制文件,如果能够正常读取,就是文本文件.(因为记事本是以文本文件的方式进行存储)
另外,文本文件和二进制文件的操作方式是不同的.
四、Java对于文件的基本操作
1.
这个就相当于路径中的"/",如果代码需要进行多语言的运行,有的语言没有"/"的识别,则需要pathSeparator进行分割.
2.构造方法
最常见的构造方法指定路径的方法是第二个:使用文件的直接路径进行调用.
3.常用方法
构造方法中的文件路径不需要真实存在,因为File中有方法可以对文件进行手动创建文件.
就是这个方法:
绝对路径:
File类的方法都是"人如其名"
另外,在文件IO这块很容易抛出异常,因为文件是存放在硬盘中的,而硬盘是电脑中最容易出问题的部分了(如坏了或者说硬盘满了).
相对路径:
注意:这里的文件路径是在Jvm包中的test.txt,也就是在
这个工作路径下的"test.txt"
这里着重说一下getAbsolutePath()与getCanonicalPath()两个方法,他们都是获取文件的绝对路径,只不过
getAbsolutePath()是获取不加修饰的文件绝对路径,而getCanonicalPath()是获取经过修饰的绝对路径.
在此时,Idea的目录下面没有创建"test.txt",所以说判断是否存在是否是文件是否是目录都是false
在手动创建完文件后,是否存在,是否是文件都是true是否是目录都是false.
我们可以创建文件当然也可以删除文件:
另外,还有一个deleteOnExit作用是:能够在程序结束的时候将这个文件删除.
(被称为临时文件)
比如我们在打开word文档时就会生成一个临时文件:
关闭目录后就会消失.
这个临时文件相当于实时保存了你文件中的内容,如果硬盘损坏或者断电了,没有保存的内容就没了.
目录的创建:
运用file.mkdirs()
mkdirs()可以创建多级目录.
文件重命名:
这样就可以把之前的"test"文件夹改名为testAAAAAAAA.
五、"流"对象
1."流"的概念
针对文件内容,使用"流对象"进行操作.
什么叫做"流对象":类比水流,就是指对文件的操作就好比水流一样,想流多少,就流多少.
就是说编译器操作文件也是一样,想读多少,就读多少.假如共有100个字节,我想读100个就读1次,我想读10个就读10次.
- 操控"流"对象
操控"流"对象,总共分为两大类:
1)字节流:(操作二进制文件的)
主要的两个类:InputStream、OutputStream 子类:FileInputStream、FileOutputStream
注意:InputStream类是不能new的,因为InputStream类是个抽象类.
首先是开与关:
然后就是如操作了:
我们的读操作有三个版本:
无参数版本:一次读一个字节.
一个参数版本:把读到的内容存到这个数组里,(此处参数是输出型参数),返回值是读取到的字节数
三个参数版本:和2类似只不过是在一定空间里尽可能填满数组.
注意:虽然read读取的是一个字节,但它的返回值是int
原因:当read读完文件时,就会返回-1,来表示文件已经读完.
结果返回的是字符,返回的数字可以用ASCII进行转换成对应的字符.
另外,还可以利用字节流读取的另外一种方式:(使用在read中填写字符数组的形式读取)
我们发现和上面的结果是一样的.
这里的传参操作,相当于把刚才准备好的数组将给read方法,让read方法内部针对这个方法进行填写.
此处的参数相当于"输出型参数".
在Java中通常是把输入的信息作为参数,输出的信息作为返回值.但也有少数情况,是使用参数来返回内容的(输出型参数),但这是在C++中很常见.
inputStream.read()的本质是的读取字节,如果在读取的过程中发现读不到内容后才返回-1.上述代码设置了数组的最大阅读长度:1024.
如果读取的内容长度大于1024,程序则会覆盖之前读的内容继续读取,直到返回-1.
所以说文件操作一般都是读一部分处理一部分,不会等文件全部多玩再进行处理.
这里回忆一下抽象类和接口有什么区别:
抽象类和普通类:几乎没有区别,只不过抽象类不能够new而且带了抽象方法
接口比抽象类更加抽象,抽象:指信息的描述更加少.
抽象类还可以有普通成员,普通方法,抽象成员,抽象方法,但是接口就只能有抽象方法和抽象成员.
我们可以用intputStream读文件,也可以用outputStream写文件:
这里采用控制台循环输入的方式如果输入-1就停止.
注意:对于我们的OutputStream来说,默认情况下,会清空文件原有的内容.(这样的话之前的内容就没了)
如果我们不想清空之前的内容,流对象还提供了一个"追加写"对象,通过这个就可以实现不清空文件,把新的内容写到后面.
这里再说一下close()操作:
在我们多线程的PCB运行过程的同时,会运行一个文件描述符表(就是一个可以存放表的数组),这个表会记录每个线程打开的文件(这也是多线程进行信息交互的方式之一)打开文件操作会申请一个位置用来存放这个表,每次关闭文件,也就会把表从这个文件描述符表中释放掉.
如果我们不释放,会发生什么呢?
注意:文件描述符表这个数组是不会扩容的,虽然有自动释放功能,但可能不够即使及时,一但文件描述符表满了文件打开操作就会发生错误,导致程序崩溃.
但是,因为close()常见操作(就是如果出现if+break)之类的问题所以我们的常见操作是:
这是我们推荐的操作,在Java中被称为try with resources,在这个代码虽然没有显式的close,但是当try的部分执行完毕后,就可以自动执行到close的!!!
点入OutputStream中去,发现该类实现了Closeable接口,这个方法就是提供了close().
try with resources是文件IO中最方便的表述形式,所以我们后面讲全部采用这种形式.
2)字符流:(操作文本文件的)
主要的两个类:Reader、Writer 子类:FileReader、FileWriter
这些类的操控都是相对固定的,主要分为四个操作:1.打开文件(构造对象)2.关闭文件(close)3.读文件(read)针对InputStream、Reader4.写文件(write)针对OutputStream、Writer
跟上面一样的代码形式,作用是读取字符
因为汉字也是用字符表示的,所以也可以读取汉字.
写入方法也是一样:
这个我们来说一下Scanner:
如果我们点入System可以发现其实System也是一个流对象
所以说,按道理来讲我们将inputStream对象填入括号中也是可以的.
Scanner控制台就读取控制台内容,Scanner文件就读取文件内容,Scanner能读取所有的流对象.
六、案例
1.扫描目录,并找到名称中包含的指定字符所有普通文件(不包含目录),并且询问用户是否要删除该文件.
就相当于:
首先,做好准备工作,将代码的输入路径和指定删除文件部分完成:
接下来,完成目录的扫描能力部分:
我们首先要弄清目录是以书的形式存储的,对应的我们在遍历目录的时候可以使用递归的形式.如果是要删除文件,就删除,如果不是就下一个
这个代码要注意几点:
将输入的根目录一下的不管是目录还是文件都放入File[ ]数组,便于之后遍历数组.
判断是不是目录,如果是就继续递归,如果不是就将文件名和要删除文件进行比对,如果比对成功就删除.
在比对的过程中需要对用户进行询问,如果用户同意再删除,如果不同意就取消删除.
package IODemo6;
import java.io.File;
import java.util.Scanner;
/**
* Created with IntelliJ IDEA.
* Description:
* User: 86139
* Date: 2023-01-29
* Time: 17:53
*/
public class IODemo8 {
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in);
System.out.println("请输入要查找的路径:");
String path=scanner.next();
File root=new File(path);
if(!root.isDirectory()){
System.out.println("您输入的路径有误!!!");
return;
}
System.out.println("请输入您要删除的文件名:");
String filename=scanner.next();
scanDir(root,filename);
}
private static void scanDir(File root, String filename) {
File[] files=root.listFiles();
if(files==null){
return ;
}
for (File f:files) {
if(f.isDirectory()){
scanDir(f,filename);
}else{
if(f.getName().contains(filename)){
System.out.println("是否删除"+f.getAbsolutePath()+"文件");
Scanner scanner=new Scanner(System.in);
String choice=scanner.next();
if(choice.equals("y")||choice.equals("Y")){
f.delete();
System.out.println("删除成功!!!");
}else{
System.out.println("删除取消!!!");
}
}
}
}
}
}
2.进行普通文件的复制(就是把一个文件拷贝成另一个文件,把第一个文件按照字节的方式进行读取,然后拷贝到第二个文件里)
首先,进行文件的读取与判断:
接下来,打开源文件的输入流对象和目标文件的输出流对象:
然后我们采取读一个字节,写一个字节的形式进行拷贝:
这样代码就完成了.
我们发现拷贝成功了!!!
package IODemo6;
import java.io.*;
import java.util.Scanner;
/**
* Created with IntelliJ IDEA.
* Description:
* User: 86139
* Date: 2023-01-29
* Time: 18:35
*/
public class IODemo9 {
public static void main(String[] args) {
Scanner scanner=new Scanner(System.in);
System.out.println("请输入要拷贝文件的源目录:");
String srcPath=scanner.next();
System.out.println("请输入要拷贝文件的目标目录:");
String destPath=scanner.next();
File srcFile=new File(srcPath);
if(!srcFile.isFile()) {
System.out.println("您输入的源文件有误!!!");
}
File destFile=new File(destPath);
try(InputStream srcInputStream=new FileInputStream(srcFile);
OutputStream destOutputStream=new FileOutputStream(destFile)) {
while(true){
int b=srcInputStream.read();
if(b==-1){
break;
}
destOutputStream.write(b);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
注意:这里的outputStream能够无中生有,不需要目标文件存在.