目录
一、文件
1.1 文件的概念
可以大概从狭义和广义两个方向来区分文件。
从狭义上来看,存储在硬盘上的数据,以"文件“为单位,进行组织。
常见的文件大致有: 文本文件,图片,office系列,视频,音频,可执行程序......
如上图所示,其实文件夹,也称目录,其实也是一种特殊的文件。
而从广义上来讲,最典型的比如 Linux操作系统,为了实现接口的统一性,将所有的 I/O 设备都抽象成了文件的概念,使用这一理念 —— 万物皆文件。
比如网卡,从网卡中接受数据,就读这个文件,往网卡里发送文件,就写这个文件。
1.2 文件的路径
在描述文件的路径之前,我们先来了解一下目录结构,它的底层结构是n叉树。
其实路径:大致分为两种,一种是绝对路径,一种是相对路径,两者的本质其实都是反应文件在电脑上的具体位置。
1.2.1 绝对路径
如何在文件系统中如何定位我们的一个唯一的文件就成为当前要解决的问题,但这难不倒计算机科学家,因为从树型结构的角度来看;
树中的每个结点都可以被一条从根开始,一直到达的结点的路径所描述,而这种描述方式就被称为文件的绝对路径(absolute path)。
1.2.2 相对路径
除了可以从根开始进行路径的描述,我们可以从任意结点出发,进行路径的描述,
而这种描述方式就被称为相对路径(relative path);相对于当前所在结点的一条路径。
举例:
说了这么多,下面我们来举个栗子吧。
形如 D:\网易云\CloudMusic\cloudmusic.exe 就是一个绝对路径
“ D: ” 称之为盘符,而中间用 "\"来进行分割的是目录。
补充一个小知识:目录之间的分割符是可以使用 \ (反斜杠),也可以使用 /(斜杠)
而相对路径就稍微显得有些复杂,因为其要需要一个基准路径(工作路径),通过它才能描述出这个文件具体在哪,简单来说,就是需要一个参考系。在日常生活中,我们知道,通过不同的参考系来观察同一个物体,得到的结果是不一样的,相对路径的计算也是这个道理。
示例:
简单难度
还是描述网易云这个软件
基准路径是D: 此时相对路径如何描述?
相对路径: 网易云\CloudMusic\cloudmusic.exe
当然也可以在前面加上 ./ 变成 ./网易云/CloudMusic/cloudmusic.exe
在相对路径中,使用 . 表示”当前目录“ 。
基准路径:D:\网易云\CloudMusic
相对路径: ./cloudmusic.exe.
进阶
如果所给的基准路径的目录下没有所找的文件该怎么办?
比如:基准路径为: D:\网易云\CloudMusic\skin
可以使用 .. 来返回上级目录。
网易云的相对路径; ../cloudmusic.exe.
当然了 如果是想通过 .. 从C盘切换到D盘,这种是不被允许的。
1.3 文件的分类
即使是普通文件,根据其保存数据的不同,也经常被分为不同的类型,我们一般简单的划分为 文本文件 和 二进制文件,分别指代保存被字符集编码的文本和按照标准格式保存的非被字符集编码过的文件。
文本文件:这类文件以文本的ASCII码形式存储再计算机中,它是以行为基本结构的一种信息组织和存储方式。
二进制文件:这类文件是以文本的二进制形式存储在计算机中,用户一般不能直接读懂它们,只有通过相应的软件才能将其执行出来,二进制文件一般是可执行文件,图形,图像,声音等等。
总结:
大家也都知道,计算机的存储在物理上是二进制的,所以文本文件与二进制文件的区别并不是在物理上的,而是逻辑上的,这两者其实只在编码上有所差异。简单来说,文本文件是基于字符编码的文件,常见的编码有ASCII编码,UNICODE编码等等。二进制文件是基于值编码的文件,你可以根据具体的场景,指定某个值是什么意思(这样的一个过程,可以看作是自定义编码)。
二、Java中操作文件
前言:Java作为一个跨平台的语言,为了统一代码,就在JVM中把不同的操作系统的操作文件的API进行了封装,Java就可以使用Java中的库代码来操作文件了。
Java 中通过 java.io.File (这里的i表示input 输入,output表示输出)类来对一个文件(包括目录)进行抽象的描述。注意,有 File 对象,并不代表真实存在该文件。
下面我们先来看看 File 类中的常见属性、构造方法和方法:
属性
修饰符及类型 | 属性 | 说明 |
static String | pathSeparator | 依赖于系统的路径分隔符,String 类型的表示 |
static char | pathSeparator | 依赖于系统的路径分隔符,char 类型的表示 |
构造方法
签名 | 说明 |
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() | 判断用户是否对文件有可写权限 |
代码示例:
案例1
import java.io.File;
import java.io.IOException;
public class demo1 {
public static void main(String[] args) throws IOException {
File f = new File("D:/test.txt");
System.out.println(f.getParent());
System.out.println(f.getName());
System.out.println(f.getPath());
System.out.println(f.getAbsolutePath());
System.out.println(f.getCanonicalPath());
}
}
运行结果:
当传入File这个类中是绝对路径时,我们可以发现 好像后三个 路径的方法 得到的结果是相同的。
但是只要我们稍微一修改代码(改成一个相对路径)就能看出他们的区别:
小补充一手:
如果是在idea上运行程序,基准路径就是项目所在目录。
解释:通过路径字符串创建文件对象,创建文件对象时不会自动创建文件。
案例2
运行结果:
案例3
运行之后,发现./test.txt 被删除了。
案例4
mkdirs() 是可以:创建多层目录。
案例5
修改目录
案例6
list的应用
总结:上述的文件操作,主要都是在操作”文件系统“。(新增文件,删除文件,列出文件目录,重命名,获取路径)。
而不是针对文件内容的操作,而针对文件内容的操作,有读文件,写文件等。
三、文件的读写——数据流
在Java的标准库中,就在流的概念上,提供了一组类,用于完成文件的读写操作。
它们分别为字节流(以字节为基本单位)和字符流(以字符为基本单位),前者一般适用于二进制文件,后者适用为文本文件。
字节流大致分为InputStream,OutputStream。
字符流大致分为 Reader,Writer。
以上都为抽象类,实际运用过程需要作为父类被继承使用。
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() | 关闭字节流 |
InputStream 只是一个抽象类,要使用还需要具体的实现类。关于 InputStream 的实现类有很多,基本可以认为不同的输入设备都可以对应一个 InputStream 类,我们现在只关心从文件中读取,所以使用 FileInputStream。
案例1——字节流读文件
读取文件内容,需要在操作前在目录中创建一个bbb.txt文件
public class demo7 {
public static void main(String[] args) throws IOException {
//打开文件
InputStream inputStream = new FileInputStream("./bbb.txt");
while (true) {
int a = inputStream.read();
//因为读完了返回-1,所以这里判断,如果是-1则跳出循环
if (a == -1) {
break;
}
System.out.println(a);
}
//注意这里一定要进行文件的关闭,否则后续太多未关闭可能造成服务器宕机等。
inputStream.close();
}
}
运行结果:
这里打印的数字,与bbb.txt里面的字符一一对应。(因为此处是英文字符,每个英文字符对应一个字节,用ASCII码值表示)。
案例2——字节流写文件
OutputStream 概述
修饰 符及 返回 值类 型 | 方法签名 | 说明 |
void | write(int b) | 写入要给字节的数据 |
void | write(byte[] b) | 将 b 这个字符数组中的数据全部写入 os 中 |
int | write(byte[] b, int off, int len) | 将 b 这个字符数组中从 off 开始的数据写入 os 中,一共写 len 个 |
void | close() | 关闭字节流 |
void | flush() | 重要:我们知道 I/O 的速度是很慢的,所以,大多的 OutputStream 为 了减少设备操作的次数,在写数据的时候都会将数据先暂时写入内存的 一个指定区域里,直到该区域满了或者其他指定条件时才真正将数据写 入设备中,这个区域一般称为缓冲区。但造成一个结果,就是我们写的 数据,很可能会遗留一部分在缓冲区中。需要在最后或者合适的位置, 调用 flush(刷新)操作,将数据刷到设备中 |
对文件进行写操作。OutputStream 同样只是一个抽象类,要使用还需要具体的实现类。我们现在还是只关心写入文件中,所以使用 FileOutputStream。
可以观察到,运行结束后,原先在bbb.txt中存储的hello world 被替换为97(a),98(b),99(c)。这是因为在使用OutputStream进行写文件操作时,只要打开文件成功,就会将文件原有的数据清空。
案例3——字符流读文件
public class demo2 {
public static void main(String[] args) throws IOException {
//使用字符流读文件
Reader reader = new FileReader("./bbb.txt");
while(true) {
int ret = reader.read();
if (ret == -1) {
break;
}
char ch = (char)ret;
System.out.println(ch);
}
reader.close();
}
}
运行结果;
针对文本文件,使用字符流的时候,还可以使用Scanner来进行读取操作
案例4——字符流写文件
注:这里可能会出现程序运行之后,文件并没有被写入数据的情况,针对这种情况可以在写入操作后,加一行:writer.flush()来刷新缓冲区。
上述代码有一定不合理的地方,因为如果close代码的前面抛出异常,那么close就执行不到,可能就会引发资源泄露等问题。
因此作出如下优化:
public class demo5 {
// public static void main(String[] args) throws IOException {
// //针对写文本文件来说,可以使用PrintWriter来简化代码
// OutputStream outputStream = null;
// try {
// outputStream = new FileOutputStream("./bbb.txt");
// PrintWriter writer = new PrintWriter(outputStream);
// writer.println();
// writer.println(2);
// writer.printf("a = %d",10);
// }finally {
// outputStream.close();
// }
// }
public static void main(String[] args) throws IOException {
//针对写文本文件来说,可以使用PrintWriter来简化代码
//利用try with resources 把关闭的对象写到try()里,当try结束,就会自动的调用
//到对应对象的close方法,而且支持一个()放多个对象,多个对象的创建之间的使用,使用;分割就行
try(OutputStream outputStream = new FileOutputStream("./bbb.txt")) {
PrintWriter writer = new PrintWriter(outputStream);
writer.println();
writer.println(2);
writer.printf("a = %d",10);
}
}
}
案例5——扫描指定文件(初级)
要求:扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要删除该文件
1.让用户输入必要的信息
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要扫描的路径:");
File rootDir = new File(scanner.next());
if (!rootDir.isDirectory()) {
System.out.println("你输入的目录不存在");
return;
}
System.out.println("请输入要搜索的关键词:");
String toDelete = scanner.next();
2. 遍历目录,需要借助一个核心方法, listFiles()——能够把当前目录里的文件和子目录列举出来,但是有个弊端,无法把子目录里的子目录列举出来。
比如;
在D盘这个分支下,有目录及文件,这个目录下可能还有目录,如果单纯只用listFile()是不行的。
解决方案就是:
遍历listFile()的结果,判断是目录还是文件,如果是文件;直接查看文件名是否包含需要查找的词,如果是目录,递归的调用listFile().
public class demo6 {
public static void main(String[] args) throws IOException {
//1.让用户输入必要的信息
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要扫描的路径:");
File rootDir = new File(scanner.next());
if (!rootDir.isDirectory()) {
System.out.println("你输入的目录不存在");
return;
}
System.out.println("请输入要搜索的关键词:");
String toDelete = scanner.next();
//2,遍历目录,需要借助一个核心方法,
//listFiles()——能够把当前目录里的文件和子目录列举出来
//但是有个弊端,无法把子目录里的子目录列举出来。
scanDir(rootDir,toDelete);
}
private static void scanDir(File rootDir,String toDelete) throws IOException {
System.out.println("当前访问:"+rootDir.getCanonicalPath());
File[] files = rootDir.listFiles();
if (files == null ) {
//说明 rootDir是一个空的目录
return;
}
for (File f: files) {
if (f.isDirectory()) {
//递归的调用
scanDir(f,toDelete);
}else {
//不是目录,是文件的话,判断文件是否符合要求。
checkDelete(f,toDelete);
}
}
}
private static void checkDelete(File f, String toDelete) throws IOException {
if (f.getName().contains(toDelete)) {
System.out.println("该单词"+toDelete+"被"+f.getCanonicalPath()+"是否要删除?(Y/N)");
Scanner scanner = new Scanner(System.in);
String choice = scanner.next();
if (choice.equals("Y") || choice.equals("y")) {
f.delete();
}
//如果不是则不删除。
}
}
}
案例6——查找文件(进阶)
刚刚上面的只查询了文件名是否包含要查找的信息,这个进阶版本是查询文件里面内容是不是包含需要查询的信息。
public class demo8 {
public static void main(String[] args) throws IOException {
//1.输入路径和要插入的关键词
Scanner scanner = new Scanner(System.in);
System.out.println("请输入要扫描的路径");
File rootDir = new File(scanner.next());
System.out.println("请输入要查询的词: ");
String toFind = scanner.next();
//2.递归的扫描目录
scanDir(rootDir,toFind);
}
private static void scanDir(File rootDir, String toFind) throws IOException {
File[] files = rootDir.listFiles();
if (files == null) {
return;
}
for (File f: files) {
if (f.isDirectory()) {
scanDir(f,toFind);
} else {
checkFile(f,toFind);
}
}
}
private static void checkFile(File f, String toFind) throws IOException {
//1,先检查文件名
if (f.getName().contains(toFind)) {
System.out.println(f.getCanonicalPath() + "文件名中包含" + toFind);
}
//2.检查文件内容
try(InputStream inputStream = new FileInputStream(f)) {
StringBuilder stringBuilder = new StringBuilder();
Scanner scanner = new Scanner(inputStream);
//需要按行读取
while (scanner.hasNextLine()) {
stringBuilder.append(scanner.nextLine()+"\n");
}
if (stringBuilder.indexOf(toFind) > -1) {
System.out.println(f.getCanonicalPath() + "文件内容包含" + toFind);
}
}
}
}
案例7——文件的复制
public class demo7 {
public static void main(String[] args) {
//文件的复制
//1.先输入要复制的文件以及把这个文件复制到哪去
//2.打开源文件,按照字节读取内容,再依次写入目标文件中
Scanner scanner = new Scanner(System.in);
System.out.println("请输入源文件");
//因为要判断文件是否存在,所以用File
File srcFile = new File(scanner.next());
System.out.println("请输入目标文件");
File destFile = new File(scanner.next());
if (!srcFile.isFile()) {
System.out.println("输入的源文件有误");
return;
}
if (!destFile.getParentFile().isDirectory()) {
System.out.println("输入的目标文件有误");
return;
}
//打开源文件
try(InputStream inputStream = new FileInputStream(srcFile);
OutputStream outputStream = new FileOutputStream(destFile)) {
while (true) {
int ret = inputStream.read();
if (ret == -1 ) {
break;
}
outputStream.write(ret);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}