硅基计划6.0 JavaEE 叁 文件IO


文章目录


一、起源

我们为了简化我们开发人员读写硬盘上的数据,操作系统对上述操作进行了封装,提供对外接口

因为操作系统有很多种,因此我们Java又对操作系统的API进行了封装,我们只需要使用相关的文件的API即可

二、路径&目录结构

我们打开电脑上的每一个目录,可能存在很多个文件,因此我们的每一级目录都是一棵N叉树,因此得名目录树

而我们在电脑上的文件位置则使用诸如"C:\JavaCode\java-ee-career\readme.md"这样的路径表示

  • 绝对路径:就是从硬盘开始一直到文件位置的路径,就比如上面那个路径
  • 相对路径:从一个基准目录出发到对应文件的目录,比如刚刚的路径"C:\JavaCode\java-ee-career\readme.md"中,我以JavaCode为起始位置,那么这个文件的相对路径就是java-ee-career\readme.md

三、文件类型

如果我们按照分类来说,分为普通的文件和一个目录文件

如果按照内容来分,我们分为文本文件和二进制文件

四、文件操作

这个是一组类的流对象,什么是流,你可以把它比喻成一条河流,通过这条河流传输信息

对于文件的内容操作,顾名思义就是去修改文件内容

而对文件系统操作,它是操作系统的一个子模块,通过文件资源管理器

五、File类

这个类是专门处理文件io的类,放在了java.io.File包下

1. 内部属性

我们常用的就一个,即文件路径分隔符

对于刚刚的路径中"C:\JavaCode\java-ee-career\readme.md"\

就是分割一级级目录的分隔符,大部分的操作系统都是/,但是windows是老系统为了兼容嘛

2. 主要构造方法

  1. 版本一File(String pathname),参数是填写操作文件的路径,可以使用相对路径。但是对于相对路劲,这个比较复杂,如果是这么写"./test.txt"表示的是基于当前程序运行的目录下,而.表示的是当前目录
  2. 版本二File(File Parent,String Child)即把路径拆分成两份。比如对于这个路径"C:\JavaCode\java-ee-career\readme.md"中双亲路径ParentC:\JavaCode\java-ee-career,孩子路径Childreadme.md

3. 主要方法

1. 返回值是String

  1. getParent(),返回File对象所在的双亲路径,例如刚刚的C:\JavaCode\java-ee-career
  2. getName(),获取文件名本身,例如刚刚的readme.md
  3. getPath(),获取整个路径,比如刚刚的"C:\JavaCode\java-ee-career\readme.md"
  4. getAbsolutePath(),获取绝对路径
  5. getCanonicalPath(),获取修饰过的路径

什么是修饰过的路径,对于绝对路径我们看不出来,但是对于相对路径

  • 方法四的获取绝对路径Java\.\test.txt
  • 方法五的获取修饰过的绝对路径Java\test.txt

去除了字符串中冗余的字符


2. 返回值是boolean

  1. exists(),判断文件或者是目录是否存在
  2. isDirectory(),判断一个文件是否是目录
  3. isFile()判定一个文件是否真的是文件
  4. createNewFile(),创建一个空文件,成功则返回true,如果硬盘满了或者是没有权限等待都会返回false表示创建失败
  5. delete(),表示直接从硬盘上物理的删除,不进入回收站
  6. mkdir(),创建一层目录
  7. mkdirs()创建多级目录

3. 返回File

  1. deleteOnExit(),标记式文件删除。即在JVM运行的时候,只是标记这个文件会删除,此时还没有删,当JVM运行结束之前再把文件删除。在开发中主要应用于临时文件的管理

4. 返回String[]

  1. list(),列出当前目录下面的所有文件的文件名

5. 返回File[]

  1. listFiles(),列出当前目录下所有文件内容,存储在File[]对象中

我们用一个代码实际演示下几个方法使用,我么使用下图目录作为演示

java 复制代码
public class Demo1 {
    public static void main(String[] args) {
        File file = new File("C:\\JavaCode\\java-ee-career\\TestProjectNewForEE20251107");
        String [] fileNames = file.list();
        for(String fileName : fileNames){
            System.out.println(fileName);
        }
        File file1 = new File("C:\\JavaCode\\java-ee-career\\TestProjectNewForEE20251107\\Ijava\\Ijavas");
        file1.mkdirs();
    }
}

可以看到我们的文件内容和多级目录已经成功创建

6. 文件目录重命名或移动

使用 boolean ret = file.renameTo(File dest)方法完成

如果目录路径是同一个,则进行文件重命名操作

如果不是同一个路径,则在这个路径下创建你指定的文件,说白了就是移动文件

比如刚刚的例子,我想把"C:\\JavaCode\\java-ee-career\\TestProjectNewForEE20251107\\Ijava\\Ijavas\\test.txt"中的文件移动至"C:\\JavaCode\\java-ee-career\\TestProjectNewForEE20251107\\test.txt"这个目录下

java 复制代码
File oldPath = new File("C:\\JavaCode\\java-ee-career\\TestProjectNewForEE20251107\\Ijava\\Ijavas\\test.txt");
File destPath = new File("C:\\JavaCode\\java-ee-career\\TestProjectNewForEE20251107\\test.txt");
System.out.println(oldPath.renameTo(destPath));//true

7. 针对文件内容的操作

我们分成两大类,一个是字节流,即逐个字节操作内容。一个是字符流,即逐个字符操作内容

对于字节流,提供了两个抽象类InputStream-->输入流OutputStream-->输出流

对于字符流,提供了两个抽象类Write-->输入流Reader-->输出流

1. InputStream抽象类

这个类主要是打开文件进行读取操作,对于read()方法有四种版本

  1. 版本一:无参数版本,即一次只读取一个直接,读到文件末尾返回-1
  2. 版本二:一个参数版本,参数byte[]是一个输出型参数,所谓输出型参数,就是利用这个参数去接收结果
  3. 版本三:三个参数版本,在版本二输出型参数byte[]基础上,len为预期要读取多少个字节,off为偏移量即相对于文件开头第一个字节的位置
java 复制代码
public static void main(String[] args) throws IOException {
        //我们提前在文件内容中写入"abcdefg"内容
        InputStream inputStream = new FileInputStream("C:\\JavaCode\\java-ee-career\\TestProjectNewForEE20251107\\test.txt");
        while(true){//不知什么时候读取完,等到读取完毕再判断退出
            int n = inputStream.read();
            if(n == -1){
                //读取完毕
                break;
            }
            //我们使用十六进制打印内容
            System.out.printf("%x\n",n);//61 62 63 64 65 66 67 正好对应我们Unicode码值
        }
    }

但是每次都是一个字节一个字节从硬盘读取,开销太大且效率慢,因此我们使用输出型参数

java 复制代码
public static void main(String[] args) throws IOException {
        InputStream inputStream = new FileInputStream("C:\\JavaCode\\java-ee-career\\TestProjectNewForEE20251107\\test.txt");
        while(true){
            byte[] bytes = new byte[1024];//每次读取1024个字节
            //如果已经读取完毕就退出
            int n = inputStream.read(bytes);
            if(n == -1){
                break;
            }
            for (int i = 0; i < n; i++) {
                System.out.printf("%x\n",bytes[i]);
            }
        }
    }

好,我们使用了资源是不是要进行释放啊,对于我们的文件描述附表,内部是不能扩容的

因此如果我们把这个表填满了就会导致程序出现重大bug,因此我们需要定时重启下以便释放资源

因此我们在每次循环最后加入关闭资源的代码,当然更加简便的做法是把整个代码快用try块包裹,然后在finally关闭资源,catch捕获异常

java 复制代码
public static void main(String[] args) throws IOException {
        InputStream inputStream = null;
        try {
             inputStream = new FileInputStream("C:\\JavaCode\\java-ee-career\\TestProjectNewForEE20251107\\test.txt");
            while (true) {
                byte[] bytes = new byte[1024];//每次读取1024个字节
                //如果已经读取完毕就退出
                int n = inputStream.read(bytes);
                if (n == -1) {
                    break;
                }
                for (int i = 0; i < n; i++) {
                    System.out.printf("%x\n", bytes[i]);
                }
            }
        }catch(IOException e){
            e.printStackTrace();
        }finally {
            //判断对象是否有构建好,避免空指针
            if(inputStream != null){
                inputStream.close();
            }
        }
    }

你是不是觉得这个代码非常丑,对的!因此我们使用try-with-resource代码块

我们可以在try()参数中指明多个对象,并且在try块执行完代码后,自动调用close()方法关闭资源

但是前提是这个类要实现closable接口并且有close()方法

java 复制代码
public static void main(String[] args){
        try (InputStream inputStream = new FileInputStream("C:\\JavaCode\\java-ee-career\\TestProjectNewForEE20251107\\test.txt")) {
            while (true) {
                byte[] bytes = new byte[1024];//每次读取1024个字节
                //如果已经读取完毕就退出
                int n = inputStream.read(bytes);
                if (n == -1) {
                    break;
                }
                for (int i = 0; i < n; i++) {
                    System.out.printf("%x\n", bytes[i]);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

这样代码不就清爽多了吗!!

2. OutputStream抽象类

这个类主要进行写的操作,对于wirte方法,有三个版本

  1. 版本一:一次写一个字符串
  2. 版本二:一次性写一个完整的字节型数组
  3. 版本三:一次性写一个字节型数组的一部分

这个类的构造方法,如果你只写文件路径一个参数,则默认把文件内容清空

如果你在文件路径后再写一个参数true,表示你现在做的操作是针对内容进行拼接,并且每一次拼接都在内容末尾

java 复制代码
public static void main(String[] args) {
        try(OutputStream outputStream = new FileOutputStream("C:\\JavaCode\\java-ee-career\\TestProjectNewForEE20251107\\test.txt")){
            outputStream.write(65);//对于字符A
            outputStream.write(66);//对于字符B
            byte[] bytes = {67,68};//对应字符C,D
            outputStream.write(bytes);
        }catch (IOException e){
            e.printStackTrace();
        }
    }
3. Reader抽象类

这个是字符流的文件读取操作,对于read()有四个版本

  1. 版本一:一次性读取一个字符
  2. 版本二:读取一次字符数组
  3. 版本三:读取一个字符数组封装的charBuffer
  4. 版本四:对字符数组的一部分内容读取
java 复制代码
public static void main(String[] args) {
        try(Reader reader = new FileReader("C:\\JavaCode\\java-ee-career\\TestProjectNewForEE20251107\\test.txt")){
            while(true) {
                int n = reader.read();//读取到末尾返回-1
                if (n == -1) {
                    break;
                }
                char c = (char)n;
                System.out.println(c);
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }
4. Write抽象类

这个是字符流的文件写操作,对于wirte()有五个版本

  1. 版本一:一次性写一个字符
  2. 版本二:一次性写一个字符串
  3. 版本三:一次性写一个字符数组
  4. 版本四:一次性写一个字符串的子串
  5. 版本五:写一个字符数组的一部分
java 复制代码
public static void main(String[] args) {
        try(Writer writer = new FileWriter("C:\\JavaCode\\java-ee-career\\TestProjectNewForEE20251107\\test.txt")){
            writer.write(66);
            char[] ch = {'a','b'};
            writer.write(ch);
        }catch (IOException e){
            e.printStackTrace();
        }
    }

六、使用Scanner搭配InputStream

我们这样搭配可以对文件的内容进行格式化解析

java 复制代码
public static void main(String[] args) throws FileNotFoundException {
        InputStream inputStream = new FileInputStream("C:\\JavaCode\\java-ee-career\\TestProjectNewForEE20251107\\test.txt");
        Scanner scanner = new Scanner(inputStream);
        String a = scanner.next();
        System.out.println(a);//Bab
    }

七、Write子类PrintWrite

这个类可以让我们可以将内容以固定格式写入文件,写入的数据先在临时缓冲区,当关闭资源的时候自动写入内容

java 复制代码
public static void main(String[] args) throws FileNotFoundException {
        PrintWriter printWriter = new PrintWriter("C:\\JavaCode\\java-ee-career\\TestProjectNewForEE20251107\\test.txt");
        printWriter.printf("%d+%d=%d\n",10,20,30);
        printWriter.close();
    }

八、使用递归去查看当前目录的所有子目录

java 复制代码
public class Demo2 {
    public static void main(String[] args) {
        //使用递归查看当前目录的所有包含指定名字的文件
        Scanner scanner = new Scanner(System.in);
        String rootPath = scanner.next();
        String fileName = scanner.next();
        //判断输入合法性
        if(rootPath.isEmpty() || fileName.isEmpty()){
            System.out.println("输入非法");
            return;
        }
        File rootFile = new File(rootPath);
        //判断这个是否存在或者是这个路径是否是目录
        if(!rootFile.exists() || !rootFile.isDirectory()){
            System.out.println("非法目录");
            return;
        }
        search(rootFile,fileName);
    }

    private static void search(File rootFile,String fileName){
        //列出当前目录中的所有文件包括子目录
        File[] files = rootFile.listFiles();
        //判定空目录
        if(files == null){
            return;  // 空目录直接返回,不需要打印
        }
        for(File file : files){
            if(file.isFile()){
                if(file.getName().contains(fileName)){
                    //找到了就尝试去删除 - 这里应该传入当前文件,不是根目录
                    tryToDelete(file,fileName);
                }
            }else if(file.isDirectory()){
                //如果是一个子目录就进行递归操作 - 这里递归参数错了
                search(file,fileName);  // 应该是file不是rootFile
            }
        }
    }

    private static void tryToDelete(File targetFile,String fileName){
        Scanner scanner = new Scanner(System.in);
        System.out.println("是否要删除 " + targetFile.getAbsolutePath() + " ?(Y/N)");
        String choice = scanner.next();
        if(choice.equals("Y") || choice.equals("y")){
            boolean success = targetFile.delete();
            if(success){
                System.out.println("删除成功: " + targetFile.getName());
            }else{
                System.out.println("删除失败: " + targetFile.getName());
            }
        }else{
            System.out.println("删除取消");
        }
    }
}

九、复制普通类型文件

java 复制代码
public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String oldPath = scanner.next();//原文件路径
        String destPath = scanner.next();//目标文件路径
        File oldFile = new File(oldPath);
        if(!oldFile.isFile()){
            //如果传入的不是文件就不能执行复制操作
            System.out.println("非法输入");
            return;
        }
        //复制的同时重命名
        File destFile = new File(destPath);
        //先找到目标路径的双亲路径
        File destParent = destFile.getParentFile();
        if(!destParent.isDirectory()){
            System.out.println("路径非法");
        }
        //同时打开两个文件路径进行复制操作
        try(InputStream inputStream = new FileInputStream(oldFile);
            OutputStream outputStream = new FileOutputStream(destFile)){
            while(true){
                //我们一次性1024字节即1kb/次速度进行复制
                byte[] bytes = new byte[1024];
                int n = inputStream.read(bytes);
                if(n == -1){
                    break;
                }
                outputStream.write(bytes,0,n);
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }

十、扫描指定目录的所有子目录中普通文件内容是否包含指定内容

java 复制代码
public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        String rootPath = scanner.next();//要搜索的根目录
        String word = scanner.next();//要搜索的关键字
        if(word.isEmpty() || rootPath.isEmpty()){
            System.out.println("输入非法");
            return;
        }
        File rootFile = new File(rootPath);
        if(!rootFile.isDirectory()){
            System.out.println("输入非法,不是目录!");
            return;
        }
        searchs(rootFile,word);
    }

    private static void searchs(File rootFile,String word){
        //列出所有的内容
        File[] files = rootFile.listFiles();
        if(files == null){
            return;
        }
        for(File file : files){
            if(file.isFile()){
                //如果是文件,就要看看内容是否包含
                trySearch(file,word);
            }else if(file.isDirectory()){
                //递归子目录
                searchs(file,word);
            }
        }
    }

    private static void trySearch(File file,String word){
        //先判断文件名是不是我们想要的
        if(file.getName().contains(word)){
            System.out.println("找到了,文件路径是:");
            System.out.println(file.getAbsolutePath());
        }
        //接下来查看文件内容,即使文件名不匹配内容也可能匹配
        try(Reader reader = new FileReader(file)){
            //把读取到的字符进行拼接
            StringBuilder str = new StringBuilder();
            while(true){
                char [] chars = new char[1024];
                int n = reader.read();
                if(n == -1){
                    //文件读取到末尾了
                    break;
                }
                str.append(chars);
            }
            if(str.indexOf(word) >= 0){
                System.out.println("找到了");
            }
        }catch (IOException e){
            e.printStackTrace();
        }
    }

文章可能有错误欢迎指出


Git码云仓库链接


END♪٩(´ω`)و♪`

相关推荐
奋斗的牛马2 小时前
硬件基础知识-电容(一)
单片机·嵌入式硬件·学习·fpga开发·信息与通信
那我掉的头发算什么2 小时前
【javaEE】多线程--认识线程、多线程
java·jvm·redis·性能优化·java-ee·intellij-idea
程序员卷卷狗2 小时前
联合索引的最左前缀原则与失效场景
java·开发语言·数据库·mysql
纪莫2 小时前
技术面:SpringCloud(SpringCloud有哪些组件,SpringCloud与Dubbo的区别)
java·spring·java面试⑧股
Nuyoah11klay2 小时前
华清远见25072班单片机基础学习day1
单片机·嵌入式硬件·学习
大白的编程日记.2 小时前
【高阶数据结构学习笔记】高阶数据结构之B树B+树B*树
数据结构·笔记·学习
会编程的吕洞宾3 小时前
Java中的“万物皆对象”:一场编程界的哲学革命
java·后端
会编程的吕洞宾3 小时前
Java封装:修仙界的"护体罡气"
java·后端
豆沙沙包?3 小时前
2025年--Lc231-350. 两个数组的交集 II-Java版
java·开发语言