JavaEE初阶 - 文件/IO

一、认识文件

我们先来认识狭义上的文件(file)。针对硬盘这种持久化存储的I/O设备,当我们想要进行数据保存时,往往不是保存成一个整体,而是独立成一个个的单位进行保存,这个独立的单位就被抽象成文件的概念,就类似办公桌上的一份份真实的文件一般。
JavaEE初阶 - 文件/IO_第1张图片
文件除了有数据内容之外,还有一部分信息,例如文件名、文件类型、文件大小等并不作为文件的数据
而存在,我们把这部分信息可以视为文件的元信息
JavaEE初阶 - 文件/IO_第2张图片

平时说的文件一般都是指存储在硬盘上的普通文件。
形如:txt文本、jpg图片、mp4视频、rar压缩包等这些文件都可以认为是普通文件。
它们都是在硬盘上存储的。
JavaEE初阶 - 文件/IO_第3张图片
但是站在计算机专业术语的角度上来讲:
在计算机中文件可能是一个广义的概念,就不只是包含普通文件,还可以包含目录(把目录称为目录文件),目录俗称文件夹。

JavaEE初阶 - 文件/IO_第4张图片

在操作系统中,还会使用文件来描述一些其它的硬件设备或者软件资源。
其实站在操作系统的角度上来看,所谓的文件,其实是一个非常广义的概念。
不仅仅指的是我们日常生活中所说的这些普通文件,也包含目录,还包含一些软硬件资源设备。

后面讲网络编程的时候,会讲到一个很重要的设备“网卡”。
网卡,是一个硬件设备。但是在操作系统中就把 网卡 这样的硬件设备也给抽象成了一个文件。
这样的操作,会给我们进行网络编程带来很大便利。
我们要想通过网卡来收数据,直接按照 读文件代码 去写 就可以了。
想通过网卡来发数据,那就按照 写文件代码 去写就行了
这样做带来的好处:简化开发。
通过这种文件的操作,来完成操作网卡的过程。

除此之外,还有显示器,键盘,这都是硬件设备吧。
操作系统也是把这些设备视为文件。
想从键盘读取数据,其实也是通过类似读文件的方式来进行的。
想往显示器上写一个数据,也是通过类似写文件的方式来进行的。

因此,操作系统谈到的文件,其实是一个更加广泛的概念。它会涉及到各个方面的情况。

当前我们需要讨论的文件,主要还是针对普通文件来讨论的。
后面去学习一些其他的硬件设备对应的文件,其实也是通过类似的代码来实现操作。
因此,我们虽然讨论的是针对普通文件为例来去演示,但实际上这里的一些代码,和后需用到的一些代码,尤其是网络编程的代码,是非常具有共性的。

二、普通文件 与 机械硬盘

普通文件是保存在硬盘上的。【一般讨论的都是机械硬盘】
JavaEE初阶 - 文件/IO_第5张图片

机械硬盘一旦通电,里面的盘片就会高速运转,
例如:我们使用的是 7200转/分钟 的家用机械硬盘,那么,每秒就是 120转/秒。
磁头就会在盘片上找到对应的数据。
因为盘片上是有一圈圈 磁道的。
然后呢,我们就把数据 去按照这一圈一圈的方式来往上去写。
因为这里面的盘片,其实是一个非常精密的设备。
这里面是可以存储很多数据的。
像这么小的硬盘(大概只有现在手机的一半大小),就可以存储上T的数据。
但是 存储空间的大小,受限于机械硬盘的硬件结构。
盘片转速越高,读写速度也就越快。
但是因为工艺的限制,盘片的转速也不可能无限高。
(现在最好的机械硬盘大概是每分钟 1 万 多 转)
另外,机械硬盘的读写速度,已经有10年 停滞不前了。【技术上无法精进】
现在的机械硬盘都是往大容量的方向发展。
前面的博文也说到过,内存的读写速度 比 现在的硬盘读写速度 快很多!!!
快 3-4 个数量级,大概是上万倍。
后面为了解决这个问题,就有了固态硬盘(SSD)。
固态硬盘的硬件结构 和 机械硬盘 截然不同。
固态硬盘就是一个大号的U盘。
JavaEE初阶 - 文件/IO_第6张图片
U盘:里面可以存储数据,但是又不需要盘片这些东西。主要是 flash 芯片(闪存),通过这个东西来进行存储数据,而且能获取到更高速率的读写。
所以,固态硬盘的读写速度要比机械硬盘高很多。【高好几倍】
现在最好的固态硬盘,读写速度已经接近十几年前的内存水平了。

本篇博文主要讨论的是 以机械硬盘为主。 因为当前企业中使用的服务器还是以机械硬盘为主。

因为 SSD 的成本 是 机械硬盘的好几倍。(4-5倍)

三、文件的分类

站在程序员的角度,主要把文件分成两类:
1、文本文件:里面存储的是字符
2、二进制文件:里面存储的是字节
针对这两种文件,在编程的时候会存在差异。
JavaEE初阶 - 文件/IO_第7张图片

3.1、如何判断是文本文件还是二进制文件

我们判定一个文件是文本 ,还是 二进制文件,有一个简单直接的方法。
就是 把一个文件直接用记事本打开,如果是乱码就是 二进制文件,如果不是乱码就是文本。
JavaEE初阶 - 文件/IO_第8张图片

四、文件的目录结构

随着文件越来越多,对文件的系统管理也被提上了日程,如何进行文件的组织呢,一种合乎自然的想法出现了,就是按照层级结构进行组织 —— 也就是我们数据结构中学习过的树形结构。这样,一种专门用来存放管理信息的特殊文件诞生了,也就是我们平时所谓文件夹(folder)或者目录(directory)的概念
JavaEE初阶 - 文件/IO_第9张图片
JavaEE初阶 - 文件/IO_第10张图片
也就是说普通文件,一定是树的叶子结点。
目录文件,就可以看作是一个子树,也就是非叶子节点。

五、文件路径(Path)

在操作系统中,就通过“路径” 这样的概念,来描述一个具体 文件/目录 的位置。

5.1、绝对路径

以盘符开头的。【以 C、D、E、F等等,盘名开头的】
JavaEE初阶 - 文件/IO_第11张图片

5.2、相对路径

以 . 或者 … 开头的文件路径。
其中 . 表示当前路径,  … 表示当前路径的父目录(上级路径)。

谈到相对路径,必须要有一个基准目录。
对于路径就是从基准目录出发,按照一个什么样的路径找到的对应文件。
JavaEE初阶 - 文件/IO_第12张图片
注意!即使是定位到同一个文件,如果基准路径不同,此时的相对路径也就不同
绝对路径 和 相对路径 的概念非常重要!!!!
虽然面试不考,但是我们在后面的编程中,需要频繁的和这些路径打交道。、

六、java 中操作文件

Java中操作文件,主要是包含两类操作
1、文件系统相关的操作
2、文件内容相关的操作

6.1、文件系统相关的操作

这个是C语言中没有的操作。C语言标准库不支持这个操作。
文件系统相关的操作:指的是通过“文件资源管理器” 能够完成的一些功能
JavaEE初阶 - 文件/IO_第13张图片
它具有的功能
1、列出目录中有哪些文件
2、创建文件(直接右键点击菜单创建,就可以创建了)
3、创建 目录/文件夹
4、删除文件
5、重命名文件

这些都是文件资源管理器所能够完成的功能。
简单来说 文件资源管理器 能做什么,我们的文件系统相关操作的代码就能做什么。

在Java中提供了一个 File 类,通过这个类来完成上述操作。
首先,这个File类就描述了一个文件/目录。
基于这个对象就可以实现上面的功能。

6.2、File类中的属性

修饰符及类型 属性 说明
static String pathSeparator 依赖于系统的路径分隔符,String 类型的表示
static char pathSeparator 依赖于系统的路径分隔符,char 类型的表示

6.3、File类中的构造方法

签名 说明
File(File parent, Stringchild) 根据父目录 + 孩子文件路径,创建一个新的 File 实例
File(String pathname) 根据文件路径创建一个新的 File 实例,路径可以是绝对路径或者相对路径
File(String parent, Stringchild) 根据父目录 + 孩子文件路径,创建一个新的 File 实例,父目录用路径表示

File 的构造方法,能够传入一个路径,来指定一个文件。
这个路径可以是绝对路径,也可以是相对路径。

6.4、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(Filedest) 进行文件改名,也可以视为我们平时的剪切、粘贴操作
boolean canRead() 判断用户是否对文件有可读权限
boolean canWrite() 判断用户是否对文件有可写权限

6.5、代码示例

6.5.1、File类的构造方法

通过绝对路径和相对路径来指向一个文件
JavaEE初阶 - 文件/IO_第14张图片

6.5.2、File类的普通方法

目前上述操作,都是针对文件系统的操作。

主要还是 增删改查 的一些操作。
但是呢,这些操作只是作用于文件表面,并没有对文件的内容做出实质性的改变。

getParent、getName、getPath、getAbsolutePath、getCanonicalPath
public class Dome1 {
    public static void main(String[] args) throws IOException {
        File f1 = new File("D:test.txt");

        System.out.println(f1.getPath());//返回 File 对象的文件路径
        System.out.println(f1.getParent());//返回 File 对象的父目录文件路径
        System.out.println(f1.getName());//返回 FIle 对象的纯文件名称
        System.out.println(f1.getAbsolutePath());//返回 File 对象的绝对路径
        System.out.println(f1.getCanonicalPath());//返回 File 对象的修饰过的绝对路径
        System.out.println("--------------------------");
        File f2 = new File("./test.txt");
        System.out.println(f2.getPath());//返回 File 对象的文件路径
        System.out.println(f2.getParent());//返回 File 对象的父目录文件路径
        System.out.println(f2.getName());//返回 FIle 对象的纯文件名称
        System.out.println(f2.getAbsolutePath());//返回 File 对象的绝对路径
        System.out.println(f2.getCanonicalPath());//返回 File 对象的修饰过的绝对路径
    }
}

exists、isDirectory、isFile
public class Dome2 {
    public static void main(String[] args) {
        File f1 = new File("d:/test.txt");
        System.out.println(f1.exists());//判断 File 对象描述的文件是否真实存在
        System.out.println(f1.isDirectory());//判断 File 对象代表的文件是否是一个目录
        System.out.println(f1.isFile());//判断 File 对象代表的文件是否是一个普通文件
    }
}

JavaEE初阶 - 文件/IO_第15张图片

createNewFile
public static void main(String[] args) throws IOException {
        File f1 = new File("d:/test.txt");
        //根据 File 对象,自动创建一个空文件。成功创建后返回 true
        System.out.println(f1.createNewFile());
    }

JavaEE初阶 - 文件/IO_第16张图片

delete
public static void main(String[] args) {
        File f1 = new File("d:/test.txt");
        System.out.println(f1.delete());//根据 File 对象,删除该文件。成功删除后返回 true
    }

JavaEE初阶 - 文件/IO_第17张图片

mkdir、mkdirs
public static void main(String[] args) {
        //在当前项目底下创建一个 aaa 目录
        File file = new File("./aaa");
        System.out.println(file.isDirectory());
        file.mkdir();// mkdir 方法 只能创建一级目录
        System.out.println(file.isDirectory());
    }

JavaEE初阶 - 文件/IO_第18张图片
与mkdir方法,相似的方法还有一个 mkdirs 方法,
意思创建多级目录。

public static void main(String[] args) {
        //在当前项目底下创建一个 aaa 目录
        File file = new File("./aaa/bbb/ccc");
        System.out.println(file.isDirectory());
        file.mkdirs();
        System.out.println(file.isDirectory());
    }

JavaEE初阶 - 文件/IO_第19张图片
目前上述操作,都是针对文件系统的操作。

主要还是 增删改查 的一些操作。
但是呢,这些操作只是作用于文件表面,并没有对文件的内容做出实质性的改变。

七、文件内容相关操作

1、打开文件
2、读文件
3、写文件
4、关闭文件
针对文件内容的读写,Java提供了一组类来完成上述操作
JavaEE初阶 - 文件/IO_第20张图片

7.1、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

7.1.1、FileInputStream 概述

构造方法

签名 说明
FileInputStream(File file) 利用 File 构造文件输入流
FileInputStream(String name) 利用文件路径构造文件输入流

7.1.2、针对字节的读操作

FileInputStream 一个字节一个字节的读

FileInputStream 一次读取若干个字节

JavaEE初阶 - 文件/IO_第21张图片

利用 Scanner 进行字符读取

上述例子中,我们看到了对字符类型直接使用 InputStream 进行读取是非常麻烦且困难的,所以,我
们使用一种我们之前比较熟悉的类来完成该工作,就是 Scanner 类

构造方法 说明
Scanner(InputStream is, String charset) 使用 charset 字符集进行 is 的扫描读取
// 需要先在项目目录下准备好一个 hello.txt 的文件,里面填充 "你好中国" 的内容
public class Main {
	public static void main(String[] args) throws IOException {
		try (InputStream is = new FileInputStream("hello.txt")) {
			try (Scanner scanner = new Scanner(is, "UTF-8")) {
				while (scanner.hasNext()) {
					String s = scanner.next();
					System.out.print(s);
				}
			}
		}
	}
}

7.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

7.2.1、针对字节的写操作

FileOutputStream 一次写一个字节

JavaEE初阶 - 文件/IO_第22张图片

FileOutputStream 一次写若干字节

JavaEE初阶 - 文件/IO_第23张图片

利用 PrintWriter 找到我们熟悉的方法

上述,我们其实已经完成输出工作,但总是有所不方便,我们接来下将 OutputStream 处理下,使用
PrintWriter 类来完成输出,因为
PrintWriter 类中提供了我们熟悉的 print/println/printf 方法

public static void main(String[] args) throws IOException {
        try (OutputStream os = new FileOutputStream("output.txt")) {
            try (OutputStreamWriter osWriter = new OutputStreamWriter(os, "UTF-8")) {
                try (PrintWriter writer = new PrintWriter(osWriter)) {
                writer.println("我是第一行");
                writer.print("我的第二行\r\n");
                writer.printf("%d: 我的第三行\r\n", 1 + 1);
                writer.flush();
            }
        }
    }
}

7.3、FileReader 针对字符的读操作

JavaEE初阶 - 文件/IO_第24张图片

7.4、FileWriter 针对字符的写操作

JavaEE初阶 - 文件/IO_第25张图片

八、小程序练习

8.1、扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要删除该文件

意思就是:
用户输入一个目录
再输入一个要删除的文件名
找到名称中包含指定字符的所有普通文件
询问用户是否要删除该文件
JavaEE初阶 - 文件/IO_第26张图片

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Scanner;

/**
 * 扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是否要
 * 删除该文件
 */
public class Dome1 {
    public static void main(String[] args) {
        //1、首先输入扫描的目录,以及要删除的文件
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入扫描的路径");
        String rootDirPath = scanner.next();
        System.out.println("请输入要删除的文件名");
        String toDelFileName = scanner.next();

        File rootDir = new File(rootDirPath);
        //2、判断一下要删除的文目录是否存在
        if(!rootDir.isDirectory()){
            return;
        }
        //3、搜到这里说明当前目录是存在的
        //4、写一个函数,实现删除文件
        scanDir(rootDir,toDelFileName);
    }

    //通过这个方法实现删除文件
    private static void scanDir(File rootDir, String toDelFileName) {
        //1、首先列出 rootDirPath 有哪些内容
        File[] files = rootDir.listFiles();
        if(files == null){
            // rootDir是一个空目录
            return;
        }
        //走到这里,说明 rootDir 一个空目录,我们就要进要遍历目录,然后删除文件
        for (File f : files) {
            if(f.isFile()){
                //普通文件的情况
                if(f.getName().contains(toDelFileName)) {
                    //不要求文件名字一样,只要求包含了关键字的文件都要删除
                    deleteFile(f);
                }
            }else if(f.isDirectory()){
                scanDir(f,toDelFileName);
            }
        }
    }

    private static void deleteFile(File f) {
        try {
            System.out.println("确认要删除文件吗(Y/N) + " + f.getCanonicalPath());
            Scanner scanner = new Scanner(System.in);
            String chioce = scanner.next();
            if(chioce.equals("Y") || chioce.equals("y")){
                f.delete();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

8.2、进行普通文件的复制

文件复制,其实说白了:就是将文件给拷贝一个副本。

我们需要让用户指定两个文件路径。
一个是原路径(被复制的文件)
一个是目标路径(复制之后生成的文件)
从哪里复制到哪里,从哪里来到哪来去。
我们需要描述清楚 起点 和 终点。
然后,我们就可以在这个基础上进行具体复制操作

我们要做的事很简单。
打开源路径文件,读取里面的内容,并写入到目标文件中,
JavaEE初阶 - 文件/IO_第27张图片

/**
 * 进行普通文件的复制
 */
public class Dome2 {
    public static void main(String[] args) {
        //1、首先输入两个路径
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入源路径:");
        String src = scanner.next();
        System.out.println("请输入目标路径:");
        String dest = scanner.next();

        File scrFile = new File(src);
        //2、判断一下输入的源路径是否存在
        if(!scrFile.isFile()){
            System.out.println("输入的源路径错误!");
            return;
        }

        //此处不需要检查目标路径是否存在, OutputStream 进行写操作的时候,如果文件不存在会自动创建文件
        //3、将源文件拷贝到目标路径里面
        try (InputStream inputStream = new FileInputStream(scrFile)){
            try (OutputStream outputStream = new FileOutputStream(dest)){
                //把inputStream 中的数据读出来,写入到outputStream里面
                byte[] buffer = new byte[1024];
                while (true){
                    int len = inputStream.read(buffer);
                    if(len == -1){
                        //读取完毕
                        break;
                    }
                    //进行写入数据
                    //在写入数据的时候,不能将整个buffer进行写入,毕竟 buffer 可能只有一部分数据是有效数据
                    outputStream.write(buffer,0,len);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

8.3、扫描指定目录,并找到名称或者内容中包含指定字符的所有普通文件(不包含目录)

进行文件内容的查找

1、输入一个路径
2、再输入一个要查找文件的 关键字
3、递归的遍历文件,找到看哪个文件里的内容包含了那些关键词,就把对应的文件路径打印出来。

思路和案例1一样,先递归遍历文件,针对每个文件都打开,并读取内容,再进行字符串查找即可。
JavaEE初阶 - 文件/IO_第28张图片

public class Dome3 {
    public static void main(String[] args) throws IOException {
        //1、首先输入要扫描的路径
        Scanner scanner = new Scanner(System.in);
        System.out.println("请输入要扫描的路径:");
        String rootDirPath = scanner.next();
        System.out.println("请输入要查找的关键词:");
        String word = scanner.next();

        File rootDir = new File(rootDirPath);
        if(!rootDir.isDirectory()){
            System.out.println("没有输入的路径!");
            return;
        }
        //实现一个函数,这个函数就用来删除办好关键词的文件
        scanDir(rootDir,word);
    }

    //递归遍历这个路径的所有文件内容
    private static void scanDir(File rootDir, String word) throws IOException {
        //1、首先列举出所有目录
        File[] files = rootDir.listFiles();
        if(files == null){
            //说明没有这个目录
            return;
        }
        //2、遍历每个元素,针对目录或者文件进行操作
        for (File f : files) {
            if(f.isFile()){
                //针对文件内容进行查找
                if(contansWord(f,word)){
                    System.out.println(f.getCanonicalPath());
                }
            }else if(f.isDirectory()){
                //针对目录进行递归遍历
                scanDir(f,word);
            }
        }
    }

    private static boolean contansWord(File f, String word) {
        //将文件的内容读出来,放到一个StringBuffer里面
        StringBuffer stringBuffer = new StringBuffer();
        try (Reader reader = new FileReader(f)){
            char[] buffer = new char[1024];
            while (true){
                int len = reader.read(buffer);
                if(len == -1){
                    break;
                }
                stringBuffer.append(buffer,0,len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        //indexOf 返回得是子串的下标
        return stringBuffer.indexOf(word) != -1;
    }
}

你可能感兴趣的:(javaEE初阶,java-ee)