目录
编辑一、认识文件
1、文件路径
2、其它知识
二、Java 中操作文件
三、文件内容的读写
1、Reader
2、InputStream
3、输出
一、认识文件
文件是在硬盘上存储数据的一种方式,操作系统帮我们把硬盘的一些细节都封装起来了
我们只需要了解文件相关的一些接口即可
硬盘是用来存储数据,和内存相比,硬盘的存储空间更大,访问速度更慢,成本更低,可以持久化存储
操作系统通过 “文件系统” 这样的模块来管理硬盘
实际上我的电脑只有一个硬盘,操作系统可以通过文件系统把这个硬盘抽象成多个硬盘一样 NTFS 是windows 上的文件系统,背后有一定的格式来组织硬盘数据
EXT4 是 Linux 上常见的文件系统
1、文件路径
不同的文件系统,管理文件的方式都是类似的
通过 目录 - 文件 构成了 “ N 叉树” 树形结构
通过 D盘 - tmp - cat.jpg 这个路线,就能 找到 / 确定 电脑上的唯一一个文件,这个东西就称为 “路径”
在 windows 上,使用 / 或者 \ 来分割不同的目录
以盘符开头的路径,也叫做 “绝对路径”
绝对路径相当于与是从 “此电脑” 这里出发,找文件的过程
以 . 或者 ... 开头的目镜,叫做 “绝对路径”
相对路径,需要有一个 “基准目录” / “工作目录”,表示从这个基准出发,怎么走能找到这个文件夹
如果以 D:为基准目录 ./tmp/cat.jpg 如果以 D:tmp 为基准 ./cat.jpg (. 表示当前所在目录)
如果以 D:/tmp/111 为基准, ..cat.jpg(..表示当前目录的上一级目录)
同样是一个 cat.jpg 文件,站在不同的基准目录上找到的路径是不一样的
2、其它知识
文件系统上存储的文件,又可以分成两个大类
1、文本文件
存储的是字符
例如:utf-8 就是一个大表,这个表上的数据的组合,就可以称为字符
2、二进制文件
存储的是二进制的数据
如何判断文件是文本文件还是二进制文件?
一个最简单的方式:直接使用记事本打开
记事本打开文件,就是尝试把当前的数据,在码表中查询
如果打开之后能看懂,就是文本文件,如果打开之后看不懂,就是二进制文件
二、Java 中操作文件
后续针对文件的操作,文本和二进制的操作方式是不同的
文件系统操作:创建文件,删除文件,创建目录....
java 中,不要通过 java.io.file 类来对一个文件(包括目录)进行具体的描述
IO : input 和 output,我们是站在 cpu 的视角来看待输入输出的
通过 File 对象来描述到一个具体的文件
File 对象可以对应到一个真实存在的文件,也可以对应到一个不存在的文件
构造方法:
第二个构造方法此处的字符串,就表示一个路径,可以是绝对路径,也可以是相对路径
方法:
站在操作系统的角度来看待:目录也是文件
操作系统的文件是一个更广义的概念,具体来说有很多不同的类型
1、普通文件(通常见到的文件)
2、目录文件(通常见到的文件夹)
windows 上,目录之间的分隔符,可以使用 / 也可以使用 \
Linux 和 mac 上面,就只支持 /
所以即使在 windows 上,也尽量使用 / ,使用 、 在代码中需要搭配转义字符
可以通过代码,来感受一下对文件的操作: 当我们把绝对路径改成相对路径,代码运行结果又会有所不同:
getAbsolutePath 会将工作目录拼接上当前目录,就是运行的结果
在 idea 中运行一个程序,工作目录就是项目所在的目录,在命令行中 运行一个程序,工作目录就是命令行当前所在的目录,如果程序是运行在 tomacat 中,工作目录就是 tomcat 下的 bin 目录
这个操作是可能会抛出异常的
比如,当前写入的路径是一个非法的路径
比如,当前创建的这个文件,对于所在的目录没有权限操作
有的时候,可能会用到这样的一个功能:临时文件
程序运行的时候,搞一个临时文件,程序结束了,临时文件中自动删掉
像 office 等很多这样的生产力软件 都有产生临时文件功能,这个临时文件就自动保存了你当前的编辑的中间状态
有的人使用 word 不喜欢保存,用了一段时间之后,电脑突然断电关机了,没保存的数据难道就没了吗?
重启电脑,由于刚才是非正常关闭,临时文件是来不及删除,仍然存在的moffice 启动就能知道上次是异常关闭了,就会提示你是否要从之前的临时文件恢复未保存的数据
创建目录代码:
import java.io.File;
import java.io.FileReader;public class Demo4 {public static void main(String[] args) {File file = new File("./test-dir");//mk -> make dir->directory//mkdir 一次只能创建一层目录,mkdirs 可以一次创建多层目录file.mkdir();//file.mkdirs();}
}
文件重命名也可以起到文件移动的效果
import java.io.File;//文件重命名
public class Demo5 {public static void main(String[] args) {File file = new File("./test.txt");File file2 = new File("./src/test2.txt");file.renameTo(file2);}
}
以上文件系统的操作,都是基于 File 类来完成的
另外还需要文件内容的操作
三、文件内容的读写
1、Reader
文件这里的内容本质是来自于硬盘,硬盘又是操作系统管理的,使用某个编程语言操作文件,本质上都是需要调用系统的 api
虽然不同的编程语言操作文件的 api 有所差别,但是基本步骤都是一样的
文件内容操作的核心步骤有四个:
1、打开文件
2、关闭文件
3、读文件
4、写文件
Java IO流 是一个比较庞大的体系,涉及到非常多的类,这些不同的类,都有各自不同的特性,但是总的来说,使用的方法都是类似的
(1)构造方法,打开文件
(2)close 方法,关闭文件
(3)如果衍生自 InputStream 或者 Read ,就可以使用 read 方法来读数据
(4)如果衍生自 OutputStream 或者 Writer ,就可以使用 write 方法来写数据
读文件操作:
这个操作非常重要,释放必要的资源
让一个进程打开一个文件,是要从系统这里申请一定的资源的(占用进程的 pcb 的文件描述符表中的一个表项)
如果不释放,就会出现 “文件资源泄露” ,是一个很严重的问题
文件描述符表 可以理解成一个顺序表,长度有限,不会自动扩容,一旦一直打开文件,而不去关闭不用的文件,文件描述符表就会被占满,后续就没法打开新的文件了
我们可以使用 try with rewsourses 来避免出现上述问题
此时只要 try 代码执行完毕了,就会自动调用到 close 方法
文件流中的任意对象,都可以按照上述讨论来进行 close
一次读一个 char 类型的数组
会把读到的内容,填充到参数的这个 cbuf 数组中,此处的参数,相当于是一个输出形参数
通过 read ,就会把本来是空的一个数组,填充上内容
n 表示实际读到的字符的个数
相当于给了 1024 这么大空间,如果文件足够大,超过 1024,就能填满这个空间
如果文件比较小,不足1024,就会把文件所有内容都填到数组中(剩下会空余)
返回值 n 就表示实际读到的字符的个数
有时候,可能会涉及到有多个小文件,都需要读取并且拼接到一起,就可以使用这个方法
假设现在有三个文件,每个文件的大小是 100 字节
最终代码如下:
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;//Reader 的使用
public class Demo6 {public static void main(String[] args) throws IOException {
/* //FileReder 的构造方法,可以填写一个路径(绝对路径和相对路径都可以),也可以写一个构造好了的 file 对象Reader reader = new FileReader("d:/test.txt");try {//中间的代码无论出现什么情况,close 都可以执行到}finally {//如果抛出异常或者 return ,close就都执行不到了reader.close();}*///上述使用 finally 的方式能解决问题,但是不优雅//使用 try with resourses 是更好的解决方法try(Reader reader = new FileReader("d:/test.txt")){while(true){char buf[] = new char[1024];int n = reader.read(buf);if(n == -1){//读到文件末尾了break;}for(int i = 0;i < n;i++){System.out.print(buf[i] + " ");}}}}
}
2、InputStream
InputStream 是字节流,用法和 Reader 相似
文本文件,也可以使用字节流打开,只不过此时你读到的每个字节,就不是完整的字符了
一次读一个字节
一次读若干个字节,尝试填满这个 byte[]
一次读若干个字节,填满数组的一部分
Java 虽然有 char ,但是很少会用,更多的是使用 String
此处,我们可以借助一些额外的工具类,就可以完成 字节 / 字符 --> 字符串 的转化
虽然也可以直接使用 String 的构造方法完成 char[] 或者 byte[] 到字符串的转化,但是比较麻烦
这个工具类就是 Scanner !!!
操作系统中,所谓的文件,是一个广义的概念,System.in 是一个特殊的文件,对应到 “标准输入”,普通的硬盘上的文件,也是文件
后来网络编程中的网卡(socket),也是文件
Scanner 都是一视同仁的,只是把当前读到的字节数据进行转换,但并不关心这个数据是来自于哪里
但是,要注意,Scanner 只是用来读取文本文件的,不是适合读取二进制文件
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;public class Demo8 {public static void main(String[] args) throws IOException {try(InputStream inputStream = new FileInputStream("d:/test.txt")){Scanner scanner = new Scanner(inputStream);//此时就是从 test.txt 这个文件中读取数据了String s = scanner.next();System.out.println(s);}}
}
3、输出
输出的使用方法和输入非常相似,关键操作是 write
write 之前要打开文件,write 之后也需要关闭文件
输出流对象(无论是字节流还是字符流),会在打开文件之后,清空文件内容!!!
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;public class Demo8 {public static void main(String[] args) throws IOException {try(InputStream inputStream = new FileInputStream("d:/test.txt")){Scanner scanner = new Scanner(inputStream);//此时就是从 test.txt 这个文件中读取数据了String s = scanner.next();System.out.println(s);}}
}
还可以按照追加写的方式打开,此时就不会清空内容了
读操作和写操作,也都能支持随机访问,可以移动光标到指定位置进行读写,此处就不进行介绍了
OutputStream 方式使用方法完全一样
只不过 write 方法,不能支持 字符串 参数,只能按照 字节 或者 字节数组来写入
实例:扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要删除此文件
import java.io.File;
import java.util.Scanner;public class Demo10 {public static void main(String[] args) {Scanner scanner = new Scanner(System.in);//1、用户输入一个目录,后续的查找都是针对这个目录进行的System.out.println("请输入要搜索的目录");File rootPath = new File(scanner.next());//2、再让用户输入要搜索和删除的关键粗System.out.println("请输入要删除的关键字");String word = scanner.next();//3、判断当前目录是否有效if (!rootPath.isDirectory()){System.out.println("此时输入的路径不是合法目录");return;}//4、遍历目录,从根目录出发,按照深度优先(递归) 的方式进行scanDir(rootPath,word);}public static void scanDir(File currentDir,String word){//1、先列出当前目录中包含哪些内容File[] files = currentDir.listFiles();if (files == null || files.length == 0){//空的目录或者非法的目录return;}//2、遍历列出的文件,分两个情况进行讨论for(File f : files){if (f.isFile()){//如果当前文件是普通文件,看看文件名是否包含了 word ,来决定是否要删除dealFile(f,word);}else {//如果当前文件是目录文件,就递归执行 scanDirscanDir(f,word);}}}public static void dealFile(File f,String word){Scanner scanner = new Scanner(System.in);//1、先判断当前文件是否包含 wordif (!f.getName().contains(word)){//此时文件不包含 word,return;}//2、包含 word,询问用户是否删除该文件System.out.println("该文件是: " + f.getAbsolutePath() + ",是否确认删除(Y / N )");String choice = scanner.next();if (choice.equals("Y") || choice.equals("y")){f.delete();}}
}
示例:进行普通的文件复制
import java.io.*;
import java.util.Scanner;public class Demo11 {public static void main(String[] args) throws IOException {System.out.println("请输入要复制的文件路径");Scanner scanner = new Scanner(System.in);String src = scanner.next();File srcfile= new File(src);if (!srcfile.isFile()){System.out.println("您输入的源文件路径是非法的");return;}System.out.println("请输入要复制到的目标路径");String dest = scanner.next();File destfile= new File(dest);//不要求目标文件存在,但是得保证目标文件所在的目录是存在的if (!destfile.getParentFile().isDirectory()){System.out.println("您输入的目标文件路径是非法的");return;}//2、进行复制操作的过程,按照字节流打开try(InputStream inputStream = new FileInputStream(srcfile);OutputStream outputStream = new FileOutputStream(destfile)){while(true){byte[] buffer = new byte[1024];int n = inputStream.read(buffer);if (n == -1){System.out.println("读取到 eof,循环结束");break;}outputStream.write(buffer,0,n);}}}
}