初始JavaEE篇 —— 文件操作与IO
找往期文章包括但不限于本期文章中不懂的知识点:
个人主页:我要学编程程(ಥ_ಥ)-CSDN博客
所属专栏:JavaEE
目录
文件介绍
Java标准库中提供操作文件的类
文件系统操作
File类的介绍
File类的使用
文件内容操作
二进制文件的读写操作
文本文件的读写操作
文件介绍
文件分为两种:一种是狭义上的文件;另一种是广义上的文件。狭义的文件就是指机器上存储数据的地方,在电脑上面,C盘、D盘这些就可以称为文件;而广义的文件是指一切资源。例如,网卡、CPU等就可以被抽象成文件。抽象成文件后,就可以更加方便地去操作,也相当于是一层封装。我们今天要学习的是狭义上的文件。也就是C盘、D盘上的文件。
而狭义的文件也分为两种,一种是目录,也就是文件夹;另外一种就是普通的文件,例如,.txt。
计算机中的文件有很多,我们是使用路径来区分的。
例如,D:\编程学习\Java代码练习\java-jdk17-version\AVLTree\src\AVLTree.java 与 D:\编程学习\Java代码练习\java-jdk17-version\AVLTree\src\Test.java 就是两个不同的路径,也就代表两者不是同一个文件。
文件其实和我们之前学习的树形结构是一样的,只不过前面学习的树形结构主要是二叉树,而这里的文件分类采用的是N叉树的方式而已。
在表示文件的路径中,主流的操作系统都是使用 / ,但是Windows系统既可以用 / ,也可以用 \ 。Windows系统默认是采用 \ 。因为 \ 可能在跟字符一起连用时,会形成转义字符,因此采用的 /。
上面那种路径表示的是绝对路径,还有一种相对路径的表示方法。
.\AVLTree.java ——> 这就是一个相对路径,在相对路径的基础上,需要有一个基准路径,这个点符就代表的是基准路径,而 .. 表示在当前目录的上一级目录。
如果在IDEA中使用 ./AVLTree.java,这里的基准路径是项目所在的目录。或者说 src 所在的目录。
站在程序员的角度,看待文件时分为两种:文本文件与二进行文件,我们后续学习的操作也是根据文件的不同,从而使用不能的类来操作。当然,所有的文件在一定程度上来说都是二进制文件,只不过某些文件的二进行数据在编码表中刚好全部可以查到并且正常解析出来(不会出现乱码的情况)。当我们使用记事本打开文件时,如果是二进制文件那么就会出现乱码的情况,如果不是乱码,那就是文本文件。(有可能这个不是二进制文件,但是打开之后,是我们看不懂的。例如,法语或者是德语之类的)
常见的文本文件:.java 、.c 、.txt等;常见的二进制文件:图像、音频、可执行的程序(.exe)。
Java标准库中提供操作文件的类
Java标准库中提供操作文件的类,有两种:一种用来是操作文件外层、另一种是用来操作文件内容。文件外层就是指创建、删除、重命名等。也被称为文件系统操作。
文件系统操作
Java中使用File类来操作文件系统,在操作任何一个文件之前,都得先创建File对象,即使这个文件是不存在的,我们也需要创建这个File对象才能进行相关操作。
File类的介绍
构造方法:
方法名 | 说明 |
File(File parent, String child) | 根据父目录+孩子文件路径,构造一个新的 File 实例 |
File(String pathname) | 根据文件路径构造一个新的 File 实例,路径可以是绝对路径或者相对路径 |
File(String parent, String child) | 根据父目录+孩子文件路径,构造一个新的 File 实 例,父目录用路径表示 |
上面的方法都是用来构造目录或者是文件的,不会自动去创建。
常用方法:
返回值 | 方法名 | 说明 |
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 运行结束时 才会进行(也就是进程结束时才删除) |
String[] | list() | 返回 File 对象代表的目录下的所有 文件名 |
File[] | listFiles() | 返回 File 对象代表的目录下的所有 文件,以 File 对象表示 |
boolean | mkdir() | 创建 File 对象代表的目录 |
boolean | mkdirs() | 创建 File 对象代表的目录,如果必 要,会创建中间目录 |
boolean | renameTo(File dest) | 进行文件改名,也可以视为我们平 时的剪切、粘贴操作 |
boolean | canRead() | 判断用户是否对文件有可读权限 |
boolean | canWrite() | 判断用户是否对文件有可写权限 |
File类的使用
代码演示:
1)创建、删除文件与目录:
public class Test {
public static void main(String[] args) throws IOException {
// 构造目录
File directory = new File("./Test");
// 创建目录 ——> 可以根据返回来决定下面执行什么逻辑
System.out.println(directory.mkdir()); // 初次创建为true,而后皆为false
// 构造文件 ——> 根据父目录+子路径来构造
File file1 = new File(directory, "./test1.txt"); // 这里的 . 代指父目录
// 创建文件
System.out.println(file1.createNewFile());
// 构造文件 ——> 直接根据路径来构造
File file2 = new File("./Test/test2.txt");
// 创建文件
System.out.println(file2.createNewFile());
// 构造文件 ——> 根据父目录+子路径来构造,父目录为路径
File file3 = new File("./Test", "./test3.txt"); // 子路径这里的 . 代指父目录
System.out.println(file3.createNewFile());
System.out.println(file1.delete()); // true
System.out.println(directory.delete()); // false
}
}
从上述代码的运行结果,我们可以得出:当目录下存在文件时,不能删除目录。
2)get 系列方法:
public class Test {
public static void main(String[] args) throws IOException {
// 构造文件
File file = new File("./test.txt");
// 创建文件
file.createNewFile();
System.out.println("父目录:"+file.getParent());
System.out.println("文件名:"+file.getName());
System.out.println("文件路径:"+file.getPath());
System.out.println("文件绝对路径:"+file.getAbsoluteFile());
System.out.println("文件被修饰的绝对路径:"+file.getCanonicalPath());
}
}
运行结果:
1、getParent 与 getPath 都是根据构造文件时的路径来获取的,如果 构造方法中的路径是绝对路径,那么获取到的结果就是绝对路径。
2、文件的绝对路径 与 文件被修饰过的绝对路径两者的区别是:被修饰过的,不会出现点符号,而是直接被解析成完成的路径了;反观,没有被修饰过的就会根据构造文件时的路径来作取舍,如果构造文件的路径中,带有点符号,那么最终的结果中也是带有点符号的,反之则不会有点符号。
3、文件路径 就是直接拿构造方法中传入的文件路径。
3)其余方法:
public class Test {
public static void main(String[] args) throws IOException {
// 构造目录
File directory = new File("./12306");
System.out.println("directory创建前是否存在:"+directory.exists());
directory.mkdir(); // 创建目录
directory.deleteOnExit(); // 在进程结束时,再去销毁
System.out.println("directory创建后是否存在:"+directory.exists());
System.out.println("directory是否是目录:"+directory.isDirectory());
System.out.println("directory是否是文件:"+directory.isFile());
// 如果直接调用list(),只会得到一个哈希值,也就是引用的地址
// 返回目录下的所有文件名
System.out.println(Arrays.toString(directory.list()));
// 返回目录下的所有文件名(包括后代的后代)
System.out.println(Arrays.toString(directory.listFiles()));
}
}
运行结果:
4)renameTo方法:
public class Test {
public static void main(String[] args) throws IOException {
// 即使没有基准路径,它也是默认在项目路径下创建
File file1 = new File("test.txt");
file1.createNewFile();
File file2 = new File("test3.txt");
// 将file1的文件名改为file2的文件名
file1.renameTo(file2);
System.out.println(file1.getName());
}
}
重命名只需要有File对象即可,不需要关注这个对象是否存在。可以理解为这个对象为 name 的载体。
public class Test {
public static void main(String[] args) throws IOException {
File file1 = new File("test.txt"); // 这个路径是在项目路径下
System.out.println(file1.createNewFile());
// ".." 代表当前路径的上一级目录
File file2 = new File("../test.txt");
System.out.println(file1.renameTo(file2));
// 打印出文件的绝对路径
System.out.println(file1.getCanonicalPath());
}
}
如果我们去仔细观察就会发现上述两个file1对象所指向的文件在重命名或者移动之后,对应磁盘中的位置也发生了变化,但是file1对象本身还是指向最初创建的那个文件路径。我们可以理解为file对象指向的是创建时的那个文件路径,如果没有修改指向的话,那么这个对象所指向的位置是不发生变化的,在重命名或者移动文件的前后,只是文件的位置发生了变化,也就是file1对象所指向位置对应的文件被删除了(剪切就是一种删除),但是file1对象还是存在的。如下图所示:
再举个例子:小明、小红两人在谈恋爱,小明经常会去小红家里找她玩。小军也喜欢小红,但是由于小红已经名花有主了,所以他只能默默地将这份爱意埋在心里,但过了一段时间两人分手了,恰巧小红又搬家了,小军刚好知道小红搬到哪里了,所以就开始疯狂追小红,两人也顺利的在一起了。
上面例子中,小明就是file1对象,知道着小红的住址,也就是文件的具体地址,后面两人分手了,也就只有小军知道小红的住址了,这就是移动文件之后,只有file2对象保存着文件的具体地址,而file1对象只是保存着刚创建时的文件地址。
文件内容操作
上面是文件的系统操作,下面我们来学习文件的内容操作。文件的内容操作,就是去进行 IO,也就是输入与输出。来修改文件的内容。
因为文件分为文本文件与二进制文件,所以文件的读写操作,也是分别有两种不同的方式。一种是针对文本文件的读写操作,另一种是针对二进制文件的读写操作。Java中是通过流对象来操作文件的,字节流可以用来读写二进制文件,字符流可以用来读写文本文件。
二进制文件的读写操作
流对象也是一种资源,因此我们在使用的时候,也是需要先打开资源,再是使用资源,最后还得关闭资源。
打开资源,也就是创建具体的流对象。
读写操作,有两个祖宗类:InputStream、OutputStream,这两个都是抽象类,后面都有一些继承它们的子类,最常用的就是 FIleInputStream、FIleOutputStream。
读操作的构造方法:
方法 | 说明 |
FileInputStream(File file) | 利用 File 构造文件输入流 |
FileInputStream(String name) | 利用文件路径构造文件输入流 |
注意:对于 OutputStream 来说,默认情况下会尝试创建不存在的文件,而对于 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() | 关闭字节流 |
无参的版本,返回的是读取到的具体数据,带有 字节数组的版本,返回的是读取的数量。
写操作的构造方法:
方法 | 说明 |
FileOutputStream(File file) | 利用 File 构造文件输出流 |
FileOutputStream(File file, boolean append) | 利用 File、append 构造文件输出流 |
FileOutputStream(String name) | 利用文件路径构造文件输出流 |
FileOutputStream(String name, boolean append) | 利用文件路径、append构造文件输出流 |
写操作的具体方法:
方法 | 说明 |
void write(int b) | 将指定的字节写入文件 |
void write(byte[] b) | 将b这个字节数组中的数据全部写入文件中 |
void write(byte[] b, int off, int len) | 将 b 这个字节数组中从off 开始的数据写入文件中,一共写len 个 |
void close() | 关闭字节流 |
代码演示:
public class Test {
public static void main(String[] args) throws IOException {
// 先创建一个文件
File file = new File("./test.txt");
boolean isCreated = file.createNewFile();
if (isCreated) {
// 开始往文件中写入数据
// 1、打开文件输出流
FileOutputStream fos = new FileOutputStream(file);
// 2、写入数据
// 因为这里的输出流是字节流,所以需要将字符串转换为字节数组
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要写入的文件内容:");
String str = scanner.nextLine();
fos.write(str.getBytes());
// 3、关闭输出流
fos.close();
// 开始往文件中读取数据
// 1、打开文件输入流
FileInputStream fis = new FileInputStream(file);
// 2、读取数据
byte[] bytes = new byte[1024]; // 存放到数组中
// 这里的读取的时候,最多读取1024个字节
int len = 0;
while ((len = fis.read(bytes))!= -1) {
// 构造一个String对象,将字节数组转换为字符串
System.out.println(new String(bytes, 0, len));
}
// 3、关闭输入流
fis.close();
} else {
System.out.println("文件创建失败");
}
}
}
运行结果:
注意:每创建一个 OutputStream 对象(包括子类)会清除上次文件中残留的内容,如果想要在上一次的内容上继续进行 "写" 操作,只能在构造方法中,传入一个 true,表示可追加模式
文件的读写操作可以总结出一个模版:
字节流:
1、写数据到文件中;
1)创建一个FileOutputStream的输入流对象,
2)利用控制台输入数据到字符串中,将字符串转为字节数组作为参数传入write方法,
3)关闭输入流对象,
2、从文件中读数据;
1)创建一个FileInputStream的输入流对象,
2)创建一个字节数组,利用read方法从文件中读取数据到字节数组中(当read的返回值为-1时,便可以结束读取了),
3)关闭输入流对象
字符流也是适用的,只不过是换了输入流与输出流对象罢了。
文本文件的读写操作
读写操作,有两个祖宗类:Reader、Writer,这两个都是抽象类,后面都有一些继承它们的子类,最常用的就是 FileReader、FileWriter。
读操作的构造方法:
方法 | 说明 |
FileReader(String fileName) | 利用文件路径构造文件输入流 |
FileReader(File file) | 利用 File 构造文件输入流 |
读操作的具体方法:
方法 | 说明 |
int read() | 一次读入一个字符,返回字符的整数值,若到达文件末尾,则返回 -1。 |
int read(char[] cbuf) | 最多读取 cbuf.length 个字符的数据到 cbuf 中,返回实际读到的数量;-1代表以及读完了 |
int read(char[] cbuf, int off, int n) | 最多读取len-off 字符的数据到 cbuf 中,从 off 开始读,返回实际读到的数量;-1代表以及读完了 |
void close() | 关闭字符流 |
写操作的构造方法:
方法 | 说明 |
FileWriter(String fileName) | 利用文件路径构造文件输出流 |
FileWriter(File file) | 利用 File 构造文件输出流 |
写操作的具体方法:
方法 | 说明 |
void write(int c) | 将指定的字节写入文件 |
void write(char[] cbuf) | 将 cbuf这个字符数组的数据全部写入文件中 |
void write(String str) | 将指定字符串写入文件 |
void close() | 关闭字符流 |
代码演示:
public class Test {
public static void main(String[] args) throws IOException {
// 创建文件
File file = new File("./test.txt");
boolean isCreated = file.createNewFile();
if (isCreated) {
// 写入数据
// 1、打开文件输出流
FileWriter writer = new FileWriter(file);
// 2、往文件中写入数据
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要写入文件的数据:");
String str = scanner.nextLine();
writer.write(str);
// 3、关闭输出流对象
writer.close();
// 读取数据
// 1、打开文件输入流
FileReader reader = new FileReader(file);
// 2、将文件的中的数据读取出来
char[] chars = new char[1024];
int len = 0;
while ((len = reader.read(chars)) != -1) {
System.out.println(new String(chars, 0, len));
}
} else {
System.out.println("文件创建失败");
}
}
}
运行结果:
注意:FileWriter 也是默认是覆盖模式,只要传入 true,这样下一次在同一个文件中进行写操作时,不会清除其中的内容,而是会从最后一个字符开始写。
Java7之后,就引进了 try-with-resources的语法。这个可以避免我们忘记释放资源,从而导致的程序异常。
代码演示:
public class Test {
public static void main(String[] args) throws IOException {
String filePath = "test.txt";
// 使用 try-with-resources 自动关闭资源
try (FileReader fr = new FileReader(filePath)) {
int len = 0;
char[] chars = new char[1024];
while ((len = fr.read(chars)) != -1) {
System.out.println(new String(chars, 0, len));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
进入 try 时,就会打开资源,当除了 try 的{} 时,就会自动释放资源。
好啦!本期 初始JavaEE篇 —— 文件操作与IO 的学习之旅就到此结束啦!我们下一期再一起学习吧!