文件IO
一,文件的概念
文件是保存在硬盘上的数据
文件是操作系统对于硬盘数据的进一步抽象封装
硬盘是一种存储介质(存储数据)
进行读文件,本质上是在读硬盘
进行写文件,本质上是在写硬盘
识狭义上的文件(file):针对硬盘这种持久化存储的I/O设备,当我们想要进行数据保存时, 往往不是保存成一个整体,而是独立成一个个的单位进行保存,这个独立的单位就被抽象成文件的概念,就类似办公桌上的一份份真实的文件一般。
文件除了可以从根开始进行路径的描述,我们还可以从任意结点出发,进行路径的描述,⽽这种描述方式就 被称为相对路径(relativepath),相对于当前所在结点的一条路径。据内容之外,还有一部分信息,例如文件名、文件类型、文件大小等并不作为文件的数据而存在,我们把这部分信息可以视为文件的元信息。
1.1树型结构组织和目录
目录是一种特殊的文件,俗称文件夹
随着文件越来越多,对文件的系统管理也被提上了日程,如何进行文件的组织呢,一种合乎自然的想法出现了,就是按照层级结构进行组织⸺也就是我们数据结构中学习过的树形结构。这样, 一种专门用来存放管理信息的特殊文件诞生了,也就是我们平时所谓文件夹(folder)或者目录 (directory)的概念。
1.2 文件路径 Path
文件路径用来确定某个文件的位置,标识一个文件
从树型结构的角度来看,树中的每个结点都可以被一条从根开始,一直到达的结点的路径所描述,而这种描述方式就被称为文件的绝对路径(absolute path)
一台电脑上,每个文件的路径都是唯一的
除了可以从根开始进行路径的描述,我们可以从任意结点出发,进行路径的描述,而这种描述方式就被称为相对路径(relative path),相对于当前所在结点的一条路径。(要有基准路径/工作目录作为起点)
其中:“ . ” 表示当前目录,“ .. ” 表示当前目录的上一级目录
1.3 两种典型的文件
即使是普通文件,根据其保存数据的不同,也经常被分为不同的类型,我们一般简单的划分为文本文件和二进制文件,分别指代保存被字符集编码的文本和按照标准格式保存的非被字符集编码过的文件。
1)文本文件
文件内容保存的是文本(字符串)就是文本文件
2)二进制文件
文件内容保存的是二进制数据(不一定是字符串)就是二进制文件
------------------------------------------------------------------------------------------------------------------------------
简单的区分方式:用记事本打开文件,可以看懂的是文本文件,看不懂的是二进制文件
(记事本会尝试把文件内容理解成文本,会通过字符编码(字符集),其中里面有一些固定的码表如UTF8,GBK等等,如果文件内容本身是文本的,则说明里面的内容都可以在码表上查到,最终构成的结果是有意义的)
写代码的时候,处理文本文件和处理二进制文件的代码是不一样的
.txt , .java , .c属于典型的文本文件
.jpg , .exe , .dll , .png , .mp3.......都属于二进制文件
.docx 是“富文本” ,比普通的文本信息更多
1.4 IO
是站在电脑的视角来谈论输入输出的

二,文件系统操作
针对文件进行创建,删除,移动,重命名,创建目录,判定存在.....
2.1 File类
Java 中通过 java.io.File 类来对一个文件(包括目录)进行抽象的描述。注意,有File对象, 并不代表真实存在该文件
文件系统是操作系统的一个模块
文件系统提供了一组API,操作系统把这组API暴露给程序员(系统原生的API都是c语言)JVM对C语言的系统API进一步封装,得到了Java版本的API
这类系统提供的API,各种语言是通用的
2.1.1 属性
| 修饰符及类型 |
属性 |
说明 |
| static String | pathSeparator(即文件路径的分隔符,windows系统支持/和\ ,其他系统为/) |
依赖于系统的路径分隔符,String类型的表示 |
| static char | pathSeparator | 依赖于系统的路径分隔符,char类型的表示 |
2.1.2 构造方法
| 签名 | 说明 |
| File(File parent, String child) | 根据父目录+孩子文件路径,创建⼀个新的File实例 |
| File(String pathname) | 根据文件路径创建一个新的File实例,路径可以是绝 对路径或者相对路径 |
| File(String parent, String child) | 根据父目录+孩子文件路径,创建⼀个新的File实 例,父目录用路径表示 |
2.1.3 方法

| boolean | renameTo(File dest) | 进⾏⽂件改名,也可以视为我们平时的剪切、粘贴操作 |
| boolean | canRead() | 判断⽤⼾是否对⽂件有可读权限 |
| boolean | canWrite() | 判断⽤⼾是否对⽂件有可写权限 |
public class Demo1 {
public static void main(String[] args) throws IOException {
File file = new File("D:/java/classTestIO/1.txt");
// 获取到当前文件所在的目录
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());
}
}

当改成相对路径时:
public class Demo1 {
public static void main(String[] args) throws IOException {
//File file = new File("D:/java/classTestIO/1.txt");
// 写成了一个相对路径
File file = new File("./1.txt");
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());
}
}

如果是在IDEA中直接来运行Java程序,此时“基准路径”就是IDEA项目所在的目录
如果是在命令行中,则前缀的这个路径就是要运行命令的基准路径
从这个运行结果可以知道:
System.out.println(file.getAbsolutePath());
只是把相对路径和基准路径进行简单的拼接
而
System.out.println(file.getCanonicalPath());
是对上面的拼接结果进行化简,去掉中间存在的 . 和 ..
对于操作系统来说,这两种都是可以识别的
public class Demo2 {
public static void main(String[] args) throws IOException {
File file = new File("./1.txt");
System.out.println(file.exists());
// 判定该文件是否是 普通文件. 如果文件不存在, 返回 false
System.out.println(file.isFile());
// 判定该文件是否是 目录. 如果目录不存在, 返回 false
System.out.println(file.isDirectory());
// 创建空白的普通文件
file.createNewFile();
System.out.println("创建文件之后");
System.out.println(file.exists());
System.out.println(file.isFile());
System.out.println(file.isDirectory());
}
}

public class Demo3 {
public static void main(String[] args) {
File file = new File("./1.txt");
// 对文件进行删除. 删除成功, 返回 true; 删除失败 (文件本来就不存在), 返回 false
// boolean result = file.delete();
// System.out.println("result = " + result);
// 不是立即删除, 而是等当前进程结束, 再触发删除.
file.deleteOnExit();
Scanner scanner = new Scanner(System.in);
System.out.println("输入任意内容继续");
scanner.next();
}
}

第二种删除方法适用于实现“临时文件”,暂时用到这个文件,进程结束就不用了
public class Demo4 {
public static void main(String[] args) {
File file = new File("./");
// 通过 list 列出目录下的内容
// 每个元素, 就是当前目录下每个文件/目录的名字
String[] list = file.list();
System.out.println(Arrays.toString(list));
// 通过 listFiles 列出目录下的内容
// 每个元素, 就是当前目录下每个文件/目录的 File 对象
File[] listFiles = file.listFiles();
System.out.println(Arrays.toString(listFiles));
}
}

public class Demo5 {
public static void main(String[] args) {
// File file = new File("./111");
File file = new File("./111/222/333/444");
// mkdir 一次只能创建一级目录
// boolean result = file.mkdir();
// mkdirs 可以创建多级目录
boolean result = file.mkdirs();
System.out.println(result);
}
}
mk = make
dir = dictionary
dirs = dictionarys
public class Demo6 {
public static void main(String[] args) {
// src => source的缩写
// File fileSrc = new File("./1.txt");
// // dest => destination的缩写
// File fileDest = new File("./2.txt");
//
// // 把 1.txt 重命名成 2.txt
// fileSrc.renameTo(fileDest);
// 移动, 就是把 dest 的路径写到其他的目录中即可
File fileSrc = new File("./1.txt");
File fileDest = new File("./11111/1.txt");
fileSrc.renameTo(fileDest);
}
}
renameTo (File dest)可以针对文件/目录 重命名
还可以移动文件(操作系统底层,重命名和移动,其实是同一个操作)
移动操作是“重命名” 是一个非常高效的操作,即使是很大的文件,都能够一瞬间完成(前提是src和dest在同一个物理硬盘上)
三,文件内容的读写
文件内容操作:针对文件里的数据进行读和写(针对普通文件)
目录文件的读和写,是操作系统自己负责的
操作系统把文件内容的读和写封装成了“流”
Java的标准库当中,提供了一组类,表示流(虽然是一组,很多类,用法大同小异)
两个大的类别:
1)字节流 针对文件读写的基本单位是“字节” == 操作二进制文件
字节流的核心类有InputStream和OutputStream
2)字符流 针对文件读写的基本单位是“字符” == 操作文本文件
3.1 字节流
3.1.2 InputStream
InputStream是一个抽象类,没办法创建具体的实例
抽象类存在的意义就是为了被扩展(继承)

第一个read方法是 一次从文件读取一个字节
返回值类型是int,int包含四个字节,但是其实只使用了一个字节,除了表示字节的正常数据以外,还有一个特殊值,-1,表示读取结束(读取到文件末尾/读取到EOF文件结束标志)
public class Demo7 {
public static void main(String[] args) throws IOException {
InputStream inputStream = new FileInputStream("./1.txt");//填写String文件路径(绝对路径和相对路径,还可以填写File对象)
//通过read方法读取数据
while(true){
int data = inputStream.read();
if(data == -1){
//读到文件末尾
break;
}
//打印数据内容
System.out.printf("%x\n", data);
}
}
}
当往1.txt中写入“你好世界”的时候,会显示如下,是因为在utf8编码中,一个汉字是三个字节

第二个read方法,含有一个参数,返回值为int
其中这个参数为输出型参数,一般我们把方法理解成工厂,参数相当于原材料,返回值相当于产品,但是这个read方法属于特殊的方法,在这个特殊的方法中,参数既是原材料又是产品,所以就可以解决一个函数无法返回多个值的问题
在这个方法中可以返回两个值:1)返回读取到的内容
2)返回读取到的数据长度
将这个方法的执行流程类比于打饭则有:提前定义好一个字节数组(空的饭碗)把字节数组传给方法参数(交给打饭阿姨)方法内部把要读取的数据写到数组中(阿姨给打好饭)方法执行结束,此时数组的内容就已经被填充好了(阿姨把饭碗递过来)
//通过read方法读取数据,一次读取一个字节数组
while(true){
//长度随意定义
byte[] bytes = new byte[1024];
//read方法会尽量把参数的数组填满
//返回值表示实际读取到的字节数
int n = inputStream.read(bytes);
if(n == -1){
break;
}
for(int i = 0;i<n;i++){
System.out.printf("0x%x\n",bytes[i]);
}
}
第三个方法,off为offset(偏移量)
最后,在使用了read方法之后还要关闭文件
public static void main(String[] args) {
InputStream inputStream = null;
try {
inputStream = new FileInputStream("./1.txt");
// 通过 read 读取数据, 一次读一个字节数组
while (true) {
// 长度随便定, 无所谓
byte[] bytes = new byte[1024];
// read 方法会尽可能的把参数的数组填满
// 返回值表示实际读取到的字节数
int n = inputStream.read(bytes);
if (n == -1) {
break;
}
for (int i = 0; i < n; i++) {
System.out.printf("0x%X\n", bytes[i]);
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 关闭文件
try {
inputStream.close();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public static void main(String[] args) {
try (InputStream inputStream = new FileInputStream("./1.txt")) {
while (true) {
byte[] bytes = new byte[1024];
int n = inputStream.read(bytes);
if (n == -1) {
break;
}
for (int i = 0; i < n; i++) {
System.out.printf("0x%X\n", bytes[i]);
}
}
// close 不必写了
// 会在 try 结束的时候, 自动调用 close
} catch (IOException e) {
e.printStackTrace();
}
}
第二个方法中,还可以定义多个流对象,定义多个的时候要用;隔开,必须要求()里的对象实现了Closable方法
3.1.3 OutputStream
public class Demo8 {
public static void main(String[] args) {
try(OutputStream outputStream = new FileOutputStream("./1.txt")){
outputStream.write(97);
outputStream.write(98);
outputStream.write(99);
}catch(IOException e){
e.printStackTrace();
}
}
}
try(OutputStream outputStream = new FileOutputStream("./1.txt"))
在这个语句中,默认情况下,使用OutputStream打开文件,就会清空文件内容
try(OutputStream outputStream = new FileOutputStream("./1.txt",true))
加了一个true参数后表示追加,就可以以此保存原来文件里面存在的内容
3.2 字符流
1)打开文件(构造)
2)读/写(字符为单位)
3)关闭(通过try with resource)
3.2.1 Reader

1,一次读取一个字符,返回读到的字符内容
2,一次读取若干个字符
3,charBuffer是标准库对字符数组进行的封装
4,一次读若干个字符,放到数组的一部分
并且后三个方法会返回整数,表示读取到的字符个数

通过这个范围可得,read方法的返回值为两个字节,但是在uft8中汉字是三个字节,而read依旧可以读取,是因为此处的read会识别出当前文件使用的字符集,发现是uft8,按照utf8的编码规则,读取三个字节,作为一个汉字,把utf8读取到的三个字节,按照一定的规则,转换为utf16的2个字节的版本(在Java中默认使用了utf16表示汉字,utf16中,一个汉字,使用2个字节表示)
public class Demo9 {
public static void main(String[] args) {
try(Reader reader = new FileReader("./1.txt")){
// 使用 read 方法读取数据
// while (true) {
// int data = reader.read();
// if (data == -1) {
// break;
// }
// char c = (char) data;
// System.out.println(c);
// }
while (true) {
char[] chars = new char[1024];
int n = reader.read(chars);
if (n == -1) {
break;
}
for (int i = 0; i < n; i++) {
System.out.println(chars[i]);
}
}
}catch(IOException e){
e.printStackTrace();
}
}
}
3.2.1 Writer
public class Demo11 {
public static void main(String[] args) {
try (Writer writer = new FileWriter("./1.txt", true)) {
// writer.write('我');
// writer.write('真');
// writer.write('帅');
// char[] chars = {'我', '真', '帅'};
// writer.write(chars);
String s = "你也挺帅的";
writer.write(s);
} catch (IOException e) {
e.printStackTrace();
}
}
}

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)