JavaEE初阶:文件操作和IO

一.介绍文件

1.1狭义文件和广义文件

  • 狭义上的文件:存储在硬盘上的文件
  • 广义 上的文件:操作系统用来管理资源的方式。不管是软件还是硬件都抽象成文件,这样方便管理(一切皆文件)。

本章讲解狭义文件。

1.2文件分类

大体来说文件分为:"文件夹" 和**"普通文件"。**

但**"文件夹"** 是老百姓通俗叫法,真正的专业术语叫**"目录"(directory)。**

从开发角度,把文件分为二进制文件文本文件。

  1. 这两种文件很好区分,我们用记事本打开文件,发现没一点规律的就是"二进制文件"无疑了,因为二进制文件本来就不是给人看的,是给机器看的。典型的二进制文件就是图片(视频等):(出现中文等字符,是记事本尝试按字符集编码二进制文件,而导致的乱码)
  2. 使用记事本打开的文件内容是有规律的,人看的懂的就是文本文件,(Windows系统中的文件类型默认与文件后缀相匹配,在如Linux、Unix等操作系统上就没这种习惯)常见的如以.txt结尾的文件:(.json .md .xml 等)

(如.docx结尾的文件(word文件)是**富文本;**富文本指的是,文件中可以包含文字、音频、图片和各种格式信息。)(很好理解,不做过多介绍)

1.3文件路径分类

文件路径主要分为两大类:绝对路径相对路径。

操作系统中文件的创建和查找就是在一颗N叉树中进行的;目录下有N个文件(包括目录),这N个文件目录下又包含N个文件,形如下图:

绝对路径:根目录开始,经过的所有目录名组合起来就叫绝对路径,如 C:\Windows\appcompat (路径不包括"此电脑"!)。

相对路径 的路径不是以根目录为基准了,是以当前所在目录为基准,例如下图,我现在所在的位置是红圈所在的文件,现在我要选择使用旁边的绿色圈中的文件,但如果使用绝对路径从头开始查找文件就太麻烦了,直接使用相对路径--》../appcompat 就代表绿圈文件了。(.. 代表所在文件的父目录路径,. 代表目前文件路径)

二.操作文件

Java标准库提供了一些列类操作文件,这些类都是围绕下面2.1或者2.2操作。

2.1文件系统操作

File类用于文件系统操作。

2.1.1File类常见的方法和属性

1.构造函数

需要注意的是,创建了File对象并不是在指定路径下就创立了一个文件,这只是根据路径参数抽象了一个对象,想在本地真正创建文件要使用下面的createNewFile()。

|----------------------------------|------------------------|
| 方法签名 | 说明 |
| File(File parent,String child) | 根据父目录+孩子文件名,创建一个File对象 |
| File(String pathname) | 直接根据路径,创建File对象 |
| File(String parent,String child) | 根据父目录+孩子文件名,创建File对象 |

java 复制代码
// 创建文件对象
File parentDir = new File("/home/user/documents");

// 使用构造函数创建文件对象
File childFile = new File(parentDir, "example.txt");

// 或者更简洁的写法
File childFile2 = new File(new File("/home/user/documents"), "example.txt");
2.属性

|---------------|-----------|-------------------------------------------------------------------------|
| 修饰符及类型 | 属性 | 说明 |
| static String | separator | 依赖于系统的路径分隔符,String类型表示。 在Windows系统上pathSeparator是**\****,其他系统就是/** |
| static char | separator | 依赖于系统的路径分隔符,char类型表示 |

大多操作系统的文件路径使用**/(斜杠)分隔(如Linux、macOS等),但Windows中使用\**(反斜杠)进行分隔。在Windows的IDEA中使用两种都可以,就算一个字符串路径同时使用 / 和 **\**分隔路径也是可以的,这归根于Windows系统做的好,两种方式都兼容。

3.方法

下面会有方法和属性的实战。

|------------|---------------------|------------------------------------------------------------------------|
| 修饰符及返回值类型 | 方法签名 | 说明 |
| String | getParent() | 返回父目录路径 |
| 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[] | listFile() | 与上述相同,除了返回类型不同。 |
| boolean | mkdir() | 创建File对象代表的目录;如果创建File对象的构造方法中有父目录不存在,使用mkdir()创建对象代表的目录时就会失败,返回false。 |
| boolean | mkdirs() | 与mkdir()不同的是,使用mkdirs()创建File对象代表的目录时,对象的父目录不存在,就会连同父目录一起创建了。 |
| boolean | renameTo(File dest) | 进行文件改名,也可以视为平时的剪切、粘贴操作 |
| boolean | canRead() | 判断用户是否对文件有可读权限 |
| boolean | canWrite() | 判断用户是否对文件有可写权限 |

2.1.2方法和属性实战

  • 构造方法
java 复制代码
package fileCSDN;

import java.io.File;
import java.io.IOException;

public class Test1 {
    public static void main(String[] args) throws IOException {
        //创建File对象
         //File(String)
        File file1 = new File("D:\\java\\javacode-25year\\File\\111");
         //File(File,String)
        File file2 = new File(file1,"333");
         //File(String,String)
        File file3 = new File("D:\\java\\javacode-25year\\File\\111","444");
        //这个点代表这个项目的路径!(..代表项目路径的父目录)
        File file = new File("./test.txt");
    }
}
  • 属性
java 复制代码
package fileCSDN;

import java.io.File;

public class Test2 {
    public static void main(String[] args) {
        System.out.println(File.pathSeparator);
        System.out.println(File.pathSeparatorChar);
        System.out.println(File.separator);
        System.out.println(File.separatorChar);
    }
}

依次打印:

  • 方法

前面5个方法:getParent()、getName()、getPath()、getAbsolutePath()、getCanonicalPath()

java 复制代码
package fileCSDN;

import java.io.File;
import java.io.IOException;

public class Test3 {
    public static void main(String[] args) throws IOException {
        File file = new File("D:\\java\\javacode-25year\\File\\src");
        //获取父目录路径
        System.out.println("第1:"+file.getParent());
        //获取File对象(代表的)文件名
        System.out.println("第2:"+file.getName());
        //获取对象路径
        System.out.println("第3:"+file.getPath());
        //获取对象的绝对路径
        System.out.println("第4:"+file.getAbsolutePath());
        //获取对象的规范路径
        System.out.println("第5:"+file.getCanonicalPath());
    }
}

打印出:

new对象时书写路径的方式不同,会导致上面这些方法打印出来的效果不同,这里最后两个方法的返回值还竟然一模一样。看看下面这个File对象使用同样的方法返回打印出的有什么不同。

这么比较上下两个File对象使用同样5种方法返回的值截然不同,getAbsolutePath()和getCanonicalPath()的区别还是挺大的。

6~10方法实战:exists()、isDirectory()、isFile()、createNewFile()、delete()

java 复制代码
public static void main(String[] args) throws IOException, InterruptedException {
        File file = new File("./test.txt");
        //判断对象代表的文件是否真实存在,已存在返回true,否则false
        System.out.println("文件是否存在:"+file.exists());
        //创建空文件
        System.out.println("文件创立:"+file.createNewFile());
        //再次判断对象代表的文件是否真实存在
        System.out.println("文件是否存在:"+file.exists());
        //刚才创建了一个普通文件------》test.txt文件,所以isDirectory()应该返回的是false
        System.out.println("是否是目录:"+file.isDirectory());
        System.out.println("是否是普通文件:"+file.isFile());
        //主线程休眠10秒
        Thread.sleep(10000);
        //删除文件
        System.out.println("文件删除:"+file.delete());
    }

上述单线程代码中,我们想最后再删除test.txt文件,就把delete()语句放到了程序末尾,这时可以的。但有时代码较为复杂,我们又想最后再删除某个文件,就用deleteOnExit() 指定某个文件程序结束时删除文件。

12~13方法实战:list()、listFile()

java 复制代码
public static void main(String[] args) {
        //创建C盘的抽象对象
        File file = new File("C:\\");
        //返回路径C:\的子文件(目录也是文件)
        String[] list = file.list();
        System.out.println(Arrays.toString(list));

        System.out.println("=====================================================");

        //返回路径C:\的子文件(目录也是文件)
        File[] files = file.listFiles();
        System.out.println(Arrays.toString(files));
    }

IDEA打印出的文件包括本地隐藏的文件操作系统文件未隐藏的文件, 我们平常在本地最多见到隐藏与未隐藏的文件(文件管理器勾选查看隐藏文件的属性),操作系统的文件是看不到的,因此上述两个方法打印出的文件名和本地C:\子文件数目不匹配。

14、15、16方法实战:mkdir()、mkdirs()、renameTo(File dest)

java 复制代码
public static void main(String[] args) {
        //注:该项目没子目录 111
        File file1 = new File("./111/222");
        //要在项目路径的子目录111下创建一个名为222的目录
        System.out.println("mkdir目录创建:"+file1.mkdir());
        System.out.println("mkdirs目录创建:"+file1.mkdirs());
        System.out.println("===========================================");
        File file2 = new File("./1");
        System.out.println("mkdir目录创建:"+file2.mkdir());//这里换成mkdir也行
    }

renameTo(File dest)

剩下canWrite()和canRead()两个方法暂不实战。

2.2文件内容操作

文件内容操作:对一个文件进行读和写。

Java提供了一组类,表示"流",流分为"输入流"和"输出流"

Java有几十个类表示流,但常见的也就8种。学其他类时可类比这8种。

上述的几十种流,分成两大类:1.字节流 2.字符流

1.字节流 在读写文件内容时,以字节为单位,是针对二进制文件使用的。

  • InputStream 输入 从文件读取数据
  • OutputStream 输出 往文件写数据

2.字符流 在读写文件内容时,以字符为单位,是针对文本文件使用的。

  • Reader 输入 从文件读取数据
  • Writer 输出 往文件写数据

注意:读写操作是以cpu为视角看带的;应用程序中使用 InputStream进行输入,是给cpu(寄存器)输入数据,不是给硬盘输入数据(本地文件在硬盘中)。上面4个输入输出流都是抽象类,它们构成Java I/O操作框架。


1.字节流

InputStream/OutputStream 都是抽象类,应使用实现了它们的具体类。

如FileInputStream实现了InputStream,FileOutputStream实现了OutputStream。本章使用FileInputStream从文件读取,用FileOutStream往文件写入。

OutputStream 说明

方法:

|-----------|-----------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 修饰符及返回值类型 | 方法签名 | 说明 |
| void | write(int b) | 写入0~255整数,表示输入的内容,如97就代表a |
| void | write(byte[] b) | 将数组b中的全部数据写入文件中 |
| void | write(byte[] b,int off,int len) | 从b中下标为off的地方开始往文件写入数据,共写len个字节 |
| void | close() | 关闭字节流 |
| void | flush() | 重要:我们知道I/O 的速度是很慢的,所以,大多的OutputStream为了减少设备操作的次数,在写数据的时候会将数据先暂存,存到内存的一个指定区域里,直到该区域满了或者满足其他指定条件时,才真正将数据写入设备中,这个区域一般称为缓冲区。但造成一个结果,就是我们写的数据,很可能会遗留一部分在缓冲区中。需要在最后或者合适的位置调用flush(刷新)操作,将数据刷到设备中。 |

FileOutputStream 说明

构造方法:

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

FileInputStream的构造方法也是如此。

输出流实战:

注意:OutputStream outputStream = new FileOutputStream(file);这里创建对象操作一旦成功,就相当于"打开文件"。每次程序打开一个文件,就会在文件描述符表中申请一个表项,由于文件描述符表不会自动关闭不用的表项,所以在业务繁忙的服务器中,不及时 outputStream.close() 就会导致文件描述符表表项耗尽,最终"文件资源泄露"。

产生这种情况的原因是,每次OutputStream outputStream = new FileOutputStream(file) 打开文件会清除file文件中的数据,要想不清除,接着上次写就要写入第二个参数-》FileOutputStream(file,true).

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() | 关闭字节流 |

输入流实战:

注意:不同编码方式,中文所占的字节数也不同,最常见的编码 UTF-8中常见中文占3个字节,有些生僻字占四个字节。

上图代码也忘记写close()关闭文件了;输入流和输出流都要手动关闭,这太麻烦了,而且容易忘记写close()。其实关闭文件操作不需要我们最后手动完成,用try-with-resources(资源管理机制)就解决了。

java 复制代码
public static void main(String[] args) {
        //实现了Closeable接口的类,出try代码块 就隐式调用close关闭,无需手动关闭。
         //多个流使用 ; 分隔
        try(InputStream inputStream = new FileInputStream("./test.txt");
            OutputStream outputStream = new FileOutputStream("./test.txt")){
            //。。。
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
总结

文件的I/O都是以CPU为视角,如输入流InputStream调用read是向cpu输入数据,输出流是从cpu输出,往文件中写数据。

不管是字节输入流还是输出流都需要关闭,关闭这两个I/O流,意味着关闭了进程(或叫程序)中对应的表项;而且I/O流不需要手动关闭,使用try-with-resources机制会自动帮我们关闭。

2.字符流

I/O的对象如果是文本文件,使用字符流操作是最方便的。

Reader和Writer也都是接口,这两个的使用和字节流的操作一模一样。

输出流(Writer)

Writer的writer方法

输出流(Reader)

Reader的read方法

CharBuffer 相当于对char[]封装。

字符流实战:
java 复制代码
package fileCSDN;

import java.io.*;

public class Test6 {
    public static void main(String[] args) {
        try(Reader reader = new FileReader("./test.txt");
            Writer writer = new FileWriter("./test.txt",true)) {
            while(true){
                //往文件写数据
                writer.write("你好呀!");
                char[] ch = new char[]{'哦','哦'};
                writer.write(ch);

                //读数据,一次最多读取4096个字符
                char[] chars=new char[4096];
                int a =  reader.read(chars);
                if(a==-1){
                    break;
                }
                for(int b=0;b<a;b++){
                    System.out.println(chars[b]);
                }
            }
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

字符流I/O操作细节与上面字节流I/O的操作细节类似,此处不再过多讲解。

综合示例:扫描指定目录,找到文件名或文件内容包含关键字的所有普通文件

java 复制代码
package fileCSDN;

import java.io.*;
import java.util.Scanner;

//扫描指定目录,找到文件名或文件内容包含关键字的所有普通文件
public class Test7 {
    public static void main(String[] args) throws IOException {
        Scanner sc = new Scanner(System.in);
        System.out.println("输入扫描的路径:");
        String dirPath = sc.next();
        File file = new File(dirPath);
        if(!file.isDirectory()){
            System.out.println("目录路径不存在或是普通文件");
            return;
        }
        System.out.println("输入关键字:");
        String keyWord = sc.next();

        search(file,keyWord);
    }

    private static void search(File file, String keyWord) throws IOException {
        if (file==null){
            return;
        }
        File[] files = file.listFiles();
        for(File f:files){
            if(f.isDirectory()) {
                search(f,keyWord);
            }else {
                dealFile(f,keyWord);
            }
        }
    }

    private static void dealFile(File f, String keyWord) throws IOException {
        //equals是判断字符串是否相等;contains是判断一个字符串是否包含另一个字符串
        if(f.getName().contains(keyWord)){
            System.out.println("文件内容包含关键字:"+f.getCanonicalPath());
            return;
        }else {
            try(Reader reader = new FileReader(f)){
                //文件内容是否包含关键字
                StringBuffer stringBuffer = new StringBuffer();
                while(true){
                    char[] data = new char[1024];
                    int num =reader.read(data);
                    if (num==-1){
                        break;
                    }
                    //这里参数不能写1024,1024是字符数组最大元素个数,我们要按读取的字符个数拼接字符串
                    stringBuffer.append(data,0,num);

                }
        //如果stringBuffer中包含关键字就返回一个下标,不存在返回-1;
                if(stringBuffer.indexOf(keyWord)>=0){
                    System.out.println("文件内容包含关键字:"+f.getCanonicalPath());
                }
            }
        }
    }
}
相关推荐
ba_pi1 小时前
每天写点什么2026-03-19-Doris三种存储模型
java·数据库·mysql
程序员老乔2 小时前
Java 新纪元 — JDK 25 + Spring Boot 4 全栈实战(二):Valhalla落地,值类型如何让电商DTO内存占用暴跌
java·spring boot·c#
SuniaWang2 小时前
《Spring AI + 大模型全栈实战》学习手册系列· 专题二:《Milvus 向量数据库:从零开始搭建 RAG 系统的核心组件》
java·人工智能·分布式·后端·spring·架构·typescript
张小洛2 小时前
Spring 常用类深度剖析(工具篇 02):ReflectionUtils——优雅操作反射的利器
java·后端·spring·工具类·spring常用类
GoodStudyAndDayDayUp2 小时前
RUO-VUE-PRO权限关联sql
java·数据库·sql
⑩-2 小时前
RabbitMQ 架构和工作原理?RabbitMQ 延迟队列如何实现?
java·分布式·架构·rabbitmq
子非鱼@Itfuture2 小时前
try-catch和try-with-resources区别是什么?try{}catch(){}和try(){}catch(){}有什么好处?
java·开发语言
Nyarlathotep01133 小时前
线程创建和Thread类
java
阿波罗尼亚3 小时前
JDK17 新特性
java