一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

Java文件操作如何入门?一文搞懂File类和IO流

时间:2026-07-24 09:15:55 编辑:袖梨 来源:一聚教程网

一、认识文件系统

1.1 什么是文件

文件是对硬盘等持久化存储设备中数据单位的抽象。我们日常办公桌上的一份份纸质文件,在计算机中就以文件的形式存在。

Java文件操作怎么入门?一文搞懂File类和IO流

每个文件不仅仅是数据的容器,还包含了一些元信息,比如文件名、文件类型、文件大小、创建时间、修改时间等,这部分信息称为文件的元信息。

1.2 树形结构组织

随着文件数量的增加,如何有效管理文件成了一个重要问题。计算机采用了树形结构来组织文件。这种结构让我们可以创建文件夹(也就是目录)来分类存放文件。目录也是文件。

在电脑中的文件系统,根目录就像书房的总体空间,子目录就像各个分区,最终的文件就像每一本具体的书。

1.3 文件路径(Path)

在树形文件系统中,使用路径(Path)可以准确定位一个唯一的文件。

通常使用斜杠/ 分割路径中的多级目录;在主流操作系统都是使用/分割,Windows默认使用反斜杠,也支持斜杠/

在代码中涉及到路径最好使用斜杠/,因为单个反斜杠加字符表示转移字符,写两个反斜杠才表示一个反斜杠。

  • 绝对路径(Absolute Path) 从根目录开始,完整描述文件位置的路径。比如在Windows系统中,C:Users张三Documentsreport.docx就是一个绝对路径,它从盘符开始,逐级定位到目标文件。

  • 相对路径(Relative Path) 从当前工作目录出发,描述文件位置的路径。例如,如果当前在C:Users张三目录下,那么Documentsreport.docx就是一个相对路径。在路径中,.代表当前目录,..代表父目录。../..表示上两层目录

    • 在IDEA中直接运行,基准路径就是项目的目录

1.4 文件的分类

从数据存储的角度,文件主要分为两大类:

  • 文本文件:存储的是被字符集编码后的文本数据。比如我们平时用记事本编写的.txt文件,用UTF-8编码保存的Java源文件等。当我们用文本编辑器打开这类文件时,能够看到人类可读的文字。

  • 二进制文件:按照特定格式存储的非文本数据。比如图片(.jpg、.png)、音频(.mp3)、视频(.mp4)以及可执行程序(.exe)等。这类文件需要用专门的软件才能正确解析和显示。

在Windows操作系统中,系统通常通过文件的扩展名(如.txt、.docx、.exe)来识别文件类型并决定用哪个程序打开。而在Linux、Unix等系统中,文件类型并不严格依赖于扩展名,而是通过文件的内部特征或权限位来判断。

1.5 文件权限与特殊文件

操作系统为了数据安全,为文件设置了权限系统,通常包括:

  • 可读(r):允许读取文件内容
  • 可写(w):允许修改文件内容
  • 可执行(x):允许将文件作为程序运行

在Linux系统中,每个文件都有针对文件所有者、同组用户和其他用户的独立权限设置。

此外,还有一种特殊文件叫做快捷方式(Windows)软链接(Linux),它们并不存储实际数据,而是指向另一个真实文件的引用,类似"指针"。

Unix/Linux操作系统秉承"万物皆文件"的设计哲学,将所有的I/O设备(如键盘、鼠标、显示器、网络接口)都抽象为文件,这使得设备操作可以统一使用文件读写接口。

二、Java中的File类

java.io.File是Java抽象文件/目录的核心类,仅负责获取元数据、创建删除文件、遍历目录、处理路径,无法读取/写入文件内部数据。

也就是说new File(路径)仅创建内存对象,磁盘上对应的文件不一定真实存在。

2.1 File构造方法

构造方法功能说明
File(String pathname)传入绝对/相对路径创建对象,最常用
File(File parent, String child)父目录File对象 + 子文件/目录名
File(String parent, String child)父目录字符串 + 子名称,拼接路径

2.2 File核心API

2.2.1 路径信息获取

方法返回值作用
getName()String获取纯文件名/目录名,不含父路径
getParent()String获取父目录路径字符串
getPath()String获取构造File时传入的原始路径(相对/绝对原样返回)
getAbsolutePath()String获取完整绝对路径,不简化..、.符号
getCanonicalPath()String规范绝对路径,自动解析..、.,推荐打印日志使用
File file = new File("./helloworld.txt");
System.out.println(file.getName());// helloworld.txt
System.out.println(file.getPath());// .helloworld.txt
System.out.println(file.getParent());// .
System.out.println(file.getAbsolutePath());
// D:Codecode-IDEAEETestJ20260707IO.helloworld.txt
System.out.println(file.getCanonicalPath());
// D:Codecode-IDEAEETestJ20260707IOhelloworld.txt

..会被getCanonicalPath()解析为上一级目录,输出干净无冗余符号的完整路径。

2.2.2 文件状态判断

方法返回值功能
exists()boolean判断磁盘文件/目录是否真实存在
isFile()boolean判断是否为普通文件(非目录)
isDirectory()boolean判断是否为文件夹
canRead()/canWrite()boolean判断读写权限

2.2.3 文件与目录创建删除

方法返回值功能
createNewFile()boolean仅创建空普通文件;父目录不存在、文件已存在、无权限、空间不足均创建失败返回false,文件不存在则创建成功返回true
mkdir()boolean仅创建路径最后一级目录,中间父目录不存在时直接创建失败
mkdirs()boolean递归自动创建所有缺失的中间父目录,开发中优先使用
delete()boolean立即删除文件或空目录;非空目录无法直接删除,需先递归清空内部所有文件
deleteOnExit()void标记文件,程序正常退出时自动删除,多用于临时缓存文件

2.2.4 目录遍历与重命名

方法返回值功能
list()String[]返回目录下所有子文件名称字符串数组;若路径不是目录,返回null
listFiles()File[]返回目录下所有子文件对应的File对象数组,支持递归深度遍历
renameTo(File dest)boolean传入目标File对象;同层级路径为重命名,不同层级为移动文件;跨磁盘分区仅能复制后删除,无法直接移动

三、字节流 InputStream / OutputStream

File类只能操作文件属性,读写文件内容依靠IO流

IO流分为两大体系:

  • 字节流:处理所有文件
  • 字符流:仅处理文本文件。

3.1 IO流基础概念

流是数据传输的管道,类比水管:

  • 输入流(InputStream):从文件读取数据到程序,相当于接水;
  • 输出流(OutputStream):程序写入数据到文件,相当于放水;

字节流以 字节(byte,0~255) 为最小操作单位,支持图片、视频、程序、文本所有文件类型,通用性最强。

3.2 字节输入流 InputStream

InputStream顶层抽象父类,无法实例化,文件读取使用子类FileInputStream

  • FileInputStream构造方法
签名说明
FileInputStream(File file)利用 File 对象构造文件输入流
FileInputStream(String name)利用文件路径字符串构造文件输入流
  • FileInputStreamAPI
方法签名功能说明特殊返回/注意事项
int read()读取单个字节返回字节数值;文件读取完毕返回 -1(结束标记)
int read(byte[] buf)批量读取字节存入数组返回本次实际读取的字节长度;无剩余数据返回 -1
int read(byte[] buf, int off, int len)从数组下标 off 位置开始,最多读取 len 个字节存入缓冲区读取完成同样返回实际读取长度,无数据返回 -1
void close()关闭IO流,释放操作系统文件句柄资源必须调用,否则会产生资源泄漏问题

打开与关闭文件

创建一个FileInputStream对象相当于打开文件,因此这个文件必须存在,否则会抛出FileNotFoundException异常。与打开文件对应的是关闭文件。打开文件类似于申请内存,关闭文件相当于释放内存。

对于JVM来说只需要申请内存,释放内存可以交给GC处理。而文件资源与内存资源不同,JVM可以自动管理内存,但是不可以管理文件。如果不手动关闭就会涉及到文件资源泄露。

程序每打开一个文件,就会在文件描述符表中申请一个表项,如果只申请不关闭,表项很快就会消耗完,后续再打开文件就会失败。

为了防止复杂逻辑导致关闭文件没有执行到,可以使用try-finally语句:

public static void main(String[] args) throws IOException {
        InputStream in = null;
        try{
            in= new FileInputStream("./test.txt");
        }finally {
            in.close();
        }
    }

这段代码功能达到了,但是代码美观性不够。更推荐使用try-with-resources语法,只要实现AutoCloseable接口代码块结束自动调用close,无论是否异常。

 public static void main(String[] args) throws IOException {
        try(InputStream in = new FileInputStream("./test.txt")){
            //读取文件
        }
    }

ReentrantLock不能用类似的方式,因为类必须实现AutoCloseable接口

两种读取方式

  1. 单字节read():循环一次读取1字节.
    public static void main(String[] args) throws IOException {
        try(InputStream in = new FileInputStream("./test.txt")){
            //一次读一个字节
            while(true){
                int data = in.read();
                if(data==-1)
                    break;
                System.out.printf("%xn",data);
            
        }
    }

这种方式IO交互次数极多,读取大文件性能极差,仅适用于极小文件;

  1. 字节数组缓冲读取:一次性读取指定的字节数
public static void main(String[] args) throws IOException {
        try(InputStream in = new FileInputStream("./test.txt")){
            //一次读多个字节
            while(true) {
                //读一次尽可能把字节数组填满,n表示实际读到的字节数
                byte[] data = new byte[1024];
                int n = in.read(data);
                if(n==-1)
                    break;
                for (int i = 0; i < n; i++) {
                    System.out.printf("%x ", data[i]);
                }
            }
        }
    }

这中写法用参数作为函数的返回值,称为“输出型函数”。可以减少磁盘IO次数,是生产环境标准写法。

提出输出型参数的概念归根结底是语法的限制,函数只能返回一个值,而read()希望返回长度和读入的字节数组;像go,python这样的语言支持返回多个值,就不需要考虑输出型函数的问题。

3.3 字节输出流 OutputStream

文件写入使用子类FileOutputStream

方法签名功能说明特殊返回/注意事项
void write(int b)写入单个字节传入int数值,仅低8位有效;超出字节范围会自动截断
void write(byte[] b)写入完整字节数组将数组内全部字节一次性写出,缓冲区未满时数据暂存内存
void write(byte[] b, int off, int len)从数组下标off位置开始,最多写出len个字节到流需保证off、len不越界数组,否则抛出数组下标越界异常
void flush()刷新缓冲区强制将缓冲区暂存数据写入目标;带缓冲输出流必须调用,否则数据丢失
void close()关闭IO流,释放操作系统文件句柄资源关闭前会自动执行一次flush;必须调用,否则产生资源泄漏

覆盖写与追加写

对于OutputStream默认创建不存在的文件,但是会清空文件内容,重新写文件,这是操作系统的特性,其他语言也有类似的效果。

FileOutputStream构造方法第二个布尔参数控制写入模式:

  • new FileOutputStream("a.txt"):默认覆盖,清空原有内容再写入;
  • new FileOutputStream("a.txt", true):追加模式,在文件末尾新增内容。

缓冲区与flush()

磁盘IO速度远低于内存,OutputStream内置内存缓冲区:写入数据先暂存内存,缓冲区填满才批量写入磁盘,减少IO次数提升性能。

隐患:程序未写满缓冲区直接结束,内存残留数据丢失。解决方案:写入完成调用flush()强制将缓冲区数据刷入磁盘;close()关闭前会自动执行flush。

四、字符流与转换流

字节流读写中文需要手动编码转换,操作繁琐;字符流以字符char为单位,原生适配文本,底层依赖字节流+字符集转换。

4.1 转换流:字节流与字符流桥梁

InputStreamReaderOutputStreamWriter是转换流核心,手动指定UTF-8编码,避免系统默认编码不一致导致乱码。

  • InputStreamReaderFileInputStream → 字符输入流;
  • OutputStreamWriterFileOutputStream → 字符输出流。

4.2 PrintWriter:便捷文本输出工具

PrintWriter封装转换流,提供print()println()printf()等和控制台输出一致的方法,开发文本日志、配置文件首选。

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;

public class PrintWriterTest {
    public static void main(String[] args) {
        try (FileOutputStream fos = new FileOutputStream("log.txt");
             OutputStreamWriter osw = new OutputStreamWriter(fos, "UTF-8");
             PrintWriter writer = new PrintWriter(osw)) {
            writer.println("第一行日志数据");
            writer.printf("数字:%d,字符串:%s%n", 100, "IO测试");
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

4.3 Scanner读取文本文件

读取文本简易方案,封装字符解码,直接按分隔符读取字符串,适合简单文本解析:

import java.io.FileInputStream;
import java.io.IOException;
import java.util.Scanner;

public class ScannerReadTest {
    public static void main(String[] args) throws IOException {
        try (FileInputStream fis = new FileInputStream("hello.txt");
             Scanner scanner = new Scanner(fis, "UTF-8")) {
            while (scanner.hasNextLine()) {
                String line = scanner.nextLine();
                System.out.println(line);
            }
        }
    }
}

五、案例演示

1. 递归扫描目录,检索指定名称文件并支持删除

输入扫描目录、检索关键词,递归遍历所有子文件夹,匹配文件名的文件列出,询问用户是否删除。

import java.io.File;
import java.util.Scanner;

public class demo06 {
    static Scanner scan = new Scanner(System.in);
    public static void main(String[] args) {
        System.out.println("输入要搜索的目录:");
        String rootDir = scan.next();
        File rootFile = new File(rootDir);
        if(!rootFile.isDirectory()){
            System.out.println("输入的路径不是目录!");
            return;
        }else if(!rootFile.exists()){
            System.out.println("文件不存在!");
            return;
        }
        
        System.out.println("请输入待检索的关键字:");
        String key = scan.next();
        scanFiles(rootFile,key);
    }

    private static void scanFiles(File rootFile, String key) {
        // 得到目录里的所有文件
        File[] files = rootFile.listFiles();
        if(files==null)
            return;

        for(File file : files){
            if(file.isDirectory()){
                scanFiles(file,key);
            }else{
                if(file.getName().contains(key)){
                    System.out.println("检索到含 "+key+" 的文件:"+file.getAbsolutePath()+",是否删除 Y/N");
                    String input = scan.next();
                    if("Y".equals(input)){
                        file.delete();
                        System.out.println("文件已删除");
                    }
                }
            }
        }
    }
}

2. 通用文件复制工具

输入源文件、目标路径,判断文件是否存在,使用字节缓冲区复制。

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

public class demo07 {
    static Scanner scan = new Scanner(System.in);
    public static void main(String[] args) throws IOException {
        System.out.println("请输入源文件路径:");
        String srcPath = scan.nextLine();
        File srcFile = new File(srcPath);
        if(!srcFile.isFile()){
            System.out.println("源文件不存在或不是文件!");
            return;
        }
        System.out.println("请输入目的文件路径:");
        String destPath = scan.nextLine();
        File destFile = new File(destPath);
        if(!destFile.getParentFile().isDirectory()){
            System.out.println("目的地址的路径不存在!");
            return;
        }

        try(InputStream in = new FileInputStream(srcFile);
            OutputStream out = new FileOutputStream(destFile)){
            int i = 0;
            while(true){
                byte[] data = new byte[1024];
                int n = in.read(data);
                if(n==-1)
                    break;
                //data不一定填满,要按照实际读入的长度写
                out.write(data,0,n);
                System.out.println(i);
                i++;
            }
            System.out.println("复制完成!");
        }
    }
}

3. 检索目录中文件名/文件内容包含关键词的文本文件

需求:递归遍历目录,不仅匹配文件名,还读取文本内容,内容包含关键词也筛选出来,适合日志检索。

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

public class SearchContentTool {
    private static List<File> result = new ArrayList<>();
    public static void main(String[] args) throws IOException {
        Scanner input = new Scanner(System.in);
        System.out.print("扫描目录:");
        String root = input.next();
        File rootDir = new File(root);
        if (!rootDir.isDirectory()) return;
        System.out.print("检索关键词:");
        String key = input.next();
        scanAll(rootDir, key);
        System.out.println("匹配文件总数:" + result.size());
        for (File f : result) {
            System.out.println(f.getCanonicalPath());
        }
    }

    private static void scanAll(File dir, String key) throws IOException {
        File[] files = dir.listFiles();
        if (files == null) return;
        for (File f : files) {
            if (f.isDirectory()) {
                scanAll(f, key);
            } else {
                // 文件名匹配 或 文件内容匹配
                if (f.getName().contains(key) || contentMatch(f, key)) {
                    result.add(f);
                }
            }
        }
    }

    // 判断文件内容是否包含关键词
    private static boolean contentMatch(File file, String keyword) throws IOException {
        StringBuilder sb = new StringBuilder();
        try (InputStream is = new FileInputStream(file);
             Scanner sc = new Scanner(is, "UTF-8")) {
            while (sc.hasNextLine()) {
                sb.append(sc.nextLine());
            }
        }
        return sb.toString().contains(keyword);
    }
}

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持本站。

您可能感兴趣的文章:
  • Java文件操作与IO流详解(File类+字节流+字符流)
  • 一起来了解Java的File类和IO流
  • Java文件操作之IO流 File类的使用详解
  • Java中IO流 RandomAccessFile类实例详解
  • Java IO流 File类的常用API实例

热门栏目