文件
- 狭义的文件 -- 保存在硬盘上的文件.
- 广义的文件 -- 操作系统进行资源管理的一种机制,很多的软件/硬件资源抽象成"文件"来进行表示
树型结构组织和目录
按照层级结构进行组织专门用来存放管理信息的特殊文件---目录,也就是我们常说的文件夹。
硬件部分
|--------|---------------|------|----------------------------------|---------------------|
| | 存储空间 | 访问速度 | 成本 | 持久化 |
| 硬盘 | 很大(几个TB) | 很慢 | 便宜,1TB固态硬盘,300-400左右,机械硬盘更便宜 | 能够持久化存储,断电之后数据任然在.. |
| 内存 | 更小(16GB 32GB) | 快 | 贵,16G内存DDR5比较新的内存价格400-500左右 | 不能持久化 |
| cpu存储器 | 非常小(不到1KB) | 非常快 | cpu 寄存器不单卖-- 算整个 cpu 的价格,就更贵到天际了 | 不能持久化 |
机器硬盘
速度慢受限于内部的结构

磁头,读写数据
磁盘,存储数据(处于"真空"环境下),高速旋转7200/min
路径
路径,定位到文件一系列的过程
计算机中的目录套目录构成了树形结构

这不同于二叉树的结构,而是N叉树的形式
文件路径

从树根开始到最终的文件中间都需要经过哪些目录,把这些目录记录下来就够成 "路径",一般使用
"/" 来分割 路径中的多级目录
/和\
/时斜杠,\是反斜杠
在 主流操作系统中,都是使用"/"来分割的,但是 Windows 是例外,Windows / 和 \ 都支持.
(但是 Windows 默认使用"\")
我们写代码时,涉及到路径建议写成"/",对于"\"在字符串中就需要转义的

以上路径都是从盘符(根节点出发的)开始,逐级表示出来的,称为绝对路径
相对路径
相对路径是根据基准路径出发



谈到相对路径要明确基准路径,如果代码中写一个"相对路径",那么基准路径是谁?

工作路径是谁并不确定,取决于程序的运行方式
1.在IDEA中直接运行,基准路径就是项目路径

C:\code\java,甚至可以通过代码中的一些api修改基准目录
2.打一个jar包,单独运行jar包
当前在哪个目录下执行运行命令(java-jar jar包名)
基准目录就是哪个目录
文件的种类
从开发的角度把文件分成两类(大前提,所有的文件都是二进制的,有一些文件是特殊的,二进制
数据刚好能构成字符)
1.文本文件
2.二进制文件
总结
- 文件存储在硬盘上的
- 目录也是文件. 操作系统通过树形结构组织目录和文件的
- 通过路径识别定位到具体的文件 (相对路径 绝对路径)
- 文件分为文本和二进制.
Java 中操作文件
Java 标准库提供了一系列的类操作文件
- 文件系统操作 创建文件, 删除文件, 重命名, 创建目录.....
- 文件内容操作 针对一个文件的内容进行读和写了.
File用于操作文件系统
|-----------------------------------|-----------------------------------------|
| 签名 | 说明 |
| File(File parent, String child) | 根据父目录 + 孩子文件路径,创建一个新的 File 实例 |
| File(String pathname) | 根据文件路径创建一个新的 File 实例,路径可以是绝 对路径或者相对路径 |
| File(String parent, String child) | 根据父目录 + 孩子文件路径,创建一个新的 File 实 例,父目录用路径表示 |
这里的参数String文件的路径使用绝对路径/相对路径均可
|-----------|---------------------|--------------------------------------------|
| 修饰符及返回值类型 | 方法签名 | 说明 |
| String | getParent() | 返回 File 对象的父目录文件路径 |
| String | getName() | 返回 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 运行结束时 才会进行 |
| boolean | mkdir() | 创建 File 对象代表的目录 |
| boolean | mkdirs() | 创建 File 对象代表的目录,如果必 要,会创建中间目录 |
| boolean | renameTo(File dest) | 进行文件改名,也可以视为我们平 时的剪切、粘贴操作 |
| boolean | canRead() | 判断用户是否对文件有可读权限 |
| boolean | canWrite() | 判断用户是否对文件有可写权限 |
java
public static void main(String[] args) throws IOException {
File file=new File("./text.txt");
System.out.println(file.getName());
System.out.println(file.getParent());
System.out.println(file.getPath());
System.out.println(file.getAbsolutePath());
System.out.println(file.getCanonicalPath());
}

当此处的new file()中转入的构造方法是绝对路径时,此处getPath就是得到绝对路径

项目目录是:C:\code\java\file
getCanonicalPath()
getCanonicalPath(),绝对路径简化的版本,把路径中间的.或者..给干掉
java
public static void main(String[] args) throws IOException {
File file=new File("./text.txt");
file.createNewFile();
System.out.println(file.exists());
System.out.println(file.isDirectory());
System.out.println(file.isFile());
}


当指定路径不存在对应的文件时,createNewFile()就会创建一个新的文件,IDEA右边也会出现
test.txt的文件
对于createNewFile()这样创建文件的操作也不一定会成功,出现以下几种情况会出现异常
- 硬盘满了
- 没有权限
- 硬盘坏了
java
public static void main(String[] args) {
File file=new File("./text.txt");
boolean result=file.delete();
System.out.println(result);
// file.deleteOnExit();
}
delete与deleteOnExit
delete在进程进行时直接删除文件,而deleteOnExit是在进程退出时删除文件
java
public static void main(String[] args) {
File file=new File("c:/");
String[] str= file.list();
System.out.println(Arrays.toString(str));
System.out.println();
File[] files=file.listFiles();
System.out.println(Arrays.toString(files));
}

list和listFile
list只是列出当前目录里面的子元素,无法列出子目录中的内容(孙子元素)

而对于listFiles得到的是File对象包含更多的操作,list得到的是文件名(String)
mkdir和mkdirs




mkdir无法创建多级目录,只能创建一级


mkdir创建目录
mkdirs创建多级目录

renameTo是重命名,从操作系统角度来看,重命名和移动的操作本质是一样的,这个移动速度通
常非常快
复制文件的复杂度是O(N),文件/目录里所有的数据,遍历,再写入写的文件/目录,重命名还能起
到 "移动" 的作用

对于canRead和canWrite分别表示文件的权限属性和用户的权限级别
文件内容操作
读写文件
Java中针对文件内容的操作,主要是通过一组"流对象"来实现的
数据流
流的对象有很多种,针对这几十种流,可以分成两个大类
1.字节流
字节流读写文件以字节为单位,是针对二进制文件使用的
InputStream---输入,从文件读数据
java
public static void main(String[] args) throws IOException {
InputStream inputStream=null;
try {
inputStream=new FileInputStream("./test.txt");
}finally {
inputStream.close();
}
}
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() | 关闭字节流 |
FileInputStream
InputStream只是一个抽象类,要使用还需要具体的实现类。用FileInputStream实现InputStream

括号里填写的参数可以填写文件的路径(绝对/相对),也可以填写File对象
InputStream流对象体系,不仅仅给文件提供操作

这里创建对象的操作一旦成功,就相当于"打开文件"
先打开然后才能读写,这是操作系统定义的流程

每次程序打开一个文件就会在文件描述符表中(固定长度的)申请一个表项,如果光打开不关闭就会
使这里的文件描述表表项耗尽,后续再打开就会出现打开失败的情况

对于这种try代码块能有效防止忘记释放文件资源,当只要出了try代码块就会自动调用close
对于这种自动释放文件资源的情况,要求这个类需要实现Closable接口

此时read是一次读一个字节
当直接去运行这段代码时,会出现文件找不到的操作

这是因为找不到对应文件的地址,此时我们需要在file目录下创建test.txt文件

此时才能运行成功

此时是按照字节读取,一个一个字节取出来分别打印,每个字节范围0-255

10进制是正常,8进制是0开头,十六进制是0x开头
当直接输出data时,是以utf8的形式来输出


当以方式二来进行字节读取时,对于byte[] data = new byte[3];
一次读多个字节, 数组的长度, 自行定义,也是读操作, 就会尽可能把字节数组给填满.填不满的话,
能填几个就是几个,此处的 n 就表示实际读了几个字节.

当将数组大小改为3时,会分批进行读取

对于int n=inputStream.read(data);

这是输出型参数,read返回时会同时返回长度和内容数组
OutputStream---输出,往文件写数据
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() | 冲涮缓冲区 |




97在ASCII中就是"a"
对于OutputStream来说,默认情况下会尝试创建不存在的文件,同时也会清除上次的文件内容,
当打开文件的一瞬间,上次的文件就清空了
此处可以追加写的模式避免内容被清空



2.字符流
字符流读写文件是以字符为单位,是针对文本文件使用
Read
Reader---输入,从文件读数据
第一个read一次读一个字符
第二个read时都字符数组,尽可能将字符数组填满,返回实际读到的字符个数
第三个read中的CharBuffer相当于对char[]进行了封装
第四个read是进行了偏移




Writer
Writer---输出,往文件写数据


总结:
如何正确使用流对象?
1.流对象的使用流程
先打开,再读写,最后关闭
2.应该是用哪个流对象
先区分文件还是文本,二进制再区分读还是写
缓冲区
缓冲区通常就是一段内存空间,用来提高程序的效率,直接读写硬盘比较低效的 (硬盘速度慢)因此
有的时候就希望减少读写文件次数。
在进行 IO 操作的时候就希望能够使用缓冲区,把要写的数据先放到缓冲区里攒一波再一起写或者
读的时候, 也不是一个一个的读, 一次读一批数据, 到缓冲区中, 再慢慢解析
当前 IO 流对象, read 和 write 就属于直接读写文件,要想提高效率有以下两种办法:
- 写代码的时候, 手动创建缓冲区 (byte[]), 手动减少 read write 次数.
- 使用标准库提供了 "缓冲区流" BufferedStream 把 InputStream 之类的对象套上一层.

这种就是构建了一个writer的缓冲区
文件IO的练习
1.扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是
否要删除文件
java
public static void main(String[] args) throws IOException {
Scanner scanner=new Scanner(System.in);
System.out.println("请输入要搜索的目录");
String rootDir=scanner.next();
File files=new File(rootDir);
if (!files.isDirectory()){
System.out.println("输入并不是目录");
return;
}
System.out.println("请输入要删除含有指定字符");
String keyword=scanner.next();
scanfile(files,keyword);
}
private static void scanfile(File rootfile, String keyword) throws IOException {
File[] files=rootfile.listFiles();
if (files==null){
return;
}
for (File file:files) {
if (file.isFile()){
dealfile(file,keyword);
}else {
scanfile(file,keyword);
}
}
}
private static void dealfile(File file, String keyword) throws IOException{
if (file.getName().contains(keyword)){
System.out.println("找到指定字符串在目录或者文件:"+file.getAbsolutePath()+"是否要删除文件该文件");
Scanner scan=new Scanner(System.in);
String str=scan.next();
if (str.equalsIgnoreCase("y")){
file.delete();
System.out.println("文件已删除");
}
}
}
2.进行普通文件复制(复制就是把文件里的每个字节都读出来写入到另一个文件中)
java
public static void main(String[] args) throws IOException {
Scanner scan=new Scanner(System.in);
System.out.println("请输入源文件路径:");
String srcPath=scan.next();
System.out.println("请输入目标文件路径:");
String destPath=scan.next();
File srcfile=new File(srcPath);
if (!srcfile.isFile()){
System.out.println("源文件不存在或者目录不存在");
return;
}
File dextfile=new File(destPath);
if (!dextfile.getParentFile().isDirectory()){
System.out.println("指定路径不存在");
return;
}
try (InputStream inputStream=new FileInputStream(srcPath);
OutputStream outputStream=new FileOutputStream(destPath)){
while (true){
byte[] buf=new byte[1024];
int n=inputStream.read(buf);
if (n==-1){
break;
}
outputStream.write(buf, 0,n);
}
}
}
3.扫描指定目录,并且找到名称后者内容包含指定字符的所有文件(不包含目录)
java
public static void main(String[] args) throws IOException{
Scanner scan=new Scanner(System.in);
System.out.println("请输入要搜索的路径:");
String rootPath=scan.next();
File rootFile=new File(rootPath);
if (!rootFile.isDirectory()){
System.out.println("输入的路径不是目录");
return;
}
System.out.println("请输入要搜索的关键字");
String keyword=scan.next();
scanDir(rootFile,keyword);
}
private static void scanDir(File rootFile, String keyword) throws IOException {
File[] files=rootFile.listFiles();
if (files==null){
return;
}
for (File file:files) {
if (file.isFile()){
dealFile(file,keyword);
}else {
scanDir(file,keyword);
}
}
}
private static void dealFile(File file, String keyword) throws IOException {
if (file.getName().contains(keyword)){
System.out.println("查找到关键字在指定路径"+file.getAbsolutePath());
return;
}
StringBuffer stringBuffer=new StringBuffer();
try (Reader read=new FileReader(file)){
while(true){
char[] buf=new char[1024];
int n=read.read(buf);
if (n == -1) {
break;
}
stringBuffer.append(buf,0,n);
}
if (stringBuffer.indexOf(keyword)>=0){
System.out.println("查找到关键字在指定文件"+file.getAbsolutePath());
}
return;
}
}