JavaEE|文件操作和IO

文件

  1. 狭义的文件 -- 保存在硬盘上的文件.
  2. 广义的文件 -- 操作系统进行资源管理的一种机制,很多的软件/硬件资源抽象成"文件"来进行表示

树型结构组织和目录

按照层级结构进行组织专门用来存放管理信息的特殊文件---目录,也就是我们常说的文件夹。

硬件部分

|--------|---------------|------|----------------------------------|---------------------|
| | 存储空间 | 访问速度 | 成本 | 持久化 |
| 硬盘 | 很大(几个TB) | 很慢 | 便宜,1TB固态硬盘,300-400左右,机械硬盘更便宜 | 能够持久化存储,断电之后数据任然在.. |
| 内存 | 更小(16GB 32GB) | 快 | 贵,16G内存DDR5比较新的内存价格400-500左右 | 不能持久化 |
| cpu存储器 | 非常小(不到1KB) | 非常快 | cpu 寄存器不单卖-- 算整个 cpu 的价格,就更贵到天际了 | 不能持久化 |

机器硬盘

速度慢受限于内部的结构

磁头,读写数据

磁盘,存储数据(处于"真空"环境下),高速旋转7200/min

路径

路径,定位到文件一系列的过程

计算机中的目录套目录构成了树形结构

这不同于二叉树的结构,而是N叉树的形式

文件路径

从树根开始到最终的文件中间都需要经过哪些目录,把这些目录记录下来就够成 "路径",一般使用

"/" 来分割 路径中的多级目录

/和\

/时斜杠,\是反斜杠

在 主流操作系统中,都是使用"/"来分割的,但是 Windows 是例外,Windows / 和 \ 都支持.

(但是 Windows 默认使用"\")

我们写代码时,涉及到路径建议写成"/",对于"\"在字符串中就需要转义的

以上路径都是从盘符(根节点出发的)开始,逐级表示出来的,称为绝对路径

相对路径

相对路径是根据基准路径出发

谈到相对路径要明确基准路径,如果代码中写一个"相对路径",那么基准路径是谁?

工作路径是谁并不确定,取决于程序的运行方式

1.在IDEA中直接运行,基准路径就是项目路径

C:\code\java,甚至可以通过代码中的一些api修改基准目录

2.打一个jar包,单独运行jar包

当前在哪个目录下执行运行命令(java-jar jar包名)

基准目录就是哪个目录

文件的种类

从开发的角度把文件分成两类(大前提,所有的文件都是二进制的,有一些文件是特殊的,二进制

数据刚好能构成字符)

1.文本文件

2.二进制文件

总结

  1. 文件存储在硬盘上的
  2. 目录也是文件. 操作系统通过树形结构组织目录和文件的
  3. 通过路径识别定位到具体的文件 (相对路径 绝对路径)
  4. 文件分为文本和二进制.

Java 中操作文件

Java 标准库提供了一系列的类操作文件

  1. 文件系统操作 创建文件, 删除文件, 重命名, 创建目录.....
  2. 文件内容操作 针对一个文件的内容进行读和写了.

File用于操作文件系统

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

这里的参数String文件的路径使用绝对路径/相对路径均可

|-----------|---------------------|--------------------------------------------|
| 修饰符及返回值类型 | 方法签名 | 说明 |
| 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 运行结束时 才会进行 |
| boolean | mkdir() | 创建 File 对象代表的目录 |
| boolean | mkdirs() | 创建 File 对象代表的目录,如果必 要,会创建中间目录 |
| boolean | renameTo(File dest) | 进行文件改名,也可以视为我们平 时的剪切、粘贴操作 |
| boolean | canRead() | 判断用户是否对文件有可读权限 |
| boolean | canWrite() | 判断用户是否对文件有可写权限 |

java 复制代码
 public static void main(String[] args) throws IOException {
        File file=new File("./text.txt");
        System.out.println(file.getName());
        System.out.println(file.getParent());
        System.out.println(file.getPath());
        System.out.println(file.getAbsolutePath());
        System.out.println(file.getCanonicalPath());
    }

当此处的new file()中转入的构造方法是绝对路径时,此处getPath就是得到绝对路径

项目目录是:C:\code\java\file

getCanonicalPath()

getCanonicalPath(),绝对路径简化的版本,把路径中间的.或者..给干掉

java 复制代码
public static void main(String[] args) throws IOException {
        File file=new File("./text.txt");
        file.createNewFile();
        System.out.println(file.exists());
        System.out.println(file.isDirectory());
        System.out.println(file.isFile());
        
    }

当指定路径不存在对应的文件时,createNewFile()就会创建一个新的文件,IDEA右边也会出现

test.txt的文件

对于createNewFile()这样创建文件的操作也不一定会成功,出现以下几种情况会出现异常

  1. 硬盘满了
  2. 没有权限
  3. 硬盘坏了
java 复制代码
 public static void main(String[] args) {
        File file=new File("./text.txt");
        boolean result=file.delete();
        System.out.println(result);
//        file.deleteOnExit();
    }

delete与deleteOnExit

delete在进程进行时直接删除文件,而deleteOnExit是在进程退出时删除文件

java 复制代码
public static void main(String[] args) {
        File file=new File("c:/");
        String[] str= file.list();
        System.out.println(Arrays.toString(str));
        System.out.println();
        File[] files=file.listFiles();
        System.out.println(Arrays.toString(files));
    }

list和listFile

list只是列出当前目录里面的子元素,无法列出子目录中的内容(孙子元素)

而对于listFiles得到的是File对象包含更多的操作,list得到的是文件名(String)

mkdir和mkdirs

mkdir无法创建多级目录,只能创建一级

mkdir创建目录

mkdirs创建多级目录

renameTo是重命名,从操作系统角度来看,重命名和移动的操作本质是一样的,这个移动速度通

常非常快

复制文件的复杂度是O(N),文件/目录里所有的数据,遍历,再写入写的文件/目录,重命名还能起

到 "移动" 的作用

对于canRead和canWrite分别表示文件的权限属性和用户的权限级别

文件内容操作

读写文件

Java中针对文件内容的操作,主要是通过一组"流对象"来实现的

数据流

流的对象有很多种,针对这几十种流,可以分成两个大类

1.字节流

字节流读写文件以字节为单位,是针对二进制文件使用的

InputStream---输入,从文件读数据

java 复制代码
 public static void main(String[] args) throws IOException {
        InputStream inputStream=null;
        try {
            inputStream=new FileInputStream("./test.txt");
        }finally {
            inputStream.close();
        }
    }
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() | 关闭字节流 |

FileInputStream

InputStream只是一个抽象类,要使用还需要具体的实现类。用FileInputStream实现InputStream

括号里填写的参数可以填写文件的路径(绝对/相对),也可以填写File对象

InputStream流对象体系,不仅仅给文件提供操作

这里创建对象的操作一旦成功,就相当于"打开文件"

先打开然后才能读写,这是操作系统定义的流程

每次程序打开一个文件就会在文件描述符表中(固定长度的)申请一个表项,如果光打开不关闭就会

使这里的文件描述表表项耗尽,后续再打开就会出现打开失败的情况

对于这种try代码块能有效防止忘记释放文件资源,当只要出了try代码块就会自动调用close

对于这种自动释放文件资源的情况,要求这个类需要实现Closable接口

此时read是一次读一个字节

当直接去运行这段代码时,会出现文件找不到的操作

这是因为找不到对应文件的地址,此时我们需要在file目录下创建test.txt文件

此时才能运行成功

此时是按照字节读取,一个一个字节取出来分别打印,每个字节范围0-255

10进制是正常,8进制是0开头,十六进制是0x开头

当直接输出data时,是以utf8的形式来输出

当以方式二来进行字节读取时,对于byte[] data = new byte[3];

一次读多个字节, 数组的长度, 自行定义,也是读操作, 就会尽可能把字节数组给填满.填不满的话,

能填几个就是几个,此处的 n 就表示实际读了几个字节.

当将数组大小改为3时,会分批进行读取

对于int n=inputStream.read(data);

这是输出型参数,read返回时会同时返回长度和内容数组

OutputStream---输出,往文件写数据

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() | 冲涮缓冲区 |

97在ASCII中就是"a"

对于OutputStream来说,默认情况下会尝试创建不存在的文件,同时也会清除上次的文件内容,

当打开文件的一瞬间,上次的文件就清空了

此处可以追加写的模式避免内容被清空

2.字符流

字符流读写文件是以字符为单位,是针对文本文件使用

Read

Reader---输入,从文件读数据

第一个read一次读一个字符

第二个read时都字符数组,尽可能将字符数组填满,返回实际读到的字符个数

第三个read中的CharBuffer相当于对char[]进行了封装

第四个read是进行了偏移

Writer

Writer---输出,往文件写数据

总结:

如何正确使用流对象?

1.流对象的使用流程

先打开,再读写,最后关闭

2.应该是用哪个流对象

先区分文件还是文本,二进制再区分读还是写

缓冲区

缓冲区通常就是一段内存空间,用来提高程序的效率,直接读写硬盘比较低效的 (硬盘速度慢)因此

有的时候就希望减少读写文件次数。

在进行 IO 操作的时候就希望能够使用缓冲区,把要写的数据先放到缓冲区里攒一波再一起写或者

读的时候, 也不是一个一个的读, 一次读一批数据, 到缓冲区中, 再慢慢解析

当前 IO 流对象, read 和 write 就属于直接读写文件,要想提高效率有以下两种办法:

  1. 写代码的时候, 手动创建缓冲区 (byte[]), 手动减少 read write 次数.
  2. 使用标准库提供了 "缓冲区流" BufferedStream 把 InputStream 之类的对象套上一层.

这种就是构建了一个writer的缓冲区

文件IO的练习

1.扫描指定目录,并找到名称中包含指定字符的所有普通文件(不包含目录),并且后续询问用户是

否要删除文件

java 复制代码
 public static void main(String[] args) throws IOException {
        Scanner scanner=new Scanner(System.in);
        System.out.println("请输入要搜索的目录");
        String rootDir=scanner.next();
        File files=new File(rootDir);
        if (!files.isDirectory()){
            System.out.println("输入并不是目录");
            return;
        }
        System.out.println("请输入要删除含有指定字符");
        String keyword=scanner.next();
        scanfile(files,keyword);
    }

    private static void scanfile(File rootfile, String keyword) throws IOException {
        File[] files=rootfile.listFiles();
        if (files==null){
            return;
        }
        for (File file:files) {
            if (file.isFile()){
                dealfile(file,keyword);
            }else {
                scanfile(file,keyword);
            }
        }
    }

    private static void dealfile(File file, String keyword) throws IOException{
        if (file.getName().contains(keyword)){
            System.out.println("找到指定字符串在目录或者文件:"+file.getAbsolutePath()+"是否要删除文件该文件");
            Scanner scan=new Scanner(System.in);
            String str=scan.next();
            if (str.equalsIgnoreCase("y")){
                file.delete();
                System.out.println("文件已删除");
            }
        }
    }

2.进行普通文件复制(复制就是把文件里的每个字节都读出来写入到另一个文件中)

java 复制代码
public static void main(String[] args) throws IOException {
        Scanner scan=new Scanner(System.in);
        System.out.println("请输入源文件路径:");
        String srcPath=scan.next();
        System.out.println("请输入目标文件路径:");
        String destPath=scan.next();
        File srcfile=new File(srcPath);
        if (!srcfile.isFile()){
            System.out.println("源文件不存在或者目录不存在");
            return;
        }
        File dextfile=new File(destPath);
        if (!dextfile.getParentFile().isDirectory()){
            System.out.println("指定路径不存在");
            return;
        }
        try (InputStream inputStream=new FileInputStream(srcPath);
             OutputStream outputStream=new FileOutputStream(destPath)){
            while (true){
                byte[] buf=new byte[1024];
                int n=inputStream.read(buf);
                if (n==-1){
                    break;
                }
                outputStream.write(buf, 0,n);
            }
        }
    }

3.扫描指定目录,并且找到名称后者内容包含指定字符的所有文件(不包含目录)

java 复制代码
 public static void main(String[] args) throws IOException{
        Scanner scan=new Scanner(System.in);
        System.out.println("请输入要搜索的路径:");
        String rootPath=scan.next();
        File rootFile=new File(rootPath);
        if (!rootFile.isDirectory()){
            System.out.println("输入的路径不是目录");
            return;
        }
        System.out.println("请输入要搜索的关键字");
        String keyword=scan.next();
        scanDir(rootFile,keyword);
    }

    private static void scanDir(File rootFile, String keyword) throws IOException {
        File[] files=rootFile.listFiles();
        if (files==null){
            return;
        }
        for (File file:files) {
            if (file.isFile()){
                dealFile(file,keyword);
            }else {
                scanDir(file,keyword);
            }
        }
    }

    private static void dealFile(File file, String keyword) throws IOException {
        if (file.getName().contains(keyword)){
            System.out.println("查找到关键字在指定路径"+file.getAbsolutePath());
            return;
        }
        StringBuffer stringBuffer=new StringBuffer();
        try (Reader read=new FileReader(file)){
            while(true){
                char[] buf=new char[1024];
                int n=read.read(buf);
                if (n == -1) {
                    break;
                }
                stringBuffer.append(buf,0,n);
            }
            if (stringBuffer.indexOf(keyword)>=0){
                System.out.println("查找到关键字在指定文件"+file.getAbsolutePath());
            }
            return;
        }
    }
相关推荐
脉动数据行情1 小时前
Python 实现融通金行情数据对接(实时推送 + K 线 + 产品列表)
开发语言·python
DavidSoCool1 小时前
Spring AI Alibaba ReactAgent 调用Tool 实现多轮对话
java·人工智能·spring·多轮对话·reactagent
skywalk81632 小时前
Trae生成的中文编程语言关键字(如“定“、“函“、“印“等)需要和标识符之间用 空格 隔开,以确保正确识别
服务器·开发语言·编程
神所夸赞的夏天2 小时前
如何获取多层json数据,存成dictionary,并取最大最小值
java·前端·json
红色的小鳄鱼2 小时前
前端面试js手写
开发语言·前端·javascript
9号达人2 小时前
为什么你应该在 MQ 里用多个消费者,而不是一个
java·后端·架构
焦糖玛奇朵婷2 小时前
健身房预约小程序开发、设计
java·大数据·服务器·前端·小程序
海盗12342 小时前
C#中的IEqualityComparer<T>使用
开发语言·c#
小新同学^O^2 小时前
简单学习 --> TCP协议
java·网络·tcp