文件管理与Java操作全解析
目录
一、认识文件
1.树型结构组织和目录
2.文件路径
二、Java操作文件
1.File概述
1)属性
2)构造方法
3)常用方法
三、数据流
1.FileInputStream
2.InputStream
3.OutputStream
4.Reader
5.Writer
一、认识文件
狭义的文件: 保存在硬盘上的文件。
广义的文件: 操作系统进行资源管理的一种机制。很多的软件/硬件资源,抽象成 "文件" 来进行表示
先来了解一下 狭义上的文件。针对硬盘这种持久化的存储的 I/O 设备,当我们想要进保存数据的时候,往往不是保存一个整体,而是独立成一个个的单位进行保存,这个独立的单位就被抽象成文件的概念。
文件中除了有数据内容之外,还有一部分信息,例如文件名、文件类型、文件大小等并不作为文件的数据而存在,我们把这部分信息可以视为文件的 元信息。
1.树型结构组织和目录
同时,随着我呢间越来越多,对于文件的管理就需要尽快的进行处理,对于处理方式也就现在所使用的方式了,所谓的——文件夹或者目录的概念。
这种就是树型结构组织和目录。
2.文件路径
绝对路径:
从盘符(根节点)出发,逐级表示出来,这种的形式称之为——绝对路径。
如:D:\Java_cun\练习 从D这个盘符出发
相对路径:
需要一个明确的一个 "基准路径",再来查找要查找的文件,就称之为——相对路径。
如:假设基准路径为:D:\Java_cun 那么查找练习这个文件就是:./练习
假设基准路径为:D:\Java_cun\练习\src 那么查找练习这个文件就是:../练习
「 . 」 表示当前所在的目录位置
「 .. 」表示当前所在的路径的上一层路径,相当于是父类
二、Java操作文件
Java中通过 java.io.File 类来对一个文件(包括目录)进行抽象的描述。这里要注意的是,存在 File 对象,并不代表真实存在该文件。
1.File概述
1)属性
修饰符及类型 | 属性 | 说明 |
static String | pathSeparator(比如Windows中为 \ ) | 依赖于系统的路径分割符,String类型的表示 |
static char | pathSeparator | 依赖于系统的路径分割符,char类型的表示 |
2)构造方法
签名 | 说明 |
File(File parent,String child) | 根据父目录 + 孩子文件路径,创建一个新的 File 实例 |
File(String pathname) | 根据文 件路径常见一个新的 File 实例,路径可以是绝对路径 或 相对路径 |
File(String parent,String chile) | 根据 父目录 + 孩子文件路径,创建一个新的 File 实例,父目录用路径表示。 |
3)常用方法
返回值类型 | 方法签名 | 说明 |
String | getParent() | 返回 File 对象的父目录文件路径 |
String | getName() | 返回 File 对象的 纯文件名称 |
String | getPath() | 返回 File 对象的文件路径(如果file是绝对路径,那么这里就是绝对路径;反之返回的就是相对路径) |
String | getAbsolutePath() | 返回 File 对象的绝对路径 |
String | getCanonicalPath() | 返回 File 对象的修饰过的绝对路径 (下面的代码会进行演示什么是修饰过的) |
演示代码实现:
先对于绝对路径进行操作:
import java.io.File;
import java.io.IOException;public class Demo1 {public static void main(String[] args) throws IOException {File file = new File("D:/Java_cun/练习/h-IO/test.txt");System.out.println(file.getParent()); // 返回 file 这个文件的父目录文件路径System.out.println(file.getName()); // 返回 file 这个文件的存文件名称// 这个getPath() 和前面构造的过程是相关的,当构造的是绝对路径,那么这里返回的就是绝对路径;反之为相对路径System.out.println(file.getPath()); // 返回 file 这个文件对象的文件路径System.out.println(file.getAbsolutePath()); // 返回 file 这个文件对象的绝对路径// 这个getCanonicalPath()方法是存在异常的,需要进行抛出异常System.out.println(file.getCanonicalPath()); // 返回 file 这个对象修饰过的绝对路径}
}
这里要注意的是对于 getCanonicalPath() 这个方法是存在异常的,需要进行特殊处理。
上述的就是代码所展示的结果了。接下来演示一下用相对路径进行演示一下:
import java.io.File;
import java.io.IOException;public class Demo1 {public static void main(String[] args) throws IOException {File file = new File("./test.txt");System.out.println(file.getParent()); // 返回 file 这个文件的父目录文件路径System.out.println(file.getName()); // 返回 file 这个文件的存文件名称// 这个getPath() 和前面构造的过程是相关的,当构造的是绝对路径,那么这里返回的就是绝对路径;反之为相对路径System.out.println(file.getPath()); // 返回 file 这个文件对象的文件路径System.out.println(file.getAbsolutePath()); // 返回 file 这个文件对象的绝对路径// 这个getCanonicalPath()方法是存在异常的,需要进行抛出异常System.out.println(file.getCanonicalPath()); // 返回 file 这个对象修饰过的绝对路径}
}
这里就可以看出区别了,getAbsolutePath()方法呢返回的直接在构造方法中路径前面添加上一个绝对路径,这个路径就是这个项目的项目目录的路径。getCanonicalPath()是进行处理的一个绝对路径,也就将 '.' 进行剔除的路径。
boolean | exists() | 判断 File对象描述的文件是否真实存在 |
boolean | isFile() | 判断 File对象代表的文件是否是一个普通文件 |
boolean | isDirectory() | 判断 File对象代表的文件是否是一个目录 |
boolean | createNewFile() | 根据 File对象,自动创建一个空文件。成功创阿金后返回 true |
import java.io.File;public class Demo2 {public static void main(String[] args) {File file = new File("./test.txt");// 判断这个 file文件对象 是否是真实存在的System.out.println(file.exists());// 判断这个 file文件对象 是否是一个普通文件System.out.println(file.isFile());// 判断这个 file文件对象 是否是一个目录System.out.println(file.isDirectory());}
}
如果运行上述的代码的话,最后输出的都将会是 false,因为当前路径下不存在 test.txt文件。对于创建文件可以选择手动创建或者是使用代码进行创建文件。这里使用代码进行创建:
import java.io.File;
import java.io.IOException;public class Demo2 {public static void main(String[] args) throws IOException {File file = new File("./test.txt");// 注意进行处理异常,这里是存在返回值的,当创建成功就返回true,否则返回falseSystem.out.println(file.createNewFile());// 判断这个 file文件对象 是否是真实存在的System.out.println(file.exists());// 判断这个 file文件对象 是否是一个普通文件System.out.println(file.isFile());// 判断这个 file文件对象 是否是一个目录System.out.println(file.isDirectory());}
}
在创建成功后,当前项目目录下就会创建一个test.txt文件:
因为这里创建的是一个文件而非目录,所以对于目录的判断即为 false。
boolean | delete() | 根据 File对象,删除该文件。成功删除后返回 true |
void | deleteOnExit() | 根据 File对象,标注文件将被删除,删除这个动作会到 JVM 运行结束时才会进行 |
import java.io.File;public class Demo3 {public static void main(String[] args) throws InterruptedException {File file = new File("./test.txt");System.out.println(file.delete());// 无返回值file.deleteOnExit();// 这里进行睡眠是为了观察 deleteOnExit() 函数的效果Thread.sleep(10000);}
}
这里 对于这个 deleteOnExit() 函数只有 进程退出时才会删除文件的操作使用场景比如说在 word、ppt中等出现这种情况,比如正在进行编写word的时候写了很多的内容,但是这时候电脑没电了,但是你的文档还没有保存,这时候是不是以为文档没有保存?如果出现这种情况,就会之后,当再次打开word的时候,就会提示你是否恢复未保存的内容,这个就是上述的进程退出时才会删除文件的操作。其实细心就会发现 当你进行操作的时候,offer系列的软件就会产生一个 隐藏的临时文件。
String[] | list() | 返回 File对象代表的目录下的所有文件名 |
File[] | listFiles() | 返回 File对象代表的目录下的所有文件名,以File对象表示 |
import java.io.File;
import java.util.Arrays;public class Demo4 {public static void main(String[] args) {File file = new File("C:/");// String类型的数组对象String[] files1 = file.list();System.out.println(Arrays.toString(files1));// File类型的数组对象File[] files2 = file.listFiles();System.out.println(Arrays.toString(files2));}
}
上述的就是输出 C盘 下的所有文件。
import java.io.File;public class Demo5 {public static void main(String[] args) {File file = new File("./test");// 在这个项目目录下创建一个 test目录file.mkdir();File files = new File("./111/222/333");files.mkdirs(); // 创建多级目录}
}
boolean | renameTo(File dest) | 进行文件改名,也可以视为平时的粘贴操作。还起到 "移动" 效果 |
import java.io.File;public class Demo6 {public static void main(String[] args) {File file = new File("./test");File newFile = new File("./test2");// 将 file 对象的test 目录的名称修改成 -> test2 这个名称file.renameTo(newFile);}
}
这个就是一个简单的重命名操作,接下来看一下 "移动" 操作:
import java.io.File;public class Demo6 {public static void main(String[] args) {File file = new File("./test");File newFile = new File("./src/test");// 将 test 目录移动到 本项目目录下的 src目录下的test目录下file.renameTo(newFile);}
}
从操作系统的角度上来看,移动操作和重命名本质是一样的。这个移动操作通常来说非常的快,但是也是存在例外的,如果你的操作是跨硬盘了,那么就是相当于是 复制+删除,就非常慢了。
三、数据流
Java中针对文件内容的操作,主要通过一组 "流对象" 来实现的。
因此,计算机中针对 读写文件,使用 流(Stream) 进行表示,流是操作系统层面的术语,和语言是无关的,各种编程语言操作文件,都叫做 —— 流。
Java中提供了一组类来表示流,这里主要进行介绍8个即可。
整体分为:
1.字节流:
读写文件,以字节为单位,是针对二进制文件使用的。
InputStream 输入(从文件读数据);OutputStream 输出(往文件写数据)
2.字符流:
读写文件,以字符为单位。是针对文本文件使用的。
Reader 输入(从文件读数据);Writer 输出(往文件写数据)
字节是不等于字符的,一个字符可以对应多个字节,具体取决于编码方式(字符集)。
数据的流向:
从硬盘 => CPU 输入;从 CPU => 硬盘 输出。
在了解 InputStream(抽象类) 之前,需要先了解它的具体的实现类。对于InputStream的实现类有很多,因为现在只是关心文件的 读取操作,那么使用 FileInputStream类
1.FileInputStream
构造方法:
签名 | 说明 |
FileInputStream(File file) | 利用 File构造文件输入流 |
FileInputStream(String name) | 利用文件路径构造文件输出流 |
只需要了解上述的两个方法即可。之后对于InputStream 会使用到这个类。
2.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() | 关闭字节流 |
在正式的进行输入操作也就是read之前呢,现将基本的框架编写出来,但是需要注意当在编写InputStream 的时候其中的 close() 函数一定需要进行执行,要不就是所谓的"占着茅坑不拉屎"
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;public class Demo7 {public static void main(String[] args) throws IOException {try(InputStream inputStream = new FileInputStream("./test.txt")) {}}
}
这个在try后面加上() 的编写方法称之为 —— try with resources 是Java 1.5 引入的语法。
这样编写的 InputStream 代码,执行完 try 这个代码块之后呢,就会自动调用 close() 函数。就如同 多线程中锁的使用似的。
但是需要注意,并不是所有的类都可以放到 try() 中,只有实现了 Closable 接口的时候才能放入。
这样编写好处也有可以在 try() 中创建多个 InputStream 类,只需要在他们中间加入一个 ';' 即可。
接下来先准备好test.txt文件,之后对于这个文件进行 read 操作
对这个文件进行read操作:
read() 一次读一个字节:
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;public class Demo7 {public static void main(String[] args) throws IOException {try(InputStream inputStream = new FileInputStream("./test.txt")) {while(true) {int data = inputStream.read();if (data == -1) {// 读完了break;}System.out.println(data);}}}
}
进行运行后呢,会发现并不是文件中对应的hello,而是一堆数字,但是是没有问题。因为这个数字翻译过来就是hello,而翻译的依据就是 ascii表:可以进入这里查看菜鸟编程——ASCII表
read(byte[] b) 一次读多个字节
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;public class Demo7 {public static void main(String[] args) throws IOException {try(InputStream inputStream = new FileInputStream("./test.txt")) {while(true) {byte[] b = new byte[3];// 输出型参数的写法,返回的就是一次读到了多少个字节int n = inputStream.read(b);if(n == -1) {// 文件读完break;}for (int i = 0;i < n;i++) {System.out.println(b[i]);}System.out.println("==========================");}}}
}
每次进行 read 的时候,都会将byte数组尽可能的进行填满,填不满就能填几个是几个,而 read 后所返回的就是读到了多少长度的字节,就是所谓的 输出型参数。
对于后面的这个 read(byte[] b,int off,int len) 函数就是在上面这个函数的基础上添加一个读取长度,不像上面的是尽可能的去填满 byte[] b 函数。
3.OutputStream
对于使用 OutputStream 之前还是如同 InputStream 似的,需要先了解一个实现类 —— FileOutputStream ,对于这个类和 FileInputStream是类似的。
先看看对于 OuputStream 的方法:
修饰符及返回值 | 方法签名 | 说明 |
void | write(int b) | 写入传入字节的数据 |
void | write(byte[] b) | 将 b 这个字节数组的数据都进行写入 |
int | write(byte[] b,int off,int len) | 将 b 这个字节数组中从off 开始的数据 写入,一共写 len个 |
void | close() | 关闭字节流 |
void | flush() | 将缓冲区遗留的数据进行刷新,进行写入到文件中 |
这里还是使用 try() 方式进行编写这个代码,可以保证执行 close() 函数
并且对于 OutputStream 要写入的数据,如果没有存在对应的文件就会进行创建构造方法中编写的文件。
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;public class Demo8 {public static void main(String[] args) throws IOException {try(OutputStream outputStream = new FileOutputStream("./output.txt")) {outputStream.write(99);outputStream.write(98);outputStream.write(97);}}
}
这里传入的可以是 ascii表的值,进行运行就会发现在对应的项目目录下创建了一个 output.txt 文件这里就是上述传入的 ascii码表对应的值:
但是这样的编写方式是存在问题的,当再次写入的时候,那么这个文件就会先清空再将写入的数据放入到文件中,那么想要继续往后链接,那么需要对于这个构造方法进行参数的再次传入一个值:
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;public class Demo8 {public static void main(String[] args) throws IOException {try(OutputStream outputStream = new FileOutputStream("./output.txt",true)) {outputStream.write(99);outputStream.write(98);outputStream.write(97);}}
}
添加一个 true,这样之后就会进行链接写入。
一次写若干个字节:
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;public class Demo8 {public static void main(String[] args) throws IOException {try(OutputStream outputStream = new FileOutputStream("./output.txt",true)) {byte[] b = {98,97,96};outputStream.write(b);}}
}
4.Reader
对于这个 Reader 接口呢,同样需要使用其实现类,为 —— FileReader这个实现类的构造方法是和上面的两个是相似的。
修饰符及返回值 | 方法签名 | 说明 |
int | read() | 一次读一个字符 |
int | read(char[] cbuf) | 读字符数组,几可能得把字符数组给填满,返回实际读到的字符个数 |
int | read(CharBuffer target) | 和上面的是相似的,对于这个CharBuffer 就是对于 char[] 数组进行了封装 |
int | read(char[] cbuf,int off,int len) | 读字符数组,从off 开始的数据 写入,一共写 len个 |
void | close() | 关闭字符流 |
一次读一个字符,这个进行转换成char,即可:
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;public class Demo9 {public static void main(String[] args) {try (Reader reader = new FileReader("./test.txt")) {while (true) {int b = reader.read();if (b == -1) {break;}System.out.println((char) b);}} catch (IOException e) {throw new RuntimeException(e);}}
}
一次读多个字符,这里会自动进行转码:
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;public class Demo9 {public static void main(String[] args) {try (Reader reader = new FileReader("./test.txt")) {while (true) {char[] buf = new char[1024];int n = reader.read(buf);if (n == -1) {break;}for (int i = 0; i < n; i++) {System.out.println(buf[i]);}}} catch (IOException e) {throw new RuntimeException(e);}}
}
5.Writer
对于这个 Writer 接口呢,同样需要使用其实现类,为 —— FileWriter这个实现类的构造方法是和上面的是相似的。
直接看方法:
修饰符及返回值 | 方法签名 | 说明 |
void | write(int c) | 写入传入的数据 |
void | write(String str) | 往里写 str 这个字符串 |
void | write(char[] cbuf) | 将 cbuf 这个字符数组的数据都进行写入 |
void | write(char[] cbuf,int off,int len) | 将 cbuf 这个字符数组从off 开始的数据 写入, 一共写 len个 |
void | write(String str,int off,int len) | 将 str 这个字符串从off 开始的数据 写入, 一共写 len个 |
void | close() | 关闭字符流 |
其余的就非常的熟悉了,直接演示对于字符串的编写:
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;public class Demo10 {public static void main(String[] args) {try (Writer writer = new FileWriter("./output.txt", true)) {writer.write("Hello World");} catch (IOException e) {throw new RuntimeException(e);}}
}
这里对于 追加写的话,同样是在构造方法的参数中,添加一个true 和上面的 OutputStream 对于追加写的操作是一致的。
感觉文章不错的话,期待你的一键三连哦,你的鼓励就是我的动力,让我们一起加油,顶峰相见。拜拜喽~~我们下次再见💓💓💓💓💓💓💓💓💓💓💓💓