目录
前言
计算机上的数据是存储在硬盘上的,当我们想要操作数据的时候,却不是通过直接操作硬盘来进行操作这些数据的,而是通过文件来操作。这是由于操作系统将硬盘上的数据抽象成了文件来供程序进行使用,也即是加多了一层封装。因而我们只需了解文件相关的操作即可。
一、文件系统、绝对路径和相对路径
操作系统是通过文件系统来管理硬盘资源的,不同的操作系统上的文件系统也不同。
如:Windows 的文件系统是 NTFS,Linux 系统上常见的文件系统是 EXT4 。
不同的文件系统,其管理文件的方式都是类似的,都是通过" 目录(文件夹)—文件 "构成了一颗 N 叉树的树形结构。
因而每个文件的位置就使用从根目录到其存放处的路径来进行描述。而路径又分为绝对路径和相对路径:
绝对路径:以盘符为开头的路径,相当于是从" 此电脑 "出发直至文件存放的位置。如:D:/tmp/test.jpg;
相对路径:以 . 或者 .. 开头的路径。其中 . 就是一个基准目录 / 工作目录,相当于是从该目录出发直至文件存放的位置。而 .. 则表示该基准目录的上一层目录。
以 D:/tmp/test.jpg 为例:
如果以 D: 为基准目录,则为 . /tmp/test.jpg;
如果以 D:/tmp/111 为基准目录,则为 .. /test.jpg 。
二、文件分类
文件系统中存储的文件,大体分为两个大类:文本文件和二进制文件。
文本文件中存储的是字符,二进制文件中存储的便是二进制数据。
我们熟知的 uft8 大表,其表上的数据的组合就可以称之为字符。因而如果所有字符都可以由该表上的数据组合表示出来,就是文本文件;如果不能或者表示出来之后让人看不懂( 也就是乱码 ),即是二进制文件。
一个最简单来判别这两种文件的方式就是用记事本来打开文件,如果看不懂则为二进制文件,能看懂就是文本文件。
三、文件系统操作类:File
Java 标准库中提供了关于文件系统的操作类 java.io.File ,使用该类对象就可以描述一个具体的对象,可以是一个真实存在的文件也可以是一个未创建的文件。
1、构造方法:一般我们传入一个文件路径来创建出一个文件对象,该路径可以是一个绝对路径也可以是一个相对路径。
2、普通方法的使用
官方文档中关于 File 类的相关操作的方法有着很多,大多数操作起来也比较简单,此处就不一一介绍。
但是对于一些方法的细节需要说明一下:
1)
该方法获取到的路径是构造 File 对象时所提供的路径,构造方法中传入的路径为何,此处该方法就直接返回个一模一样的字符串;
2)
这两个方法,一个是判断是否是目录,另一个是判断是否是文件。
文件这一概念的范围很广泛,目录也可以是文件,普通文件也是文件,因此这两个方法就是对目录和普通文件进行判别。
如果文件不存在,会直接返回 false;
3)
这两个方法作用为列出当前目录下包含了哪些文件,就规定了当前 File 类对象必须为一个目录;
4)
该方法为创建出一个当前路径表示的文件,但即便这个文件已经存在了,也不会产生副作用,而是直接不创建了;
当文件路径为一个非法路径或者对该路径中的目录没有访问权限,则会直接抛出异常;
5)
这两个方法都是删除文件,但是删除的时机有所差异:第一个为调用该方法后就立即删除,而第二个为程序执行完毕之后再进行删除;
6)
该方法为文件重命名,但是也可以起到移动文件的效果:
public class Demo2 {
public static void main(String[] args) throws IOException {
File file1 = new File("./text.txt");
file1.createNewFile();
}
}
当运行上述代码之后,就会在工作目录下创建出一个 test.txt 文件:
public class Demo2 {
public static void main(String[] args) throws IOException {
File file1 = new File("./text.txt");
File file2 = new File("./src/text.txt");
file1.renameTo(file2);
}
}
而当运行上述这段代码之后,原本与 src 目录同级的 text.txt 文件就移动到了 src 目录之下:
四、文件内容操作
上述 File 类是针对文件系统的操作,而除此之外,我们还需要对文件内容进行操作,这就需要文件流来进行操作:
针对文件内容操作的核心步骤有四步:
1)打开文件 2)读文件 3)写文件 4)关闭文件
而 Java 中对于文件内容的操作是由一系列类进行展开的,这些类主要针对于需要操作的单位进行分类:
一类是针对于字节流的 InputStream 和 OutputSream;
另一类是针对字符流的 Reader 和 Writer 。
后续操作字节和字符的类都是由这两类衍生出来的。
接下来就先使用 Reader 来演示一下打开文件和关闭文件的操作:
1、打开文件
一般使用构造方法就可以打开文件
Reader reader = new FileReader("");
由于 Reader 是一个抽象类,因此需要 new 一个具体的类 FileReader 才能构造出对象。其他的类也是如此来创建对象。
2、关闭文件
打开文件使用完了之后,一定要注意关闭文件,因此进程打开一个文件,就需要从系统中申请一定的资源,占用进程的 pcb 中的文件描述符表中的一个表项,这个表类似于顺序表一样。由于这个表有固定长度,且无法扩容,因此如果打开文件之后都不关闭,这个表早晚都会有满了的时候,后续就无法打开其他文件,造成文件资源泄露问题。
reader.close();
为了稳妥起见,避免由于代码中间直接进行 return 或者抛出异常而导致无法执行到关闭文件的地方,使用 try-with-resource 来打开文件更好:
try (Reader reader = new FileReader("");) {
}
这样当 try 代码块执行完毕之后,程序就会自动调用 close 来关闭文件。
除此之外,也可以使用 finally 来解决。
3、读文件
Reader 和 InputStream 都可以进行读文件操作。
先讲一下 Reader 如何使用:
Reader 主要是针对于文本文件进行读取,调用 read( ) 来进行写文件操作,而 read( ) 有多种方式来进行调用:
第一种是一次性读取一个字符;
第二种是传入一个字符数组,读取该数组长度个字符到该数组中,返回值为读取的字符个数,因为有可能会出现文件中的字符个数小于数组长度,如果返回的是 -1 ,则为读到了文件末尾;
第三种则是在第二种的基础上,使用了 off 来定义开始输入文件内容的数组下标和 len 来定义输入内容的最大字节长度;
第四种的 target 实质仍然是一个字符数组,只不过使用了类来进行多一层封装而已。
此处虽然读的是字符,但是可以发现返回值却都是 int ,但是查阅之后可以发现,其返回的范围是 0—65535,刚好是两个字节的大小,Java 中的字符也刚好是两个字符的大小,因此返回的仍然是字符数据,只不过使用 int 型来表示而已。
而到了 InputStream ,其读取文件的方式跟 reader 是差不多的,只不过读取单位变成了字节而已。因此其可以读取二进制文件也可以读取文本文件,因为都可以逐个字节来进行读取。
从上图中就可以看到其方法的参数都是与 reader 类似的。
其中返回值 int 的范围就变成了 0—255,为一个字节的大小。
4、写文件
写文件主要是使用 Writer 和 OutputStream,这两个类的使用方式也很相似,仍旧只是单位的不同而已,因此使用 Writer 来进行展开即可:
写文件操作是通过调用 write( ) 方法来进行的:
从这些方法参数也可以知道,write 方法的参数和 read 方法参数很相似,只不过多了可以直接写入一个字符串的操作而已,其他参数与 read 方法都相同。
但是要注意的是,写文件操作在打开文件的时候,会清空该文件之前的数据,而如果只是想追加而不是清空,则需要在构造的时候多传入一个参数:
Writer writer = new FileWriter("", true);
第二个参数名 append ,意思便为是否追加,如果不传入该参数,则默认为 false,因此就会清空原先的文件内容。