【JavaEE初阶】告别小白!Java IO 流读写 + 文件操作实战


我的个人主页
我的专栏: 人工智能领域、java-数据结构、Javase、C语言,MySQL,JavaEE初阶,希望能帮助到大家!!! 点赞👍收藏❤


目录

    • 一、先搞懂:文件和文件系统的基础认知
    • [二、Java 中操作文件的"核心工具":File 类](#二、Java 中操作文件的“核心工具”:File 类)
      • [1. File 类的关键属性、构造和方法](#1. File 类的关键属性、构造和方法)
      • [2. File 类实操:从获取信息到创建删除](#2. File 类实操:从获取信息到创建删除)
        • [(1)搞懂 get 系列方法:获取文件信息](#(1)搞懂 get 系列方法:获取文件信息)
        • [(2)创建与删除文件:createNewFile() 和 delete()](#(2)创建与删除文件:createNewFile() 和 delete())
        • [(3)创建目录:mkdir() 和 mkdirs() 的区别](#(3)创建目录:mkdir() 和 mkdirs() 的区别)
        • (4)文件重命名:renameTo()
    • [三、Java IO 流:文件内容读写的核心](#三、Java IO 流:文件内容读写的核心)
      • [1. 字节流:InputStream 和 OutputStream](#1. 字节流:InputStream 和 OutputStream)
      • [2. 字符流:更方便的文本读写](#2. 字符流:更方便的文本读写)
        • [(1)用 Scanner 读文本文件](#(1)用 Scanner 读文本文件)
        • [(2)用 PrintWriter 写文本文件](#(2)用 PrintWriter 写文本文件)
    • 四、实战案例:把知识点串起来用
      • [案例 1:扫描目录,找到指定文件并删除](#案例 1:扫描目录,找到指定文件并删除)
      • [案例 2:文件复制工具](#案例 2:文件复制工具)
      • [案例 3:扫描目录,找到内容包含关键词的文件](#案例 3:扫描目录,找到内容包含关键词的文件)
    • 五、新手避坑总结

前言:

对于刚接触 Java"文件操作"和"IO" 的小伙伴来说,"文件操作"和"IO 流"总像两座小山峰------听着有点难,实则只要找对路径,一步一步就能轻松登顶。今天这篇文章,就带着大家从基础概念到实战代码,把 Java 文件操作和 IO 流彻底搞明白。

一、先搞懂:文件和文件系统的基础认知

在写代码之前,我们得先明白"文件"到底是什么。狭义上的文件,是硬盘这种持久化存储设备中独立的数据单位 ,就像办公桌上一份份单独的文档,不仅有文字内容,还有文件名、类型、大小这些"附加信息"------我们把这些附加信息叫做"文件的元信息"。

比如你在电脑上看到的"PSGet.Format.ps1xml"文件,它的元信息就包括"修改日期 2019/3/19""类型 Windows PowerShell 数据文件""大小 9KB",这些信息和文件内容是分开保存的。

而随着文件越来越多,系统就用"树形结构"来管理它们------这就是我们熟悉的"文件夹(folder)或者目录(directory)"。比如 Windows 里的"此电脑→Windows(C:)→Program Files(X86)",Linux 里的"/usr/bin",都是通过层级目录把文件组织起来,既方便查找,逻辑上也更清晰。

另外,定位文件必须用到"路径",这里分了两种:

  • 绝对路径 :从根目录开始的完整路径,比如 C:\Program Files (x86)\WindowsPowerShell,不管当前在哪里,都能通过它找到文件;
  • 相对路径 :从当前目录出发的路径,比如从"WindowsPowerShell"目录去"Windows NT",用 ..\Windows NT 就行(.. 代表父目录,. 代表当前目录)。

拓展:即使是普通文件,根据其保存数据的不同,也经常被分为不同的类型,我们一般简单的划分为:

文本文件:保存被字符集编码的文本。

二进制文件:按照标准格式保存的非被字符集编码过的文件。


在Windows操作系统上,还有一类文件比较特殊,就是平时我们看到的快捷方式(shortcut),这种文件只是对真实文件的一种引用而已。其他操作系统上也有类似的概念,例如软链接(soft link)等。

最后,很多操作系统为了实现接口的统一性,将所有的 I/O 设备都抽象成了文件的概念,使用这一理念最为知名的就是 Unix、Linux 操作系统------万物皆文件。这种抽象设计能让操作系统对不同I/O设备(如硬盘、键盘、打印机等)的操作,都统一到文件操作的接口上,简化了开发和使用逻辑,无需为不同设备单独设计一套操作方式。

二、Java 中操作文件的"核心工具":File 类

Java 用 java.io.File 类来抽象描述一个文件(包括目录),但要注意:创建了 File 对象,不代表真实存在这个文件,它只是对文件的"描述"而已。

1. 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 实例,父目录用路径表示

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() 判断用户是否对文件有可写权限

2. File 类实操:从获取信息到创建删除

(1)搞懂 get 系列方法:获取文件信息

比如想知道文件的父目录、名称、路径,用这几个方法就行,:

java 复制代码
import java.io.File;
import java.io.IOException;

public class Main {
    public static void main(String[] args) throws IOException {
        // 这里的文件不一定真实存在
        File file = new File("..\\hello-world.txt");
        System.out.println(file.getParent()); // 输出父目录:..
        System.out.println(file.getName());   // 输出文件名:hello-world.txt
        System.out.println(file.getPath());   // 输出路径:..\hello-world.txt
        System.out.println(file.getAbsolutePath()); // 输出绝对路径:D:\代码练习\文件示例1\..\hello-world.txt
        System.out.println(file.getCanonicalPath()); // 输出简化绝对路径:D:\代码练习\hello-world.txt
    }
}
(2)创建与删除文件:createNewFile() 和 delete()
java 复制代码
import java.io.File;
import java.io.IOException;

public class Main {
    public static void main(String[] args) throws IOException {
        File file = new File("hello-world.txt"); // 确保文件初始不存在
        System.out.println(file.exists());      // 初始不存在:false
        System.out.println(file.createNewFile());// 创建成功:true
        System.out.println(file.exists());      // 创建后存在:true
        System.out.println(file.isFile());      // 是普通文件:true
        System.out.println(file.delete());      // 删除成功:true
        System.out.println(file.exists());      // 删除后不存在:false
    }
}

这里要注意:createNewFile() 只能创建普通文件,不能创建目录;delete() 直接删除文件,不会进回收站,操作要谨慎。

(3)创建目录:mkdir() 和 mkdirs() 的区别

新手最容易踩的坑就是这两个方法的区别!

  • mkdir():只能创建单层目录,如果父目录不存在,创建失败;
  • mkdirs():能创建多层目录(包括不存在的父目录)。

比如要创建"some-parent\some-dir"这个多层目录,用 mkdir() 会失败,用 mkdirs() 才能成功:

java 复制代码
package IO;
import java.io.File;
public class demo3 {
    public static void main(String[] args) {
        File dir=new File("some-parent\\some-dir");
        System.out.println(dir.mkdir());
        System.out.println(dir.isDirectory());
        System.out.println(dir.mkdirs());
        System.out.println(dir.isDirectory());
    }
}

这个区别一定要记牢,不然创建多层目录时会卡很久。

(4)文件重命名:renameTo()
java 复制代码
import java.io.File;
import java.io.IOException;

public class Main {
    public static void main(String[] args) throws IOException {
        File oldFile = new File("some-file.txt"); // 确保该文件存在
        File newFile = new File("dest.txt");      // 确保该文件不存在
        System.out.println(oldFile.exists());
        System.out.println(newFile.exists());
        System.out.println(oldFile.renameTo(newFile)); // 重命名成功:true
        System.out.println(oldFile.exists());     // 原文件不存在:false
        System.out.println(newFile.exists());     // 新文件存在:true
    }
}


注意:如果目标文件(dest.txt)已经存在,renameTo() 会返回 false,所以先判断目标文件是否存在很重要。

三、Java IO 流:文件内容读写的核心

搞懂了文件操作,接下来就是"读写文件内容"------这就需要 IO 流了。可以把 IO 流比喻得很形象:读文件像"接水"(输入流 InputStream),写文件像"灌水"(输出流 OutputStream) ,我们就顺着这个逻辑来学。

1. 字节流:InputStream 和 OutputStream

字节流是最基础的 IO 流,以"字节"为单位读写数据,适合所有文件(比如文本、图片、视频)。

(1)InputStream:读文件内容

InputStream 是抽象类,我们常用它的子类 FileInputStream 来读文件。它的核心方法是 read(),有三种用法:

  • read():读1个字节,返回字节值(-1 表示读完);
  • read(byte[] b):读多个字节到数组 b 中,返回实际读的字节数;
  • read(byte[] b, int off, int len):从 off 位置开始,读 len 个字节到数组 b 中。
  • close():关闭字节流。

FileInputStream类构造方法

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

强调:用数组读比单个字节读效率高,因为减少了 IO 次数。比如读"hello.txt"里的"Hello":

单个字节读:

java 复制代码
package IO;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class demo5 {
    //需要在项目目录下创建hello.txt文件
    public static void main(String[] args) throws IOException {
        try(InputStream is=new FileInputStream("hello.txt")){
            while(true){
                int b=is.read();
                if(b==-1){
                    break;
                }
                System.out.printf("%c",b);
            }
        }
    }
}

数组读:

java 复制代码
package IO;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class demo6 {
    public static void main(String[] args) throws IOException {
        try(InputStream is=new FileInputStream("hello.txt")){
            byte[] buf=new byte[1024];
            int len;
            while(true){
                len=is.read(buf);
                if(len==-1){
                    break;
                }
                for(int i=0;i<len;i++){
                    System.out.printf("%c",buf[i]);
                }
            }
        }
    }
}

运行后会输出"Hello",这里用了 try-with-resources 语法,能自动关闭流,避免资源泄漏,新手一定要养成这个习惯。

如果读中文文件(比如"你好中国"),要注意编码,用 UTF-8 解码,因为 UTF-8 中一个中文字符占 3 个字节:

java 复制代码
package IO;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class demo7 {
    public static void main(String[] args) throws IOException {
        try(InputStream is=new FileInputStream("hello.txt")){
            byte[] buf=new byte[1024];
            int len;
            while (true){
                len=is.read(buf);
                if(len==-1){
                    break;
                }
                for(int i=0;i<len;i+=3){
                    String s=new String(buf,i,3,"UTF-8");
                    System.out.printf("%s",s);
                }
            }
        }
    }
}

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

构造方法 说明
Scanner(InputStream is, String charset) 使用 charset 字符集进行 is 的扫描读取
java 复制代码
package IO;

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Scanner;
public class demo8 {
    public static void main(String[] args) throws IOException {
        try(InputStream is=new FileInputStream("hello.txt")){
            try(Scanner sc=new Scanner(System.in)){
                while(sc.hasNext()){
                    String s=sc.next();
                    System.out.print(s);
                }
            }
        }
    }
}
(2)OutputStream:写文件内容

OutputStream 也是抽象类,常用子类 FileOutputStream 写文件。核心方法是 write(),同样有三种用法,还有一个关键方法 flush()------因为 OutputStream 有缓冲区,数据会先存在内存,必须调用 flush() 才能把数据刷到硬盘

比如写字符串"你好中国"到"output.txt":

java 复制代码
import java.io.*;

public class Main {
    public static void main(String[] args) throws IOException {
        try (OutputStream os = new FileOutputStream("output.txt")) {
            String s = "你好中国";
            byte[] b = s.getBytes("UTF-8"); // 转成 UTF-8 字节数组
            os.write(b); // 写入字节数组
            os.flush();  // 必须刷新,否则数据可能留在缓冲区
        }
    }
}

运行后打开"output.txt",就能看到"你好中国"。如果想追加内容,把 FileOutputStream 构造方法改成 new FileOutputStream("output.txt", true) 即可(第二个参数 true 表示追加)。

2. 字符流:更方便的文本读写

字节流读中文需要处理编码,很麻烦,这时候就需要"字符流"------按"字符"为单位读写,自动处理编码问题。用 Scanner 读字符,用 PrintWriter 写字符。

(1)用 Scanner 读文本文件

Scanner 能按行读文本,还能指定编码(比如 UTF-8),避免乱码。比如读"hello.txt"里的"你好中国":

java 复制代码
import java.io.*;
import java.util.Scanner;

public class Main {
    public static void main(String[] args) throws IOException {
        try (InputStream is = new FileInputStream("hello.txt")) {
            // 指定 UTF-8 编码,避免中文乱码
            try (Scanner scanner = new Scanner(is, "UTF-8")) {
                while (scanner.hasNextLine()) { // 按行读
                    String line = scanner.nextLine();
                    System.out.println(line); // 输出:你好中国
                }
            }
        }
    }
}

这种方式比字节流简单多了,新手读文本文件优先用这个。

(2)用 PrintWriter 写文本文件

PrintWriter 有我们熟悉的 print()println()printf() 方法,写文本很方便,还能指定编码。

java 复制代码
import java.io.*;

public class Main {
    public static void main(String[] args) throws IOException {
        try (OutputStream os = new FileOutputStream("output.txt")) {
            // 先转成 OutputStreamWriter,指定 UTF-8 编码
            try (OutputStreamWriter osWriter = new OutputStreamWriter(os, "UTF-8")) {
                // 用 PrintWriter 写内容
                try (PrintWriter writer = new PrintWriter(osWriter)) {
                    writer.println("我是第一行"); // 换行
                    writer.print("我是第二行");   // 不换行
                    writer.printf("%d: 我是第三行\n", 3); // 格式化输出
                    writer.flush(); // 刷新到硬盘
                }
            }
        }
    }
}

运行后"output.txt"里会有三行内容,格式清晰,比直接用 OutputStream 方便太多。

四、实战案例:把知识点串起来用

学完基础,必须通过实战巩固。通过一些经典案例,我们逐个拆解,新手跟着写一遍就能掌握。

案例 1:扫描目录,找到指定文件并删除

需求:输入根目录和关键词,找到文件名包含关键词的普通文件,询问用户是否删除。

核心思路:用"递归"遍历树形目录(因为文件系统是树形结构),找到符合条件的文件后处理。

java 复制代码
import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args) throws IOException {
        Scanner scanner = new Scanner(System.in);
        // 输入根目录
        System.out.print("请输入要扫描的根目录(绝对路径/相对路径):");
        String rootDirPath = scanner.next();
        File rootDir = new File(rootDirPath);
        if (!rootDir.isDirectory()) {
            System.out.println("根目录不存在或不是目录,退出!");
            return;
        }
        // 输入关键词
        System.out.print("请输入文件名包含的字符:");
        String token = scanner.next();

        List<File> result = new ArrayList<>();
        // 递归扫描目录
        scanDir(rootDir, token, result);

        // 处理结果
        System.out.println("共找到 " + result.size() + " 个符合条件的文件:");
        for (File file : result) {
            System.out.print(file.getCanonicalPath() + ",是否删除?(y/n)");
            String choice = scanner.next();
            if (choice.toLowerCase().equals("y")) {
                file.delete();
                System.out.println("已删除!");
            }
        }
    }

    // 递归扫描目录的方法
    private static void scanDir(File rootDir, String token, List<File> result) {
        File[] files = rootDir.listFiles();
        if (files == null || files.length == 0) {
            return; // 目录为空,返回
        }
        for (File file : files) {
            if (file.isDirectory()) {
                scanDir(file, token, result); // 是目录,递归扫描
            } else {
                // 是普通文件,判断文件名是否包含关键词
                if (file.getName().contains(token)) {
                    result.add(file.getAbsoluteFile());
                }
            }
        }
    }
}


这个案例用到了 File 类的 isDirectory()listFiles(),还有递归遍历,新手要理解递归的逻辑------"自己调用自己,处理子目录"。

案例 2:文件复制工具

需求:输入源文件路径和目标路径,实现文件复制(支持所有文件类型,比如文本、图片)。

核心思路:用 InputStream 读源文件,用 OutputStream 写目标文件,用字节数组做缓冲区提高效率。

java 复制代码
import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args) throws IOException {
        Scanner scanner = new Scanner(System.in);
        // 输入源文件
        System.out.print("请输入要复制的文件路径:");
        String sourcePath = scanner.next();
        File sourceFile = new File(sourcePath);
        if (!sourceFile.exists()) {
            System.out.println("源文件不存在!");
            return;
        }
        if (!sourceFile.isFile()) {
            System.out.println("不是普通文件,无法复制!");
            return;
        }
        // 输入目标路径
        System.out.print("请输入目标路径:");
        String destPath = scanner.next();
        File destFile = new File(destPath);

        // 处理目标文件已存在的情况
        if (destFile.exists()) {
            if (destFile.isDirectory()) {
                System.out.println("目标是目录,无法覆盖!");
                return;
            }
            System.out.print("目标文件已存在,是否覆盖?(y/n)");
            String choice = scanner.next();
            if (!choice.toLowerCase().equals("y")) {
                System.out.println("停止复制!");
                return;
            }
        }

        // 开始复制:读源文件,写目标文件
        try (InputStream is = new FileInputStream(sourceFile);
             OutputStream os = new FileOutputStream(destFile)) {
            byte[] buf = new byte[1024]; // 1KB 缓冲区
            int len;
            while ((len = is.read(buf)) != -1) {
                os.write(buf, 0, len); // 写读到的字节
            }
            os.flush(); // 刷新缓冲区
        }
        System.out.println("复制完成!");
    }
}

执行完成后ouput文件复制了hello文件里面的内容。

这个案例是 IO 流的经典应用,不管复制什么文件(文本、图片、视频)都能用,因为字节流不区分文件类型。

案例 3:扫描目录,找到内容包含关键词的文件

需求:输入根目录和关键词,找到文件名或内容包含关键词的普通文件。

核心思路:在案例 1 的基础上,增加"读文件内容判断"的逻辑,用 Scanner 读文件内容,判断是否包含关键词。

java 复制代码
import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args) throws IOException {
        Scanner scanner = new Scanner(System.in);
        System.out.print("请输入要扫描的根目录:");
        String rootDirPath = scanner.next();
        File rootDir = new File(rootDirPath);
        if (!rootDir.isDirectory()) {
            System.out.println("根目录无效,退出!");
            return;
        }
        System.out.print("请输入要查找的关键词:");
        String token = scanner.next();

        List<File> result = new ArrayList<>();
        scanDirWithContent(rootDir, token, result);

        System.out.println("共找到 " + result.size() + " 个文件:");
        for (File file : result) {
            System.out.println(file.getCanonicalPath());
        }
    }

    // 递归扫描目录,判断文件名或内容
    private static void scanDirWithContent(File rootDir, String token, List<File> result) throws IOException {
        File[] files = rootDir.listFiles();
        if (files == null || files.length == 0) {
            return;
        }
        for (File file : files) {
            if (file.isDirectory()) {
                scanDirWithContent(file, token, result);
            } else {
                // 文件名包含,或内容包含,都加入结果
                if (file.getName().contains(token) || isContentContains(file, token)) {
                    result.add(file);
                }
            }
        }
    }

    // 判断文件内容是否包含关键词(按 UTF-8 处理)
    private static boolean isContentContains(File file, String token) throws IOException {
        StringBuilder sb = new StringBuilder();
        try (InputStream is = new FileInputStream(file);
             Scanner scanner = new Scanner(is, "UTF-8")) {
            while (scanner.hasNextLine()) {
                sb.append(scanner.nextLine()).append("\n"); // 读所有行
            }
        }
        return sb.indexOf(token) != -1; // 判断是否包含关键词
    }
}

这个案例增加了 isContentContains() 方法,读文件内容并判断,适合查找文本文件中的关键词(注意:大文件会影响性能,文档里也提到了这一点)。

五、新手避坑总结

看到这里,你已经掌握了 Java 文件操作和 IO 流的核心内容,最后再总结几个新手常踩的坑,帮你少走弯路:

  1. File 类不代表真实文件 :创建 File 对象只是"描述"文件,不代表文件存在,必须调用 createNewFile()mkdirs() 才会真实创建;
  2. mkdir() 和 mkdirs() 别用混 :创建多层目录一定要用 mkdirs()
  3. IO 流必须关闭 :用 try-with-resources 语法,自动关闭流,避免资源泄漏;
  4. OutputStream 要 flush() :写完数据必须调用 flush(),否则数据可能留在缓冲区,没写到硬盘;
  5. 读中文要指定编码:用 Scanner 或 OutputStreamWriter 时,明确指定 UTF-8,避免乱码。

至此,Java 文件操作和 IO 流的核心知识和实战就讲完了。其实这些内容并不难,关键是多写代码、多跑案例------把文中的代码逐个复制到 IDE 里运行,改改参数(比如换个文件路径、关键词),很快就能熟练掌握。告别小白,从搞定文件操作和 IO 流开始吧!

相关推荐
沐知全栈开发4 小时前
Vue3 Ajax(Axios)详解
开发语言
dllmayday4 小时前
QWidget上叠加半透明QML组件显示方案
开发语言·qt5
电子_咸鱼4 小时前
【QT——信号和槽(1)】
linux·c语言·开发语言·数据库·c++·git·qt
爬山算法4 小时前
Netty(7)如何实现基于Netty的TCP客户端和服务器?
java·服务器·tcp/ip
全栈独立开发者4 小时前
软考架构师实战:Spring Boot 3.5 + DeepSeek 开发 AI 应用,上线 24 小时数据复盘(2C1G 服务器抗压实录)
java·spring boot·后端
deephub4 小时前
PyCausalSim:基于模拟的因果发现的Python框架
开发语言·python·机器学习·因果发现
weixin_307779134 小时前
Jenkins Declarative Pipeline:现代CI/CD的声明式实践指南
开发语言·ci/cd·自动化·jenkins·etl
CoderYanger4 小时前
D.二分查找-基础-2529. 正整数和负整数的最大计数
java·开发语言·数据结构·算法·leetcode·职场和发展
小光学长4 小时前
ssm农民养殖经验交流与分享平台bc046578(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
java·数据库·spring