文章目录
提示:以下是本篇文章正文内容,下面案例可供参考
一、认识文件
平时说的文件一般都是指存储在硬盘上的普通文件
比如txt,jpg,mp4,rar等,这些文件都可以认为是普通文件,它们都是在硬盘上存储的
在计算机中,文件可能是一个广义的概念,就不只是包含普通文件,还可以包含目录(把目录称为目录文件)
在操作系统中,还会使用文件来描述一些其他硬件设备或软件资源。
比如网卡(硬件),但是操作系统中就把网卡这样的硬件也给抽象成了一个文件——简化开发
比如显示器/硬盘,操作系统也是认为是文件
ps:当前讨论的文件主要还是针对普通文件,后续学习一些其他的硬件设备对应的文件,仍然是通过类似的代码操作的。
关于硬盘的一些小知识:
1.1树型结构组织和目录
计算机中,保存管理文件,是通过操作系统中“文件系统”这样的模块来负责的。
在文件系统中,一般是通过“树型”结构来组织磁盘上的目录和文件的
注意:这里的树不是二叉的,是N叉的
整体的文件系统,就是这种树形结构,
如果是一个普通文件,就是树的叶子结点
如果是一个目录文件,目录中就可以包含子树,这个目录就是非叶子结点
这个树上的每个节点上的子树都可以有N个,这就是一个N叉树了
1.2文件路径(重要)
操作系统中,就通过“路径”这样的概念,来描述某个具体文件/目录的位置。
路径这里有两种描述风格:
1.绝对路径:以盘符开头的
比如E:\java-ee\text.txt 这种
2.相对路径:以“.”或“. .”开头的,其中“.”表示当前路径,“. .”表示当前路径的父目录(上级路径)
而谈到相对路径,必须有一个基准目录,相对路径就是从基准目录出发,按照一个啥样的路径找到对应的文件。
举例说明:
如下是我家到一个餐馆的路线图
绝对路径:我从我家到这个餐馆路线——先向西走,再向北走,再向西走,再向北走
相对路径:比如我现在正在路上的某个点,不在我家,如图:
那么我现在去餐馆,只要向西,再向北就可以了。
总结:
绝对路径——无论我现在在哪个位置,绝对路径都是从起点到目标
相对路径——从我当前所处位置,到目标位置
1.3文本文件和二进制文件
站在我们程序员角度,文件主要有两类:
1.文本文件:
里面存储的是字符,文本文件本质上也是存字节的,但是文本文件中,相邻的字节在一起恰好能构成一个个字符
2.二进制文件:
里面存储的是字节,字节与字节之间没有联系
快速判断一个文件是文本文件还是二进制文件方法:
用记事本打开,如果打开后是乱码,就是二进制文件;不是乱码,就是文本文件。
类似的,像日常中使用的,
.txt, .c, .java都属于文本文件
.doc,.ppt,.exe,.zip,.class等等都属于二进制文件
ps:word、excel这种office系列的文件都是二进制的
因为这种软件保存的不单单是文本,而是“富文本”,文本中有各种格式化信息(字体大小,颜色等等)
二、java中操作文件
java中操作文件,主要是包含两类操作
1.文件系统相关操作:
指的是通过“文件资源管理器”,能够完成的一些功能
1)列出目录中有哪些文件
2)创建文件
3)创建目录
4)删除文件
5)重命名文件
…
2.文件内容相关操作:
2.1File概述
我们在java中提供了一个File类,通过这个类来描述上述操作。首先,这个File类就描述了一个文件/目录,基于这个对象就可以实现上面的功能。
2.1.1构造方法
File的构造方法,能够传入一个路径,来指定一个文件,这个路径可以是绝对路径,也可以是相对路径。
构造好方法后,就可以通过这个方法,来完成一些具体的功能了。
2.1.2方法
(图片来自比特就业课)
路径和绝对路径的相关方法:
代码示例如下:
public static void main(String[] args) throws IOException {
File f=new File("d:/input.txt");//绝对路径
System.out.println(f.getParent());//获取文件父目录
System.out.println(f.getName());//获取文件名
System.out.println(f.getPath());//获取文件路径
System.out.println(f.getAbsoluteFile());//获取绝对路径
System.out.println(f.getCanonicalPath());//这也是获取绝对路径,
//但是这里要抛一个异常,IOException表示输入输出可能会有问题
System.out.println("========分割线========");
File f2=new File("./input.txt");//相对路径
//谈到相对路径,一定得先明确基准路径
//上述代码中是看不出基准路径是哪个的
//——基准路径是由运行java程序的方式来确定的
//1.如果通过idea方式,那么基准路径就是当前java项目的路径
//2.如果通过命令行的方式,此时执行命令所在的目录,就是基准路径(实际中几乎用不掉2这种情况)
//3.后面会学到,把一个java代码打成war包,放到tomcat上运行,这种基准路径就是tomcat的bin目录
System.out.println(f2.getParent());//获取文件父目录
System.out.println(f2.getName());//获取文件名
System.out.println(f2.getPath());//获取文件路径
System.out.println(f2.getAbsoluteFile());//获取绝对路径
System.out.println(f2.getCanonicalPath());//这也是获取绝对路径
}
运行结果如下:
分析:
判断文件的相关方法:
public static void main(String[] args) {
File f=new File("d:/input.txt");//d盘中有这样一个文件
System.out.println(f.exists());//判断文件是否存在——打印true
System.out.println(f.isDirectory());//是不是目录——打印false
System.out.println(f.isFile());//是不是文件——打印true
System.out.println("======分割线======");
File f2=new File("./input.txt");//当前java项目目录下没有这样一个文件
System.out.println(f2.exists());//判断文件是否存在——打印false
System.out.println(f2.isDirectory());//是不是目录——打印false
System.out.println(f2.isFile());//是不是文件——打印false
}
运行结果如下:
解释如下:
文件的创建与删除的方法:
创建
public static void main(String[] args) throws IOException {
//文件的创建与删除
File f=new File("./test.txt");
System.out.println(f.exists());
System.out.println("创建文件开始");
f.createNewFile();
System.out.println("创建文件结束");
System.out.println(f.exists());
}
一开始没有这个文件,所以你exist判断的时候,会打印false。后面创建了这个文件,你exist再判断就是true了
删除
public static void main(String[] args) {
File f=new File("./test.txt");
System.out.println(f.exists());
f.delete();//删除文件
System.out.println(f.exists());
}
一开始存在f这个文件,然后你删了,再去exist判断就没有了
创建目录相关方法:
public static void main(String[] args) {
//创建一级目录
File f=new File("./a");
f.mkdir();//创建目录
System.out.println(f.isDirectory());//判断是不是目录——打印true
//创建多级目录
File f2=new File("./b/c");
f2.mkdir();
System.out.println(f2.isDirectory());//打印false——mkdir不能创建多级目录
File f3=new File("./b/c");
f3.mkdirs();
System.out.println(f3.isDirectory());//打印true——创建多级目录要用mkdirs
}
运行结果如下:
list和listFiles:
public static void main(String[] args) {
File f=new File("./aaa/bbb/ccc");
f.mkdirs();
File f2=new File("./aaa");
System.out.println(f2.list());
//直接打印是[Ljava.lang.String;@1b6d3586——表示字符串的数组
//正确打印方式:
System.out.println(Arrays.toString(f2.list()));//打印[bbb]
System.out.println(Arrays.toString(f2.listFiles()));//打印[.\aaa\bbb]
}
运行结果如下:
文件改名方法:
public static void main(String[] args) {
File f=new File("./aaa");
File f2=new File("./zzz");
f.renameTo(f2);//把aaa的名字改成zzz
}
2.2文件内容的读写——数据流
我们下面要说的都是流(Stream)对象。此处我们说的流,就像水龙头里的水流,打开开关就源源不断。比如我们这里想通过这个水龙头,接100ml的水:我们可以1次接10ml,分10次接;也可以1次接20ml,分5次接…
而我们想通过这样的流对象来读取100字节也是同样道理,比如我们想读100字节:可以1次读10字节,读10次;也可以1次读20字节,读5次…
ps:读是和取水一样,写也是同理
针对文件内容的读写,java标准库提供了一组类,首先按照文件的内容,分成了两个系列:
(1)字节流对象,针对二进制文件,以字节为单位进行读写的
读:inputStream
写:OutputStream
我们现在用FileInputStream演示:
比如我们现在D盘里有个文件test.txt
一次读一个字节:
//1次读1个字节
public static void main(String[] args) throws IOException {
//构造方法中需要指定打开文件的路径
//此处的路径可以是绝对路径,也可以是相对路径,还可以是File对象
//1.创建对象
InputStream inputStream=new FileInputStream("D:/test.txt") ;
try {
//2.尝试一个一个字节的读取,把整个文件读完
while(true){
//read有三个版本的重载
//1.无参:1次读1个字节,返回值是读到的这个字节
//2.一个参数版本:1次读n个字节,把读的结果放到参数中指定的数组中,返回值就是读到的字节数
//3.三个参数版本:1次读n个字节,把读的结果放到参数中指定的数组中,返回值就是读到的字节数
//不是从数组的起始位置放,而是从中间位置放(off这个下标),len表示最多能放多少元素(字节)
int b=inputStream.read();
//为什么是一个字节一个字节的读取,返回值是int?
//1个字节的范围是:0~255或-128~127
//而这个方法里规定:-1"是已经读完文件"的标记,
//如果你正常用byte,就没办法知道-1是正好有一个-1的数,还是说已经读完了
//所以我们这里会用更大范围的类型
//针对字符流,也有类似的设定:1次读一个char
if(b==-1){
break;
}
System.out.print(b+" ");
}
} catch (IOException e) {
e.printStackTrace();
}finally {//放到finally里面close是防止前面报异常导致无法执行到close
//读完之后,要关闭文件,释放资源
inputStream.close();
}
}
运行结果如下,结果正好对应test.txt里面的字母ASCII码:
如果你嫌上面的代码麻烦,还有改进的方法:
//改进写法
public static void main(String[] args) throws FileNotFoundException {
try(InputStream inputStream=new FileInputStream("D:/test.txt")){
while(true){
int b=inputStream.read();
if(b==-1){
break;
}
System.out.println(b);
}
} catch (IOException e) {
e.printStackTrace();
} //这个代码中,没有显示的调用close,但是try会自动帮我们调用
}
一次读若干字节:
//1次读若干字节
public static void main(String[] args) {
try(InputStream inputStream=new FileInputStream("D:/test.txt")){
while(true){
byte[] buffer=new byte[1024];
int len=inputStream.read(buffer);
if(len==-1){
break;//返回-1说明读取完毕
}
for(int i=0;i<len;i++){
System.out.print(buffer[i]+" ");
}
}
}catch (IOException e){
e.printStackTrace();
}
}
按字节流写文件:
//使用字节流,写文件
public static void main(String[] args) {
try(OutputStream outputStream=new FileOutputStream("d:/test.txt")){
byte[] buffer=new byte[]{97,98,99};//写入abc
//注意!每次按照写方式打开文件,都会清空文件的内容,然后从头开始写
//ps:也有一种不清空文件,而是在原有内容上追加写的流对象
outputStream.write(buffer);
}catch (IOException e){
e.printStackTrace();
}
}
(2)字符流对象,针对文本文件,是以字符为单位进行读写的
读:Reader
写:Writer
按字符流读文件:
public static void main(String[] args) {
try(Reader reader=new FileReader("d:/test.txt")){
//按字符来读
while(true){
char[] buffer=new char[1024];
int len=reader.read(buffer);//不一定能读满1024
if(len==-1){
break;
}
//法一
for(int i=0;i<len;i++){
System.out.print(buffer[i]);//打印abc
}
System.out.println("========分割线==========");
//法二
String str=new String(buffer,0,len);//从下标0读len个
//如果这里传入的数组是byte数组,还可以手动指定utf-8,字符集来避免乱码
System.out.println(str);//打印abc
}
}catch (IOException e){
e.printStackTrace();
}
}
按字符流写文件:
public static void main(String[] args) {
try(Writer writer=new FileWriter("d:/test.txt")){
writer.write("i love china");
}catch (IOException e){
e.printStackTrace();
}
}
两个系列的读和写都是抽象类,实际使用往往是这些类的子类,比如:
FileInputStream
FileOutputStream
FileReader
FileWriter
上面8个类中,
黑色字体的抽象类既可以针对普通文件的读写,也可以针对特殊文件(网卡、socket文件)进行读写。
红色字体的都是特指针对普通文件进行读写的。
ps:虽然这里类很多,但是实际操作这些类的方法都是一样的
三、一些案例
3.1实现查找文件并删除
import java.io.File;
import java.io.IOException;
import java.util.Scanner;
//案例1:实现查找文件并删除
public class Demo12 {
public static void deleteFile(File f){//删除文件
try {
System.out.println("你确认要删除文件"+f.getCanonicalPath()+"吗?");
System.out.println("请输入yes/no来进一步确认:");
Scanner scanner=new Scanner(System.in);
String choice=scanner.next();
if(choice.contains("Y")||choice.contains("y")){
f.delete();
System.out.println("文件已删除");
}else{
System.out.println("删除操作已取消");
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void scanDir(File rootDir,String toDeleteName){//递归遍历并删除
//1.先列出rootDir中有哪些内容
File[] files=rootDir.listFiles();
if(files==null){
//rootDir是一个空目录
return;
}
//不为空目录
//2.遍历当前列出的这些内容,如果是普通文件,就检查文件名是否是要删除的文件
//如果还是目录,就继续递归
for(File f:files){
if(f.isFile()){
//普通文件的情况
if(f.getName().contains(toDeleteName)){
//不要求名字完全一样,只要文件名中包含了关键字即可删除
deleteFile(f);
}else if(f.isDirectory()){
//目录就继续递归遍历
scanDir(f,toDeleteName);
}
}
}
}
public static void main(String[] args) {
//1.先输入要扫描的目录,以及要删除的文件
Scanner scanner=new Scanner(System.in);
System.out.println("请输入要扫描的路径:");
String rootDirPath=scanner.next();
System.out.println("请输入要删除的文件名:");
String toDeleteName=scanner.next();
File rootDir=new File(rootDirPath);
if(!rootDir.isDirectory()){
System.out.println("输入的扫描路径有误,请重新输入:");
}
//2.遍历目录,目录是一个树形结构,我们用递归方式
scanDir(rootDir,toDeleteName);
}
}
3.2文件的复制
需要让用户指定两个文件路径,一个是原路径(被复制的文件),一个是目标路径(复制之后生成的文件)
import java.io.*;
import java.util.Scanner;
public class Demo13 {
public static void main(String[] args) {
//1.输入两个路径
Scanner scanner=new Scanner(System.in);
System.out.println("请输入要拷贝的路径:");
String src=scanner.nextLine();
System.out.println("请输入要拷贝的路径:");
String dest=scanner.nextLine();
File srcFile=new File(src);
if(!srcFile.isFile()){
System.out.println("输入的源路径不正确");
return;
}
//此处不太需要检查目标文件是否存在,OutputStream写文件时可以自动创建不存在的文件
//2.读取源文件内容,拷贝到目标文件中
try(InputStream inputStream=new FileInputStream(src)){
try(OutputStream outputStream=new FileOutputStream(dest)){
//把inputStream的内容读出来,再写入到outputstream
byte[] buffer=new byte[1024];
while(true){
int len=inputStream.read(buffer);
if(len==-1){
//读取完
break;
}
//写入的时候,不能把整个buffer都写进去,buffer可能是只有一部分有效
//(比如一个文件只有24字节,但是buffer是1024,你只需要读24个字节即可)
outputStream.write(buffer,0,len);
}
}
}catch (IOException e){
e.printStackTrace();
}
}
}
3.3找到指定名称或内容包含指定字符的所有普通文件
这个案例的核心就是进行文件内容的查找
先输入一个路径,再输入一个要查找的文件内容的“关键词”
递归的遍历文件,看哪个文件的内容包含关键词,就把对应文件的路径打印出来
先递归遍历这个文件,针对每个文件都打开,并读取内容,再进行字符串查找即可。
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.Scanner;
public class Demo14 {
private static boolean containsWord(File f, String word) {//判断文件内容是否包含word
StringBuilder stringBuilder=new StringBuilder();
//把f中的内容都读出来,放到一个StringBuilder中
try(Reader reader=new FileReader(f)){
char[] buffer=new char[1024];
while(true){
int len=reader.read(buffer);
if(len==-1){
break;
}
//把这段读到的结果,放到StringBuilder中
stringBuilder.append(buffer,0,len);
}
}catch (IOException e){
e.printStackTrace();
}
return stringBuilder.indexOf(word)!=-1;
//indexOf返回的是字符串下标,如果word在stringBuilder中不存在,返回-1
}
private static void scanDir(File rootDir,String word) throws IOException {
//1.先列出rootDir中有哪些内容
File[] files=rootDir.listFiles();
if(files==null){
return;
}
//2.遍历每个元素,针对普通文件和目录分别进行处理
for(File f:files){
if(f.isFile()){
//文件进行内容查找
if(containsWord(f,word)){
System.out.println(f.getCanonicalPath());
}
}else{
//目录就继续往下递归
scanDir(f,word);
}
}
}
public static void main(String[] args) throws IOException {
//1.输入要扫描的文件路径
Scanner scanner=new Scanner(System.in);
System.out.println("请输入要扫描的路径");
String rootDirPath=scanner.next();
System.out.println("请输入要查找的关键词");
String word=scanner.next();
File rootDir=new File(rootDirPath);
if(!rootDir.isDirectory()){
System.out.println("输入的路径非法");
return;
}
//2.递归的进行遍历
scanDir(rootDir,word);
}
}