目录
一、什么是文件
文件 就是在计算机的硬盘中躺着的,这些文件都是一个个单独的个体。
⽂件除了有数据内容之外,还有⼀部分信息,例如⽂件名、⽂件类型、⽂件⼤⼩等并不作为⽂件的数 据⽽存在,我们把这部分信息可以视为⽂件的元信息。
我们文件的存储 是依靠树型结构来存储的
目录:相当于文件夹,可能很多个文件放一起的
文件:一个单独的个体
文件的分类
即使是普通文件,存储的数据也不尽相同。
所以我们依靠数据的类型,把他们又分为 文本文件 和 二进制文件
文本文件:里面的内容在码表上“有据可查”,能查到对应的
二进制文件:里面的内容在码表上查不到,就是一堆乱码
啥是码表?
因为计算机只看得懂二进制,所以要把字解析给计算机看的,为了统一,就是根据码表来解析的。
码表也有很多种,最常见的是ASCII码,解析英文字母。但是我们的中文啊,还有其他的特殊字符,就用不了,就需要UTF8或者Unicode字符集等等。
值得注意的是utf8中,一个汉字3个字节。Unicode一个汉字2个字节。
如何分辨是 文本文件 还是 二进制文件呢?
最简单的办法,就是把文件拖到记事本上:看下图,像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 {
//使用finally
public 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拒绝访问
(在输入目的路径的时候可能会“拒绝访问”报错)
这是因为你没有指定你复制到目的地的名字,只给了路径,还需要给它安一个名字。
如上图,就不会报错了。