IO、存储、硬盘、文件系统相关常识
计算机的硬件
处理器(CPU)、存储器(内存)、输入设备(Input Device)、输出设备(Output Device),后两者就被称为I/O
内存(Memory)又被称为RAM(Random Access Memory),支持O(1)时间复杂度,根据下标(内存地址)访问的存储
CPU只能和内存做直接的数据交换
Input Device:现实中的物理信号(光信号、电信号、波信号)转换为数字格式,存储在内存中
Output Device:将数字信号转化为物理信号
I/O设备包括:显示器(O)、触屏显示器(I/O)、鼠标(I)、键盘(I)、摄像头(I)、麦克风(I)、扬声器(O)
开发中接触得比较多得是:硬盘(I/O)、网卡(I/O)等
硬盘
既然要提到硬盘,那么存储得概念也必须要知道
从内存往上的称为易失存储,硬盘往下的称为持久存储;
易失存储在断电后会丢失数据,以进程为单位管理;变量、对象本质上的编程语言对硬件中内存(易失存储)的抽象
持久存储断电后一般仍能保存,通常可以跨进程读写
各个存储访问速度的一个直观感受:
硬盘(Hard Disk Drive - HDD)的实现:
- 磁盘 (利用磁性实现的一种存储方式),一般就指硬盘或者(机械硬盘)
- 固态硬盘 (Solid State Disk - SSD)
- 闪存 (Flash Memory)
磁盘查找文件的步骤:
- 旋转盘片找到对应的扇区
- 找到对应的磁道
从磁盘的读取数据的过程中有许多物理操作,因此效率较低
并且磁盘更适合做连续数据的读写,而不适合做随机数据的读写
文件
软件层面认识硬盘中的数据:
硬盘可以分为两部分:「有数据存储的」和「暂时没有数据存储的(可用空间)」
有数据存储部分可以细分为数据1、数据2、数据3
每个数据又被抽象为文件1、文件2、文件3,可以说文件就是对硬盘中数据的抽象概念
因此硬盘数据的读写就是对文件的读写问题
主要由OS + 文件系统(FileSystem) 统一管理文件
我们看到的文件的基本知识:
-
文件被以树形结构(数据结构学的树,但不是二叉树)进行管理,文件都是树上的一个结点
-
文件可以大体分成两大类:
- 存储数据的文件 - 普通文件(俗称的文件)
- 管理树性结构组织数据的文件 - 目录/文件夹(directiry 或 dir) - 以
/
结尾,就代表这个结点是目录,比如C:/
-
这颗文件树只是逻辑结构,而不是硬盘上的物理结构
无论是目录或者文件都只是树上的一个结点,一个结点可以有多个孩子
对于普通文件:
- 在Windows OS下,会以后缀名(file suffiex)来标记出这个文件存储的内容是什么内容,比如
*.txt
- 普通文本 - Windows下,我们可以将文件设置为可见或隐藏文件,建议把显示隐藏 + 显示扩展名都勾上
文件分为:目录、文本文件、不是文本的文件(二进制文件)
文本文件比如:*.txt
以及*.java
等
不是文本的文件比如:*.class
和*.png
等
路径
关于文件路径(Path):
根据一个规则,从文件树上确定一个位置,这个位置一定对应到某个结点,但是这个结点可以不存在(路径只对应一个位置,但是该位置可以不存在该文件)
在Windows下,像C:/这种就称为根结点或者更多称为根目录
路径有两种:
- 绝对路径(absolute path - abs path):从一棵树的根结点处罚描述的路径,无论“我们”身处何处,总是能够唯一确定一个文件
- 相对路径(relative path):从“我们”所在的当前位置出发,描述的路径,“我们”的位置一变,路径就可能失效了
“我们”所在的位置一定是一个目录,而不能处于一个文件上的,首先找到我的父结点(Windows),再去到父亲结点
什么叫“我们”所在的位置:每个进程都有一个当前工作目录(current working directory - cwd),一般一个进程的启动目录,就是当前工作文件
真实中如何去描述路径(重点):
Windows下,绝对路径例子:D:\课程\2022-06-27-火箭班-IO\板书.png
Mac下,绝对路径例子:
/Users/harley/Desktop/code/FileTest/a
C:或者D:或者E:代表的就是根,这个以根开头的路径一定是个绝对路径,\
或/
一般称为路径分隔符
“我们”身处D:\课程\
和/User/harley/Desktop/
中:
Windows下,相对路径例子:\课程\2022-06-27-火箭班-IO\板书.png
Mac下,相对路径例子:
/Desktop/code/FileTest/a
不是由根开始,所以是相对路径
在代码的字符串中如何表示这个路径
如果这么写:String path = "D:\课程\2022-06-27-火箭班-IO"
是错误的
字符串中的反斜杠\
表示转义的意思,因此应该写作:String path = "D:\\课程\\2022-06-27-火箭班-IO"
Windows使用\
作为路径分隔符,Linux(Unix/XOS)使用/
作为路径分隔符,不过Java是跨平台的,所以会自动帮我们处理这个问题,在代码中写\\
或者/
都可以,方便起见,可以使用/
因此可以这么写:String path = "D:/课程/2022-06-27-火箭班-IO"
路径表示中的两个特殊的符号:.
和..
其中.
表示在当前位置(目录)不动
其中..
表示回到当前位置的父结点(目录)上,如果在根目录下使用该符号是没效果的,根没有双亲
这两个符号一般使用在相对路径中,假设我们还在桌面文件下:/Desktop/./code/../code/FileTest/
关于文件的路径的总结:
- 路径是树上找到一个结点的位置
- 路径并不表示文件一定存在
- 路径分为绝对路径和相对路径
- 当前位置:进程的当前工作目录
- 文件路径
/
或者\\
表示路径分隔符,Java下使用哪个都可以
操作文件
普通文件只能是叶子结点,而目录文件既可以是叶子结点也可以是非叶子结点
在文件系统中,以结点为单元进行操作(在代码层面)
- 文件移动操作(文件重命名、文件剪切 + 粘贴):结点的移动(重命名 or 移动到其他结点下)
- 文件复制操作(复制 + 粘贴):新建结点 + 内容的复制
- 目录移动操作(目录重命名、目录剪切 + 粘贴):以该结点为根的一颗子树的移动
- 目录复制操作:以该结点为根的一颗子树的复制
- 删除:默认情况下只能删除普通文件或空目录,只能删除结点,不能删除子树
- 删除非空目录:对整棵树的删除
树的操作,需要转换成结点的操作:以目录的复制为例,遍历(深度 or 广度)整棵树,然后对其中的每个结点进行复制
通过代码操作文件
文件数据 = {元数据 + 内容数据}
与文件属性相关的数据是元数据或者管理数据,文件里的具体内容就是内容数据
接下来的学习中我们只关注元数据本身,暂时不管内容数据
比如对文件系统树的操作,判断是否存在,判断是否为目录,创建,删除,重命名等等
File类常用方法
在Java中通过File对象描述一个文件(结点,结点对应的文件是否存在不一定)
File类的构造方法:
public static void main(String[] args) {
// 1. 绝对路径的方式创建
// 1.1 直接传入字符串的路径即可
File file1 = new File("/Users/harley/Desktop/code/FileTest/a/a.txt");
// 1.2 传入父路径 + 子路径
File file2 = new File("/Users/harley/Desktop/code/FileTest/b", "b.txt");
// 1.3 以File传入 parent
File parent = new File("/Users/harley/Desktop/code/FileTest");
File file3 = new File(parent, "c.txt");
}
其他常用的方法:
-
int compareTo(File pathname)
:按字典序比较两个抽象路径名 -
public boolean createNewFile()
:当且仅当此的文件尚不存在时,才以原子方式创建一个以此抽象路径名命名的新的空文件返回true:创建成功
返回false:创建失败(文件已经存在)
抛出IOException:发生了IO异常(最常见的,是文件应该在的目录现在还不存在)
代码示例:
public static void main(String[] args) throws IOException {
File file4 = new File("/Users/harley/Desktop/code/FileTest/d.txt");
System.out.println(file4.createNewFile());// true
System.out.println(file4.createNewFile());// false
}
// 第一次创建文件的时候成功了,后面因为文件已经存在因此不会再创建文件了
执行后的文件目录:
如果在不存在的目录下创建文件,会发生什么情况:
还有两个文件的创建方法:
public static File createTempFile(String prefix, String suffix)
:在默认临时文件目录中创建一个空文件,使用给定的前缀和后缀生成其名称public static File createTempFile(String prefix, String suffix, File directory)
:在指定目录中创建新的空文件,使用给定的前缀和后缀字符串生成其名称
删除方法:
public boolean delete()
:立即删除文件,只有当文件目录是正确的情况下删除成功并返回true,其他情况比如非空目录是无法删除的,回返回falsepublic void deleteOnExit()
:在JVM退出时才真正删除
删除单个文件的代码示例:
public static void main(String[] args) throws IOException {
File file1 = new File("/Users/harley/Desktop/code/FileTest/d.txt");
// 在d目录下创建一个文本文件 d.txt,然后再删除
System.out.println(file1.createNewFile());// true
System.out.println(file1.delete());// true
}
如果想要删除非空目录,先采用深度优先遍历,删除这个目录下的所有子孙,让这个目录变成空目录,然后再删除该目录
删除非空目录的代码示例:
public static void main(String[] args) throws IOException {
File file = new File("/Users/harley/Desktop/code/FileTest/c/");
// 删除c目录下的1目录
traversal(file);
file.delete();
}
private static void traversal(File dir) throws IOException {
File[] files = dir.listFiles();// 查看这个目录下得所有孩子
for(File file : files) {
if(file.isDirectory()) {
// 如果这个孩子也是个目录,就继续深度优先遍历
System.out.println(file.getCanonicalPath() + "/");
traversal(file);
// 当深度优先遍历完成时,该目录为空,可删除
file.delete();
}else {
System.out.println(file.getCanonicalPath());// 得到这个文件的一个标准路径(去除一切无意义的.和..)
file.delete();
}
}
}
删除前,我们看看c目录下的所有文件
由于是深度优先遍历所以一旦碰到目录,就先将碰到的目录遍历完再遍历自己目录中剩下的文件
去掉file.delete()
的注释,执行代码,删除c目录下所有文件