目录
前言
在前十章中,介绍了多线程的一些操作,从第十一章开始,会进行第二个部分的讲解:文件IO操作,这也是JAVAEE中一个非常重要的板块,但难度相比较于多线程会简单许多。
一、什么是文件操作
硬盘(外存)内存的区别
- 速度:内存比硬盘快很多
- 空间:内存比硬盘空间小很多
- 成本:内存比硬盘贵很多
- 持久化:内存断电后数据消失,外存断电后数据还在
在JAVASE、数据结构这些板块中,主要操作的是内存,而MySQL和这里的文件操作,主要操作的就是外存。
绝对路径和相对路径
在系统中,文件通常是以N叉树的结构来组织的
如果我们需要系统中找一个文件,则需要通过遍历这颗树的方式来进行查找。
- 绝对路径:从盘符开始,一层一层往下找,得到的路径叫做绝对路径
- 相对路径:从给定的某个目录触发,一层一层往下找,得到的路径叫相对路径
查找时一定要明确基准目录是什么,假设此时工作目录是这个此时需要找到
相对路径:以工作目录为基准,往下找一层
此时就找到了README这个文件
表示为./MySQL Server 5.7/README
绝对路径则是C:\\Program Files\\MySQL\\MySQL Server 5.7\\README
(“\\”相当于一个“/”,转义字符)
- .是一个特殊符号表示为当前目录
- ..表示当前目录的上级目录
如果要查找的文件在工作目录的上一级,那就表示为:../README
文件系统上,任何一个文件对应的路径都是唯一的,不会存在两个路径相同但是文件不同的情况,在Linux上可能存在一个文件有两个不同的路径的情况,但在Windows上文件和路径都是一一对应的
文本文件和二进制文件
- 文本文件:存储文本(ASCII码值或utf8之类的字符集编码)
- 二进制文件:存储二进制数据
判定方式
直接使用记事本打开某个文件,如果你看得懂就是文本文件,如果是一堆乱码就是二进制文件,这是因为记事本默认是使用文本文件的打开方式读取文件的。
二、文件系统操作
在JAVA标准库中提供了File这个类,来操作硬盘
File对象是硬盘上的一个文件的抽象表示:因为文件存储在硬盘上,直接使用代码操作硬盘是不方便的,所以可以在内存中创建一个对应的对象,通过操作这个对象来影响改变硬盘中的数据
File类的构造方法
File类的方法
import java.io.File;
import java.io.IOException;
public class iodemo1{
public static void main(String[] args) throws IOException {
//1.构造File对象
File file = new File("d:/cat.jpg");//这个路径可以存在也可以不存在,我这里不存在
System.out.println(file.getParent());//父路径
System.out.println(file.getName());//文件名
System.out.println(file.getPath());//文件路径
System.out.println(file.getAbsolutePath());//绝对路径
System.out.println(file.getCanonicalPath());//修饰过的绝对路径,需要抛出IOException异常
}
}
这段代码中构造了一个File对象,我们可以调用一些方法来看一看结果
在构造File时我填的是绝对路径,现在修改为相对路径来看一下结果
import java.io.File;
import java.io.IOException;
public class iodemo1{
public static void main(String[] args) throws IOException {
//1.构造File对象
File file = new File("./cat.jpg");//这个路径可以存在也可以不存在,我这里不存在
System.out.println(file.getParent());//父路径
System.out.println(file.getName());//文件名
System.out.println(file.getPath());//文件路径
System.out.println(file.getAbsolutePath());//绝对路径
System.out.println(file.getCanonicalPath());//修饰过的绝对路径,需要抛出IOException异常
}
}
可以看到,如果填入相对路径,这里的工作路径就会被识别为当前代码所处路径。 也就是这段代码在你的电脑中所处的路径。
使用file类创建文件
public static void main(String[] args) throws IOException {
File file = new File("./hello_world.txt");
System.out.println(file.isDirectory());//是不是一个目录
System.out.println(file.isFile());//是不是一个文件
System.out.println(file.isAbsolute());//是不是绝对路径
}
这段代码中构建了一个file对象在文件中,此时文件中是没有hello_world这个文件的,所以它不是目录不是文件不是绝对路径,输出结果则为:
我们可以把这个文件创建出来,然后看结果
public static void main(String[] args) throws IOException {
File file = new File("./hello_world.txt");
System.out.println(file.isDirectory());//是不是一个目录
System.out.println(file.isFile());//是不是一个文件
System.out.println(file.isAbsolute());//是不是绝对路径
file.createNewFile();//创建文件
System.out.println();
System.out.println(file.isDirectory());//是不是一个目录
System.out.println(file.isFile());//是不是一个文件
System.out.println(file.isAbsolute());//是不是绝对路径
}
}
文件被创建出来,由于构建file对象时使用的是相对路径,所以文件被创建在代码存储目录下
这里的hello_world就是被创建的文件。
使用file类创建目录
- mkdir():构建一个单级目录
- mkdirs():构建一个多级目录
public class iodemo3 {
public static void main(String[] args) {
File file = new File("test_dir");
file.mkdir();
}
}
这里使用file类构建了一个test_dir对象,然后使用mkdir构建一个单级目录,运行后左侧会出现一个名为test_dir的目录
public class iodemo3 {
public static void main(String[] args) {
File file = new File("test_dir/aaa/bbb");
file.mkdirS();
}
}
这里使用file类构建了一个test_dir对象,然后使用mkdirs构建一个多级目录,运行后左侧会出现一个名为test_dir的多级目录
列出一个目录下所包含的内容
list()
方法返回一个String数组,包含指定目录下所有文件和子目录的名称,但不包括子目录中的内容。
listFiles()
方法返回一个File
类型的数组,包含指定目录下所有文件和子目录的File
对象,但不包括子目录中的内容。
为了方便区分我在test_dir目录下创建了一些文件
public class iddemo4 {
public static void main(String[] args) {
File file = new File("test_dir");
String[] a = file.list();
System.out.println(Arrays.toString(a));
File[] b = file.listFiles();
System.out.println(Arrays.toString(b));
}
}
三、文件内容操作
- 针对文本文件,JAVA标准库提供了一组类,统称为“字符流”(Reader,Writer),读写的基本单位是字符
- 针对二进制文件,JAVA标准库提供了一组类,统称为“字节流”(InputStream,OutputStream),读写的基本单位是字节
流:这是一个比较抽象的概念,我们将文件中的内容称为流,这样在读取数据时,如果有100个字节的数据,我们可以分两次每次读50个,也可以分5次每次读20个,或者分10次每次读10个......通过流我们可以自由的读取数据
InputStream使用方法
public class iodemo5 {
public static void main(String[] args)throws IOException {
InputStream inputStream = new FileInputStream("d:/test.txt");
inputStream.close();
}
}
由于InputStream是一个抽象类,不能直接实例化,所以需要实例化基础了这个抽象类的子类FileInputStream。
文件打开以后一定要记得关闭!!!否则会出大问题
在JAVA中内存是不需要手动释放的,因为在JVM有一个GC资源回收机制,会自动进行内存的释放,而文件资源是没办法自动释放的。在每个进程中,会使用PCB来描述,在PCB中又存在一个文件描述符表,在这个表中,每打开一个文件,表中就会申请一个位置将文件放到这个表中,但是这个表是长度是有限的,如果在关闭文件之后没有进行释放,那么在程序运行过程中,可能会导致这个表的空间被占用满了,后续文件打开时无法在这个表中申请到空间,从而导致打开文件失败,这会导致很严重的问题!
为了解决这个问题,我们可以使用这样一种语法来预防忘记执行close操作的情况
public class iodemo5 {
public static void main(String[] args)throws IOException {
try(InputStream inputStream = new FileInputStream("d:/test.txt");) {
}
}
}
我们将这个语法称为try with resoures:带有资源的try操作,这个语法会在try代码块结束时,自动执行close操作
在创建完inputstream流对象后,我们通过这个对象来读取文件中的内容,此处我读取的文件是d盘的test.txt文件,我已经提前创建好了,文件中的内容为:
下面可以通过read()方法来读取文件,需要注意的是:
read()方法的返回值是int类型而不是byte类型
这是因为byte的值域为0-255,而read()方法在文件读到末尾时会返回一个-1,这并不在byte的值域内,所以可以干脆将返回值设置为int类型,在需要处理数据时,在将其转为byte即可,如果是-1,将会转换失败,从而标记文件读到了末尾。
public class iodemo5 {
public static void main(String[] args)throws IOException {
try(InputStream inputStream = new FileInputStream("d:/test.txt");) {
while (true){
int b = inputStream.read();
if(b == -1){
//读到末尾,return
break;
}
System.out.println(b);
}
}
}
}
结果为:
这是一组ASCII码数据,对应这些ASCII码可以将其转换为对应的数据。因为文件中存储的内容为中文,所以会使用utf8字符集来表示数据,在utf8中,一个汉字=3个字节,这里读出了21个ASCII码,也就是21个字节,7个汉字。
还可以使用OutputStream中的write方法来进行数据写入
public class iodemo5 {
public static void main(String[] args)throws IOException {
try(OutputStream outputStream = new FileOutputStream("d:/test.txt");) {
outputStream.write(97);
outputStream.write(98);
outputStream.write(99);
}
}
}