文件路径、文件系统操作、字节流字符流、文件内容操作、自己实现文件查找 删除 复制、IO报错:拒绝访问
目录
一、什么是文件
文件的分类
文件路径
二、文件系统操作
四、文件内容操作
字节流字符流
1)字节流(二进制文件)
InputStream概述
FileInputStream 概述
OutputStream 概述
2)字符流(文本文件)
五、实现文件查找,删除,复制
根据文件内容查找:
删除:
复制:
IO拒绝访问
一、什么是文件
文件 就是在计算机的硬盘中躺着的,这些文件都是一个个单独的个体。
⽂件除了有数据内容之外,还有⼀部分信息,例如⽂件名、⽂件类型、⽂件⼤⼩等并不作为⽂件的数 据⽽存在,我们把这部分信息可以视为⽂件的元信息。
我们文件的存储 是依靠树型结构来存储的
目录:相当于文件夹,可能很多个文件放一起的
文件:一个单独的个体
文件的分类
即使是普通文件,存储的数据也不尽相同。
所以我们依靠数据的类型,把他们又分为 文本文件 和 二进制文件
文本文件:里面的内容在码表上“有据可查”,能查到对应的
二进制文件:里面的内容在码表上查不到,就是一堆乱码
啥是码表?
因为计算机只看得懂二进制,所以要把字解析给计算机看的,为了统一,就是根据码表来解析的。
码表也有很多种,最常见的是ASCII码,解析英文字母。但是我们的中文啊,还有其他的特殊字符,就用不了,就需要UTF8或者Unicode字符集等等。
值得注意的是utf8中,一个汉字3个字节。Unicode一个汉字2个字节。
查看字符编码(UTF-8) (mytju.com)
如何分辨是 文本文件 还是 二进制文件呢?
最简单的办法,就是把文件拖到记事本上:看下图,像png这种的一拖到记事本,就是乱码,所以它是二进制文件。那么剩下的就是文本文件了
文件路径
我们既然知道了文件的分类,如何找到这个文件呢?那就需要 文件路径 了
文件路径分为 绝对路径 和 相对路径
绝对路径:从最早的根目录开始(就是C盘E盘那些),一直到达这个结点
相对路径:可以从任意目录开始(相对这个目录),一直到这个结点
如下,如果要找test.txt这个文件,绝对路径:E:\code\J20240926-FileIO\test.txt
相对路径需要看从哪里出发(.是当前目录 ..是上一级目录)
若从src里面出发:..\test.txt
若从这个界面的路径出发:.\test.txt
二、文件系统操作
我们文件有个类叫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 | getParent() | 返回 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() | 判断⽤⼾是否对⽂件有可写权限 |
代码如下:
public class demo1 {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.getAbsoluteFile());System.out.println(file.getCanonicalPath());}
}//执行结果
.
test.txt
.\test.txt
E:\code\J20240926-FileIO\.\test.txt
E:\code\J20240926-FileIO\test.txt
public class demo2 {public static void main(String[] args) throws IOException {File file=new File("./hello");System.out.println(file.exists());System.out.println(file.isFile());System.out.println(file.isDirectory());System.out.println(file.delete());System.out.println(file.createNewFile());}
}true
false
true
false
false
public class demo3 {public static void main(String[] args) {File file=new File("./hello/ll/qq");System.out.println(Arrays.toString(file.list()));System.out.println(file.mkdirs());}
}[]
false
public class demo4 {public static void main(String[] args) {File srcFile =new File("./test.txt");File descFIle =new File("newTest.txt");srcFile.renameTo(descFIle);}
}
如何打印目录中的文件呢?
使用递归,把看看是不是目录,里面有没有东西,有的话直接装到一个File[ ]中,然后一个一个打印,遇到目录就继续递归。
public class demo5 {private static void scan(File f) throws IOException {//1.判断是不是目录if(!f.isDirectory()){return;}//2.判断目录内容File[] files=f.listFiles();if(files==null||files.length==0){return;}//3.打印当前目录System.out.println(f.getCanonicalPath());//4.打印文件for(File file:files){if(file.isFile()){System.out.println(file.getCanonicalPath());}else{scan(file);}}}public static void main(String[] args) throws IOException {File file=new File("./");scan(file);}
}
四、文件内容操作
文件内容操作其实很简单!只需要三步:打开、写或读、关闭
创建了字节流字符流,就相当于打开了文件,用完之后一定要进行关闭。
为什么操作完文件内容之后,要关闭文件呢?
打开文件,其实是在该进程的文件描述表中,创建了一个新的表项。
文件描述符表:描述了该进程都要操作哪些文件,它可以认为是一个数组,数组的每个元素就是一个结构体文件对象(linux中)每个结构体就描述了对应操作的文件的信息,数组的下标被称为“文件描述符”。
每次打开一个文件,就相当于在数组上,占用的一个位置,而在系统内核中,文件描述符表数组,是固定长度 & 不可扩容的。用一个少一个,如果没了就打开不了文件了。
代码如下:
(推荐使用第二种的语法糖,用起来方便简单吃起来很甜。如果多个就用分号隔开,InputStream下面详讲)
public class demo7 {//使用finallypublic static void main1(String[] args) throws IOException {InputStream file=null;try {file=new FileInputStream("./test.txt");}catch (IOException e){e.printStackTrace();}finally {try {file.close();}catch (IOException e){throw new RuntimeException();}}}
//第二种使用try with resources,会自动帮你关闭public static void main(String[] args) throws IOException {try(InputStream inputStream=new FileInputStream("./test.txt")) {//....}}
}
而进行写和读,则需要用到字节字符流的一些方法了。
字节流字符流
数据流就是计算机对数据的 读取 和 写入 。
为什么叫流呢?
这其实是一个很生动形象的比喻,就像我们接水流100ml,我们可以拿个杯子一次接1ml,接100次,也可以接2ml,接50次.....也可以接100ml,接1次。
所以我们计算机的,可以一次读1个字节,读100次,也可以读2个字节,读50次...也可以读100个字节,读一次。(字符流的话就是读字符)
可以分为两大类:
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
FileInputStream 概述
构造方法
签名 | 说明 |
FileInputStream(File file) | 利⽤ File 构造⽂件输⼊流 |
FileInputStream(String name) | 利⽤⽂件路径构造⽂件输⼊流 |
public class demo8 {public static void main(String[] args) throws IOException {try (InputStream inputStream = new FileInputStream("./test.txt")) {while (true) {byte[] buffer = new byte[1024];inputStream.read();int n = inputStream.read(buffer);if (n == -1) {break;}for (int i=0;i<n;i++){System.out.printf("0x%x\n",buffer[i]);}}}}
}
这里的buffer是输出型参数,就是被当参数传进去,然后被填满着出来。
就像你去食堂吃饭,把空的盘子给阿姨,阿姨给你抖抖抖,装到你盘子里,然后还给你。
利⽤ Scanner 进⾏字符读取
上述例⼦中,我们看到了对字符类型直接使⽤ InputStream 进⾏读取是⾮常⿇烦且困难的,所以,我 们使⽤⼀种我们之前⽐较熟悉的类来完成该⼯作,就是 Scanner 类。
构造方法 | 说明 |
Scanner(InputStream is, String charset) | 使⽤ charset 字符集进⾏ is 的扫描读取 |
// 需要先在项⽬⽬录下准备好⼀个 hello.txt 的⽂件,⾥⾯填充 "你好中国" 的内容
public class Main {public static void main(String[] args) throws IOException {try (InputStream is = new FileInputStream("hello.txt")) {try (Scanner scanner = new Scanner(is, "UTF-8")) {while (scanner.hasNext()) {String s = scanner.next();System.out.print(s);}}}}
}
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
public class demo9 {public static void main(String[] args) throws IOException {try(OutputStream outputStream=new FileOutputStream("./test.txt",true)) {// outputStream.write(98);byte[] bytes=new byte[]{98,98,97};outputStream.write(bytes);}}
}
注意:!!!
这里的OutPutStream它写入的时候,会覆盖文件已有的内容,如果要接着文件内容写,要在FileOutputStream的参数那里,加一个true。
2)字符流(文本文件)
为什么既然都能用,为什么还需要字符流呢?
因为用字节流时,需要我们程序员去记住那些字节对应的东西,如果是中文的话,就需要3个字节,对程序员来说是一件比较麻烦的事情,所以我们直接读字符,也就是字符流。
同理,一个是Reader,一个是Writer,也都是抽象类不能直接实例,而是通过FileReader和FileWrite来实例。
Reader方法如下:
public class demo10 {public static void main(String[] args) {try (Reader reader = new FileReader("./test.txt")) {
// int n=reader.read();
// char ch=(char)n;
// System.out.println(ch);while (true) {char[] chs = new char[1024];int n=reader.read(chs);if(n==-1){break;}for(int i=0;i<n;i++){System.out.print(chs[i]);}}} catch (IOException e) {e.printStackTrace();}}
}
Writer方法如下:
public class demo11 {public static void main(String[] args) {try(Writer write=new FileWriter("./test.txt",true)) {write.write("你好世界");} catch (IOException e) {e.printStackTrace();}}
}
五、实现文件查找,删除,复制
根据文件内容查找:
首先先输入它所在的目录,然后看看这个目录有没有错。
没错的话,就输入文件内容的关键字,然后scan搜索递归, 不是目录就返回,把它放到一个File[]里面,空的也返回。
然后reader一下把文件的内容都读到字符串里面,看看有没有包含关键字key。没有的话就下一个,是目录就继续递归。
public class demo14 {public static void main(String[] args) throws IOException {Scanner scanne = new Scanner(System.in);System.out.println("请输入你要搜索的路径");String rootPath = scanne.next();File file = new File(rootPath);if (!file.isDirectory()) {System.out.println("路径有误");return;}System.out.println("请输入关键字");String key = scanne.next();scan(file, key);}private static void scan(File file, String key) throws IOException {if (!file.isDirectory()) {return;}File[] files = file.listFiles();if (files == null || files.length == 0) {return;}for (File f : files) {if (f.isFile()) {doSreach(f, key);} else {scan(f, key);}}}private static void doSreach(File file, String key) throws IOException {StringBuilder stringBuilder = new StringBuilder();try (Reader reader = new FileReader(file)) {while (true) {char[] buffer = new char[1024];int n = reader.read(buffer);if (n == -1) {break;}stringBuilder.append(buffer,0,n);}} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {e.printStackTrace();}if(stringBuilder.indexOf(key)==-1){return;}else{System.out.println("找到了"+file.getCanonicalPath());}}
}
删除:
首先也是输入文件所在的目录, 然后看看这个目录有没有错。
没错的话,就输入文件名字的关键字,然后scan搜索递归, 不是目录就返回,把它放到一个File[]里面,空的也返回。
然后按File[]的内容,for循环找有这个关键字名字的文件,提示用户要不要删除。
public class demo12 {public static void main(String[] args) throws IOException {System.out.println("请输入你要搜索的路径");Scanner scanner=new Scanner(System.in);String rootPath=scanner.next();File file=new File(rootPath);if(!file.isDirectory()){System.out.println("路径不存在");return;}System.out.println("请输入你要删除文件的关键字");String key=scanner.next();scan(file,key);}private static void scan(File currentDir, String key) throws IOException {if(!currentDir.isDirectory()){return;}File[] files=currentDir.listFiles();if(files==null||files.length==0){return;}for(int i=0;i<files.length;i++){if(files[i].isFile()){doDelete(files[i],key);}else {scan(files[i],key);}}}private static void doDelete(File file, String key) throws IOException {if(!file.getName().contains(key)){return;}Scanner scanner=new Scanner(System.in);System.out.println(file.getCanonicalPath()+"是否要删除该文件,Y/n");String s=scanner.next();if(s.equals("Y")||s.equals("y")){file.delete();}}
}
复制:
首先也是输入文件所在的路径, 然后看看这个文件有没有错。
没错的话,就输入你要复制到目标地的路径,如果这时候目标路径的父节点不是目录就不行,因为这个复制是要跟目的地同一个等级。
然后源文件和目的地都没错之后,就可以从源文件中读,然后在目的地中写。
public class demo13 {public static void main(String[] args) {System.out.println("请输入源文件路径");Scanner scanner = new Scanner(System.in);String srcPath = scanner.next();File srcFile = new File(srcPath);if (!srcFile.isFile()) {System.out.println("无此文件");return;}System.out.println("请输入目标文件路径");String descPath = scanner.next();File descFile = new File(descPath);if (!descFile.getParentFile().isDirectory()) {System.out.println("路径有误");return;}try (InputStream inputStream = new FileInputStream(srcFile);OutputStream outputStream = new FileOutputStream(descFile)) {while (true) {byte[] buffer = new byte[1024];int n = inputStream.read(buffer);if (n == -1) {break;}outputStream.write(buffer,0,n);}} catch (FileNotFoundException ex) {ex.printStackTrace();} catch (IOException ex) {ex.printStackTrace();}}
}
IO拒绝访问
(在输入目的路径的时候可能会“拒绝访问”报错)
这是因为你没有指定你复制到目的地的名字,只给了路径,还需要给它安一个名字。
如上图,就不会报错了。