1. 认识文件
所谓的文件是一个广义的概念,可以代表很多东西;在操作系统里面,会把很多的硬件设备和软件设备都抽象成“文件”,统一进行管理;但是大部分情况下,我们读到的文件,都是指硬盘的文件,文件就相当于是针对“硬盘”数据的一种抽象;
1.1 简单了解硬盘
1.1.1 机械硬盘HDD
1.1.2 固态硬盘SSD
固态硬盘里面就都是集成程度很高的芯片,且固态硬盘就要比机械硬盘的效率高很多;
我们在进行服务器开发的过程中,涉及到的硬盘有的是机械硬盘有的是固态硬盘,特别是一些用来存储大规模数据的机器,任然是机械硬盘为主,但是固态硬盘的读写速速要比内存慢很多;
1.1.3 文件的操作方式
文件的操作方式是通过操作硬盘来实现的,一台计算机上有很多的文件,这些文件是通过“文件系统”(有由操作系统所提供的模块)来进行组织的,操作系统使用“目录”这样的结构来组织文件;
1.2 文件的类型
从编程的角度来看,文件类型,主要是两大类:
1、文本(文件中保存的数据,都是字符串,保存的内容都是合法的字符串)
2、二进制(文件中保存的数据,仅仅是二进制数据,不要求保存的内容是合法的字符)
合法的字符->字符集/字符编码(主流的就是utf8,一个规定的表格,里面规定了什么样的字符对应什么样的编码),如果我们的文件时utf8编码的,此时文件中的每一个数据都是合法的utf8编码的字符,就可以认为这个文件是文本文件了;如果存在一些不是utf8合法字符的情况,就是二进制文件了;
本身计算机存储的数据都是二进制的;
2. File 概述
2.1 file的属性
2.2 file类的构造方法
一个file对象,就表示一个硬盘上的文件,在构造对象的时候,就需要把这个文件的路径指定进来(使用绝对、相对路径都可以)
File提供的核心方法:文件名=前缀+扩展名->使用路径构造file对象,一定要把前缀和扩展名都写上;一个文件系统上都会对文件有权限的限制(约定了这个文件,那些用户可以读,那些用户可以写),我们是创建file对象的时候,就会使用到java提供的import java.io.File;该包里面的io分别表示:
I:input
0:output
2.3 file的相关方法
一般来说,在使用文件的内容访问时候,io操作都需要抛出import java.io.IOException;下图是关于file类方法的调用:
q:关于下图输出的数值是文件的内存地址吗?
a:首先在jvm上层,java代码中是没有任何方法获取到“内存地址”的,想要获取内存地址,只能靠native方法,进入jvm内部,通过c++代码获取到;其次我们获取到的字符串是哈希值,是通过调用hashcode的方法获取到的
3. 流对象
基于文件数据传输的特点,将文件中传输的数据称之为“文件流”
在标准库中,提供的读写文件的流对象,不是一两个类,而是有很多类,但是实际上可以吧这么多类归结于两个类中:
3.1 reader 概述
Reader是一个抽象类,不能new实例,只能new一个子类,java标准库提供了一个现成的类filereader类。其创建方法如下图所示:
创建reader对象的过程,就是“代开文件”的过程,下面是关于reader的三种方法:
1、五参数read:一次只读取一个字符;
2、一个参数read:一次读取若干个字符,回答参数指定的cbuf数组给填充满
3、三个参数read:一次读取若干个字符,回答参数指定的cbuf数组中的从off这个位置开始,到len这么长的范围内尽量填满;代码细节如下所示:
在java标准库内部,对于字符编码是进行了很多处理工作的,如果只使用char,此时使用的字符集,固定的就是unicode;如果是使用string,此时就会自动的把每一个unicode转换为utf8;
char【】 c ->包含的每一个字符都是unicode,一旦使用这个字符数组构造成string,string s = new string(c),就会在内部把每一个字符都转换成utf8
把多个unicode连续放到一起,是很难区分从哪里到哪里地一个完整的字符的,utf8是可以做到区分的;utf8可以认为是针对连续多个字符进行传输时候的一种改进方案
对于reader.read( )这个方法里面,应该是往这个read里面传入的是一个空的字符数组(不是null,而是没有实际意义数据的数组),然后由read方法内部,对这个数组内容进行填充,此时的cbuf这个参数,称为“输出型参数”
如果文件为空,就直接返回-1了;
当一个文件读取完了之后,我们要记得进行关闭close,使用colse方法,最主要的就是为了释放文件描述符;
3.2 writer 概述
writer对象主要是写入文件,默认情况下就会把原有的文件内容清空掉,如果不想清空,就需要在构造方法中加个参数,如下图所示;
关于write方法的种类如下图所示:
System.in ====》inputstream;
关于write部分代码如下所示:
package io;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
public class Demo12 {
public static void main(String[] args) {
try (OutputStream outputStream = new FileOutputStream("d:/test.txt")) {
// 这就相当于把字符流转成字节流了.
PrintWriter writer = new PrintWriter(outputStream);
writer.println("hello");
writer.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
代码分析:
缓冲区:printwriter这样的类,在进行写入的时候,不一定直接写入硬盘,而是先把数据写在一个内存构成的“缓冲区”中(buffer)
我们引入缓冲区,目的是为了提高效率;把数据写入内存是非常快的;把数据写到硬盘,是非常慢的;
这样会导致一个新的问题:当我们写入缓冲区后,如果还没来得及把缓冲区里的数据写进硬盘,进程就结束了,此时数据就会丢失;没有正真的写入硬盘;(进程结束,该内存就会释放)->为了确保数据会被写入硬盘,就应该在合适的时机,使用flush方法进行手动刷新缓冲区;
该fiush操作,可以理解为“刷新缓冲区”,将我们存放在内存缓冲区中的数据冲刷到硬盘中;
4. 代码实例
扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要 删除该文件
4.1 案例分析
文件系统操作:
1、list列出目录内容
2、判定文件的类型
3、删除文件
所谓的扫描指定文件,就是找到目录中的所有文件,以及子目录中的所有文件,只要遇到子目录都能往里面找->采用递归的方式,把所有的子目录都给扫描一遍;
4.2 代码实现
package io;
import java.io.File;
import java.util.Scanner;
public class Demo13 {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 1. 先让用户输入一个要扫描的目录
System.out.println("请输入要扫描的路径: ");
String path = scanner.next();
File rootPath = new File(path);
if (!rootPath.isDirectory()) {
System.out.println("您输入的扫描的路径有误!! ");
return;
}
// 2. 再让用户输入一个要查询的关键词.
System.out.println("请输入要删除文件的关键词: ");
String word = scanner.next();
// 3. 可以进行递归的扫描了.
// 通过这个方法进行递归.
scanDir(rootPath, word);
}
private static void scanDir(File rootPath, String word) {
// 1. 先列出 rootPath 中所有的文件和目录.
File[] files = rootPath.listFiles();
if (files == null) {
// 当前目录为 null, 就可以直接返回了.
return;
}
// 2. 遍历这里的每个元素, 针对不同类型做出不同的处理.
for (File f : files) {
// 加个日志, 方便观察当前递归的执行过程.
System.out.println("当前扫描的文件: " + f.getAbsolutePath());
if (f.isFile()) {
// 普通文件. 检查文件是否要删除. 并执行删除动作.
checkDelete(f, word);
} else {
// 目录. 递归的再去判定子目录里包含的内容
scanDir(f, word);
}
}
}
private static void checkDelete(File f, String word) {
if (!f.getName().contains(word)) {
// 不必删除, 直接方法结束
return;
}
// 需要删除
System.out.println("当前文件为: " + f.getAbsolutePath() + ", 请确认是否要删除(Y/n): ");
Scanner scanner = new Scanner(System.in);
String choice = scanner.next();
if (choice.equals("Y") || choice.equals("y")) {
// 真正执行删除操作
f.delete();
System.out.println("删除完毕!");
} else {
// 如果输入其他值, 不一定非得是 n, 都会取消删除操作.
System.out.println("取消删除!");
}
}
}
ps:本篇的内容到这里就结束了,如果感兴趣的话就请一键三连哦!!!