Java:File类、递归、字符集、IO流体系及Commons-io框架

Java:File类、递归、字符集、IO流体系及Commons-io框架

1. File类:文件与目录的操作入口

1.1 File类的作用与创建对象

File类是Java操作文件和目录的基础,它仅表示文件/目录的路径信息(不直接操作文件内容),通过路径定位文件系统中的资源。

创建File对象:绝对路径与相对路径
  • 绝对路径:从盘符开始的完整路径,如 C:/Users/笔记.txt(Windows)或 /home/user/笔记.txt(Linux/Mac),定位唯一且固定
  • 相对路径:从程序运行目录(当前工作目录)出发的路径,如 ./src/main/java./ 表示当前目录),移植性强(项目移动时无需修改路径)。

代码案例

import java.io.File;

public class FileDemo {
    public static void main(String[] args) {
        // 1. 绝对路径创建File对象(Windows系统示例)
        File absoluteFile = new File("C:/Users/Desktop/学习计划.txt");
        System.out.println("绝对路径是否存在:" + absoluteFile.exists()); // 判断文件是否存在

        // 2. 相对路径创建File对象(假设当前工作目录是项目根目录)
        // ./ 表示当前目录,可省略;src是项目下的源代码目录
        File relativeFile = new File("src/main/resources/data.txt"); 
        System.out.println("相对路径文件名称:" + relativeFile.getName()); // 获取文件名
    }
}

1.2 操作文件和目录的常用方法

方法名作用示例场景
createNewFile()创建新文件(若不存在)创建日志文件
mkdir()创建单级目录创建"temp"临时目录
mkdirs()创建多级目录(如 a/b/c创建嵌套文件夹结构
delete()删除文件/空目录(非空目录需先删内容)清理临时文件
exists()判断文件/目录是否存在读取文件前检查存在性
isFile()/isDirectory()判断是否为文件/目录遍历目录时区分文件和子目录
getName()/getPath()获取文件名/路径显示文件列表
length()获取文件大小(字节数)检查文件是否为空

1.3 listFiles方法的注意事项

listFiles() 用于获取目录下的所有文件/子目录,返回 File[] 数组。注意事项

  1. 必须是目录:若调用者是文件或不存在,返回 null,需先判断 isDirectory()
  2. 权限问题:若目录无访问权限,返回 null
  3. 性能考虑:目录下文件过多时,数组占用内存较大,建议分批处理。

代码案例

public class ListFilesDemo {
    public static void main(String[] args) {
        File dir = new File("C:/Users/Desktop");
        if (dir.isDirectory()) { // 先判断是否为目录
            File[] files = dir.listFiles(); // 获取目录下所有文件/子目录
            if (files != null) { // 避免null指针异常
                for (File file : files) {
                    // 区分文件和目录,显示名称和类型
                    String type = file.isFile() ? "文件" : "目录";
                    System.out.println(type + ":" + file.getName());
                }
            }
        }
    }
}

2. 递归:自身调用解决层级问题

2.1 什么是递归?

递归是方法自身调用自身的编程技巧,用于解决具有重复子问题层级结构的问题(如目录遍历、树形结构处理)。
形式:方法内部包含对自身的调用;
注意事项:必须有终结条件,否则会导致栈溢出(StackOverflowError)。

2.2 递归三要素

  1. 公式:递归问题的分解(f(n) = n + f(n-1));
  2. 终结点:停止递归的条件(如 n == 0 时返回 0);
  3. 走向终结点:每次递归需让参数接近终结点(如 n 逐渐减小)。

2.3 案例:根据文件名称查找文件

需求:递归遍历指定目录下所有文件,找到名称包含“笔记”的文件。
代码案例

public class FileSearchByRecursion {
    public static void main(String[] args) {
        File dir = new File("C:/Users/Desktop"); // 起始目录
        searchFile(dir, "笔记"); // 调用递归方法查找包含"笔记"的文件
    }

    /**
     * 递归查找文件
     * @param dir 要遍历的目录
     * @param keyword 文件名关键字
     */
    private static void searchFile(File dir, String keyword) {
        // 终结条件1:目录不存在,直接返回
        if (dir == null || !dir.exists()) return;
        
        // 终结条件2:若当前是文件,判断是否包含关键字
        if (dir.isFile()) {
            if (dir.getName().contains(keyword)) {
                System.out.println("找到文件:" + dir.getAbsolutePath());
            }
            return; // 文件无需继续递归,返回
        }

        // 若当前是目录,获取所有子文件/子目录
        File[] files = dir.listFiles();
        if (files == null) return; // 权限不足或为空目录,返回

        // 递归处理每个子文件/子目录(走向终结点:层级逐渐深入)
        for (File file : files) {
            searchFile(file, keyword); // 自身调用,处理子元素
        }
    }
}

执行流程:从起始目录开始,若为目录则遍历所有子元素,若为文件则检查名称,直到遍历完所有层级。

3. 字符集:字符与字节的映射规则

3.1 什么是字符集?

字符集(Charset)是字符与二进制字节的对应规则表,用于解决“如何用计算机存储文字”的问题。不同字符集支持的字符范围和编码方式不同,错误使用会导致乱码

3.2 常见字符集介绍

字符集起源与特点支持语言存储一个汉字所需字节
ASCII美国标准,仅包含英文字母、数字和符号英语不支持汉字
GBK中国国家标准,兼容ASCII,扩展支持中文中文、英文2字节
Unicode国际标准,包含全球所有语言字符所有语言2字节(固定长度)
UTF-8Unicode的可变长度实现,节省空间所有语言1-4字节(中文通常3字节)

3.3 UTF-8编码方案

UTF-8是目前最广泛使用的字符集,特点:

  • 可变长度:根据字符范围使用1-4字节(英文字母1字节,中文3字节,生僻字4字节);
  • 兼容ASCII:ASCII字符(0-127)用1字节表示,与ASCII完全兼容;
  • 无字节序问题:无需BOM(字节顺序标记),适合网络传输和文件存储。

3.4 编码与解码

  • 编码String → byte[],将字符转换为字节(需指定字符集);
  • 解码byte[] → String,将字节转换为字符(需与编码字符集一致,否则乱码)。

代码案例

public class CharsetDemo {
    public static void main(String[] args) throws UnsupportedEncodingException {
        String str = "你好,Java!";
        
        // 1. 编码:字符串 → 字节数组(使用UTF-8)
        byte[] utf8Bytes = str.getBytes("UTF-8"); 

        // 2. 解码:字节数组 → 字符串(使用UTF-8,与编码一致)
        String utf8Str = new String(utf8Bytes, "UTF-8");
        System.out.println("UTF-8解码结果:" + utf8Str); // 结果:你好,Java!

        // 3. 错误示例:用GBK解码UTF-8字节(乱码)
        String gbkStr = new String(utf8Bytes, "GBK");
        System.out.println("GBK错误解码结果:" + gbkStr); // 结果:浣犲ソ锛屽璞★紒(乱码)
    }
}

4. IO流:数据传输的管道

4.1 认识IO流

IO流(Input/Output Stream)是Java用于读写数据的管道,数据从数据源(如文件、网络)通过“流”传输到程序,或从程序传输到目标(如文件、控制台)。

4.2 IO流的分类

按数据流向分:
  • 输入流:数据从外部进入程序(读操作,如InputStreamReader);
  • 输出流:数据从程序到外部(写操作,如OutputStreamWriter)。
按数据单位分:
  • 字节流:以字节(1byte)为单位传输,可处理所有类型数据(文本、图片、视频等);
  • 字符流:以字符为单位传输,仅处理文本数据(需指定字符集,避免乱码)。
按流的角色分:
  • 节点流:直接连接数据源/目标的流(如FileInputStream直接读文件);
  • 处理流:包装节点流,增强功能(如缓冲流提升性能,转换流处理字符集)。

4.3 IO流体系与实现类

核心父类

  • 字节流:InputStream(输入)、OutputStream(输出);
  • 字符流:Reader(输入)、Writer(输出)。

常用实现类

流类型节点流处理流
字节输入流FileInputStreamBufferedInputStream(缓冲)
字节输出流FileOutputStreamBufferedOutputStream(缓冲)
字符输入流FileReaderBufferedReader(缓冲)、InputStreamReader(转换)
字符输出流FileWriterBufferedWriter(缓冲)、OutputStreamWriter(转换)

5. 字节流:处理所有类型数据

5.1 字节输入流(InputStream)

作用:从数据源读取字节数据(如文件、网络)。
核心方法

  • read():读取单个字节(返回字节值,-1表示结束);
  • read(byte[] b):读取多个字节到数组(返回实际读取字节数,-1表示结束);
  • close():关闭流,释放资源。

使用步骤

  1. 创建流对象(关联数据源);
  2. 调用read()读取数据;
  3. 关闭流(必须释放资源)。

代码案例:读取图片文件(字节流处理非文本数据)

public class FileInputStreamDemo {
    public static void main(String[] args) {
        InputStream is = null;
        try {
            // 1. 创建字节输入流对象,关联图片文件
            is = new FileInputStream("C:/Users/Desktop/pic.jpg");
            
            // 2. 读取数据(使用byte数组缓冲,减少IO次数)
            byte[] buffer = new byte[1024]; // 每次读取1024字节(1KB)
            int len; // 记录实际读取的字节数
            while ((len = is.read(buffer)) != -1) { // 循环读取,直到返回-1(文件结束)
                System.out.println("读取到" + len + "字节数据");
                // 此处可处理数据(如写入输出流)
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 3. 关闭流(必须在finally中执行,确保异常时也能关闭)
            if (is != null) { // 避免null指针异常
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

5.2 字节输出流(OutputStream)

作用:将字节数据写入目标(如文件、网络)。
核心方法

  • write(int b):写入单个字节;
  • write(byte[] b):写入字节数组;
  • write(byte[] b, int off, int len):写入数组的部分字节(从off开始,共len个);
  • close():关闭流。

代码案例:写入文本到文件(字节流处理文本需注意字符集)

public class FileOutputStreamDemo {
    public static void main(String[] args) {
        OutputStream os = null;
        try {
            // 1. 创建字节输出流对象,关联目标文件(append: true表示追加内容,false表示覆盖)
            os = new FileOutputStream("C:/Users/Desktop/log.txt", true);
            
            // 2. 写入数据(字符串需先编码为字节数组,指定UTF-8字符集)
            String content = "程序启动成功!\n";
            byte[] data = content.getBytes("UTF-8"); // 编码为字节数组
            os.write(data); // 写入字节数组
            
            System.out.println("写入完成");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 3. 关闭流
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

5.3 案例:文件复制(字节流经典应用)

需求:将一张图片从C:/source.jpg复制到D:/target.jpg
代码案例

public class FileCopyByByteStream {
    public static void main(String[] args) {
        // 1. 声明输入流和输出流(作用域需覆盖try-catch-finally)
        InputStream is = null;
        OutputStream os = null;
        
        try {
            // 2. 创建流对象,关联源文件和目标文件
            is = new FileInputStream("C:/source.jpg");
            os = new FileOutputStream("D:/target.jpg");
            
            // 3. 缓冲数组(一次读取1024KB,提升效率)
            byte[] buffer = new byte[1024 * 1024]; // 1MB缓冲
            int len; // 实际读取字节数
            
            // 4. 循环读取并写入
            while ((len = is.read(buffer)) != -1) {
                os.write(buffer, 0, len); // 写入实际读取的字节(避免写入缓冲数组中的无效数据)
            }
            System.out.println("文件复制完成!");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            // 5. 关闭流(先关输出流,再关输入流,避免资源泄漏)
            if (os != null) {
                try {
                    os.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (is != null) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

5.4 资源释放方案

资源:实现了CloseableAutoCloseable接口的对象(如流、数据库连接),需手动关闭,否则会导致资源泄漏。

方案1:try-catch-finally(传统方式)

如上述案例,在finally中关闭流,确保异常时也能释放资源。

方案2:try-with-resource(JDK 7+,推荐)

语法:将资源声明在try()中,程序结束后自动关闭资源,无需手动close()
代码案例

public class TryWithResourceDemo {
    public static void main(String[] args) {
        // 资源声明在try()中,自动关闭(多个资源用;分隔)
        try (InputStream is = new FileInputStream("C:/source.jpg");
             OutputStream os = new FileOutputStream("D:/target.jpg")) {
            
            byte[] buffer = new byte[1024];
            int len;
            while ((len = is.read(buffer)) != -1) {
                os.write(buffer, 0, len);
            }
            System.out.println("复制完成(try-with-resource方式)");
            
        } catch (IOException e) { // 无需finally,资源自动关闭
            e.printStackTrace();
        }
    }
}

6. 字符流:专门处理文本数据

6.1 字符输入流(Reader)

作用:按字符读取文本数据(自动处理字节→字符的转换,需指定字符集)。
常用实现类FileReader(简单文本读取)、BufferedReader(缓冲字符流,提升性能)。

核心方法

  • read():读取单个字符(返回字符的Unicode值,-1表示结束);
  • read(char[] cbuf):读取字符到数组;
  • close():关闭流。

代码案例:用FileReader读取文本文件

public class FileReaderDemo {
    public static void main(String[] args) {
        // try-with-resource方式声明流,自动关闭
        try (Reader reader = new FileReader("C:/Users/Desktop/note.txt", StandardCharsets.UTF_8)) {
            char[] buffer = new char[1024]; // 字符数组缓冲
            int len;
            while ((len = reader.read(buffer)) != -1) {
                // 将字符数组转换为字符串(取前len个有效字符)
                String content = new String(buffer, 0, len);
                System.out.print(content); // 打印读取内容
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

6.2 字符输出流(Writer)

作用:按字符写入文本数据(自动处理字符→字节的转换,需指定字符集)。
常用实现类FileWriter(简单文本写入)、BufferedWriter(缓冲字符流)。

核心方法

  • write(int c):写入单个字符;
  • write(char[] cbuf):写入字符数组;
  • write(String str):写入字符串(最常用);
  • flush():刷新缓冲区(将内存数据强制写入目标);
  • close():关闭流(会自动调用flush())。

注意事项flush()close()的区别

  • flush():仅刷新缓冲区,流可继续使用(如多次写入后需立即保存);
  • close():刷新缓冲区+关闭流,流不可再使用。

代码案例:用FileWriter写入文本

public class FileWriterDemo {
    public static void main(String[] args) {
        try (Writer writer = new FileWriter("C:/Users/Desktop/log.txt", StandardCharsets.UTF_8, true)) {
            writer.write("用户登录成功\n"); // 写入字符串
            writer.write("操作时间:" + LocalDateTime.now() + "\n");
            
            // 若需立即保存(如日志实时写入),调用flush()
            writer.flush(); // 强制刷新缓冲区,确保数据写入文件
            System.out.println("写入完成");
        } catch (IOException e) {
            e.printStackTrace();
        }
        // 无需close(),try-with-resource自动关闭(会先flush())
    }
}

7. 缓冲流:提升IO性能的利器

7.1 缓冲流的作用与原理

作用:通过内存缓冲区减少物理IO次数,提升读写性能(尤其是大文件操作)。
原理:普通流每次读写1个字节/字符(频繁访问磁盘),缓冲流先将数据读入内存缓冲区(如8KB),缓冲区满后再一次性读写,减少磁盘访问次数。

7.2 缓冲字节流(BufferedInputStream/BufferedOutputStream)

使用方式:包装普通字节流,无额外API,用法与普通字节流一致。
代码案例:缓冲流复制大文件(性能对比普通流提升显著)

public class BufferedByteStreamDemo {
    public static void main(String[] args) {
        try (// 缓冲流包装节点流
             InputStream is = new BufferedInputStream(new FileInputStream("C:/largefile.zip"));
             OutputStream os = new BufferedOutputStream(new FileOutputStream("D:/copy.zip"))) {
            
            byte[] buffer = new byte[1024 * 1024]; // 1MB缓冲(缓冲流内部已有缓冲区,此处可进一步优化)
            int len;
            while ((len = is.read(buffer)) != -1) {
                os.write(buffer, 0, len);
            }
            System.out.println("大文件复制完成(缓冲字节流)");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

7.3 缓冲字符流(BufferedReader/BufferedWriter)

特有方法

  • BufferedReader.readLine():读取一行文本(不含换行符,返回null表示结束);
  • BufferedWriter.newLine():写入系统兼容的换行符(Windows:\r\n,Linux:\n)。

代码案例:读取文本文件并按行处理

public class BufferedCharStreamDemo {
    public static void main(String[] args) {
        try (BufferedReader br = new BufferedReader(new FileReader("C:/poem.txt", StandardCharsets.UTF_8));
             BufferedWriter bw = new BufferedWriter(new FileWriter("C:/poem_copy.txt", StandardCharsets.UTF_8))) {
            
            String line; // 存储每行内容
            // 按行读取(readLine()返回null时结束)
            while ((line = br.readLine()) != null) {
                System.out.println("读取行:" + line);
                bw.write(line); // 写入行内容
                bw.newLine(); // 写入换行符(跨平台兼容)
            }
            System.out.println("文本复制完成(缓冲字符流)");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

7.4 提升IO性能的总结

  1. 使用缓冲流:优先选择BufferedXXX系列,减少物理IO次数;
  2. 合理设置缓冲区大小:默认8KB,大文件可适当增大(如1MB);
  3. 使用数组缓冲:读取时用byte[]/char[],减少循环次数;
  4. 减少流的创建次数:避免在循环中创建流对象;
  5. 及时关闭流:释放资源,避免占用系统句柄。

8. 其他流:特殊场景的解决方案

8.1 字符输入转换流(InputStreamReader)

作用:将字节输入流转换为字符输入流,并指定字符集(解决文本文件乱码问题)。
场景:读取GBK编码的文本文件(FileReader默认使用系统字符集,可能乱码)。

代码案例:读取GBK编码文件

public class InputStreamReaderDemo {
    public static void main(String[] args) {
        try (// 字节流→字符流转换,指定GBK字符集
             InputStreamReader isr = new InputStreamReader(new FileInputStream("C:/gbk_file.txt"), "GBK");
             BufferedReader br = new BufferedReader(isr)) { // 包装为缓冲流提升性能
            
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println("GBK文件内容:" + line); // 正确解码,无乱码
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

8.2 打印流(PrintStream/PrintWriter)

作用:方便输出各种数据类型(如intString对象),自动转换为字符串,无需手动编码。
特点

  • PrintStream:字节流,默认输出到控制台(System.out就是PrintStream);
  • PrintWriter:字符流,可指定字符集,支持写入文件。

代码案例:用PrintWriter写入日志

public class PrintWriterDemo {
    public static void main(String[] args) {
        try (PrintWriter pw = new PrintWriter("C:/app.log", StandardCharsets.UTF_8)) {
            pw.println("===== 系统日志 ====="); // 写入字符串(自动换行)
            pw.printf("启动时间:%s%n", LocalDateTime.now()); // 格式化输出(%n是跨平台换行符)
            pw.println("状态:正常运行");
            pw.flush(); // 立即写入(或使用autoFlush参数)
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

8.3 数据输入输出流(DataInputStream/DataOutputStream)

作用:读写基本数据类型(如intdoubleboolean),保持数据类型信息(普通字节流仅读写字节,丢失类型)。
特点:收/发必须对应(写入顺序与读取顺序一致)。

代码案例:写入并读取基本数据类型

public class DataStreamDemo {
    public static void main(String[] args) throws IOException {
        // 写入基本数据类型
        try (DataOutputStream dos = new DataOutputStream(new FileOutputStream("data.bin"))) {
            dos.writeInt(2024); // 写入int(4字节)
            dos.writeDouble(3.14); // 写入double(8字节)
            dos.writeBoolean(true); // 写入boolean(1字节)
        }

        // 读取基本数据类型(顺序必须与写入一致)
        try (DataInputStream dis = new DataInputStream(new FileInputStream("data.bin"))) {
            int year = dis.readInt();
            double pi = dis.readDouble();
            boolean flag = dis.readBoolean();
            System.out.println("读取结果:" + year + ", " + pi + ", " + flag); // 结果:2024, 3.14, true
        }
    }
}

9. Commons-io框架:简化IO操作

9.1 框架介绍

Commons-io是Apache提供的IO工具类库,封装了Java原生IO的复杂操作,提供简洁API,减少重复代码。
依赖方式:Maven项目添加依赖:

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.15.1</version> <!-- 最新版本 -->
</dependency>

手动添加:https://commons.apache.org/proper/commons-io/download_io.cgi下载解压后在项目创建lib目录,导入jar包即可

9.2 常用方法(FileUtils类)

方法名作用原生IO实现复杂度
copyFile(File src, File dest)复制文件需手动处理流、缓冲
deleteDirectory(File dir)删除目录(含所有子文件/子目录)需递归删除
readFileToString(File file, Charset charset)读取文件内容为字符串需字符流+缓冲+编码
writeStringToFile(File file, String data, Charset charset)写入字符串到文件需字符流+编码
listFiles(File dir, String[] extensions)按扩展名筛选文件需遍历+字符串判断

代码案例:用Commons-io复制文件

import org.apache.commons.io.FileUtils;
import java.io.File;
import java.nio.charset.StandardCharsets;

public class CommonsIODemo {
    public static void main(String[] args) {
        try {
            File src = new File("C:/source.txt");
            File dest = new File("D:/dest.txt");
            
            // 1. 复制文件(一行代码,无需处理流)
            FileUtils.copyFile(src, dest);
            System.out.println("文件复制完成(Commons-io)");
            
            // 2. 读取文件内容为字符串(指定UTF-8)
            String content = FileUtils.readFileToString(src, StandardCharsets.UTF_8);
            System.out.println("文件内容:" + content);
            
            // 3. 写入字符串到文件
            FileUtils.writeStringToFile(dest, "追加内容", StandardCharsets.UTF_8, true); // true表示追加
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

优势:Commons-io将原生IO的“创建流→缓冲→读写→关闭流”等步骤封装为单方法调用,极大简化代码,减少出错概率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值