Java I/O

Java I/O 操作

I/O 概述

I/O(Input/Output)即输入/输出,用于程序与外部设备(如文件、磁盘、网络)之间的数据传输。程序中的数据默认存储在内存中,程序终止后会丢失,通过 I/O 操作可将数据永久保存到外部存储设备,或从外部设备读取数据供程序使用。

Java 提供了丰富的 I/O 类库,核心分为两大类:

  • 按数据类型:字符流(处理文本数据,如 Reader/Writer)、字节流(处理二进制数据,如 InputStream/OutputStream);
  • 按操作方向:输入流(读取数据)、输出流(写入数据)。

File 类:文件与目录的抽象表示

java.io.File 类封装了文件或目录的路径信息,提供了文件/目录的属性操作(如创建、删除、重命名、查询属性),但不包含读写文件内容的方法

File 类的构造方法

构造方法 说明
File(String pathname) 根据指定路径名(文件或目录)创建 File 对象
File(String parent, String child) 根据父目录路径(字符串)和子路径(文件/目录)创建 File 对象
File(File parent, String child) 根据父目录 File 对象和子路径(文件/目录)创建 File 对象

File 类核心方法

方法名 返回值 说明
exists() boolean 判断文件/目录是否存在
canRead() boolean 判断文件/目录是否存在且可读
canWrite() boolean 判断文件/目录是否存在且可写
isDirectory() boolean 判断是否为目录
isFile() boolean 判断是否为文件
isAbsolute() boolean 判断是否为绝对路径
isHidden() boolean 判断文件/目录是否隐藏
getAbsolutePath() String 获取绝对路径
getCanonicalPath() String 获取标准路径(去除冗余 ./..,盘符大写)
getName() String 获取文件名/目录名
getPath() String 获取完整路径(构造方法传入的路径)
getParent() String 获取父目录路径
lastModified() long 获取最后修改时间(时间戳)
length() long 获取文件大小(字节),目录返回 0
listFiles() File[] 返回目录下所有文件/子目录的 File 数组
delete() boolean 删除文件/空目录,成功返回 true
renameTo(File dest) boolean 重命名文件/目录为目标 File 对象的名称
mkdir() boolean 创建目录,父目录不存在则失败
mkdirs() boolean 创建目录,父目录不存在则一并创建(支持层级创建)
createNewFile() boolean 创建文件(需处理 IOException

File 类使用示例

假设存在目录 E:\Home_java 及文件 E:\Home_java\123.txtE:\Home_java\Welcome.java,示例代码如下:

package com.exception_io;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;

/**
 * @author Jing61
 */
public class FileDemo {

    public static void main(String[] args) {
        var file = new File("E:/Home_java/Welcome.java");
        var dir = new File("E:/Home_java");

        if(file.exists()) {
            System.out.println("文件已存在");
            System.out.println("文件名:" + file.getName());
            System.out.println("文件大小:" + file.length());
            System.out.println("文件是否可读:" + file.canRead());
            System.out.println("文件是否可写:" + file.canWrite());
            System.out.println("文件是否隐藏:" + file.isHidden());
            System.out.println("文件是否时目录:" + file.isDirectory());
            System.out.println("文件是否是按绝对路径创建:" + file.isAbsolute());
            System.out.println("文件创建时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(file.lastModified()));
            System.out.println("文件父目录:" + file.getParent());
            System.out.println("文件父目录是否是目录:" + file.getParentFile().isDirectory());
            System.out.println("文件绝对路径:" + file.getAbsolutePath());
            System.out.println("文件是否可执行:" + file.canExecute());
        } else {
            System.out.println("文件不存在");
        }

        if(dir.exists()) {
            System.out.println("目录已存在");
            System.out.println("目录名:" + dir.getName());
            System.out.println("目录是否可读:" + dir.canRead());
            System.out.println("目录是否可写:" + dir.canWrite());
            System.out.println("目录是否隐藏:" + dir.isHidden());
            System.out.println("目录是否是目录:" + dir.isDirectory());
            System.out.println("目录是否是按绝对路径创建:" + dir.isAbsolute());
            System.out.println("目录创建时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(dir.lastModified()));
            System.out.println("目录父目录:" + dir.getParent());
            System.out.println("目录父目录是否是目录:" + dir.getParentFile().isDirectory());
            System.out.println("目录绝对路径:" + dir.getAbsolutePath());
            System.out.println("目录是否可执行:" + dir.canExecute());
            System.out.print("目录下的文件:");
            for(File f : dir.listFiles()) {
                System.out.print(f.getName() + " ");
            }
            System.out.println();

            //删除文件
            System.out.println(new File(dir, "Welcome.java").delete());

            // 创建目录,父目录不存在时,创建失败
            var f = new File(dir, "456");
            f.mkdir();

            // 层级创建目录,当父目录也不存在时,会将父目录创建
            var f2 = new File("E:/Home_java/Welcome/Welcome/cc");
            f2.mkdirs();

            // 创建文件
            var f3 = new File(dir, "Welcome.java");
            try {
                f3.createNewFile();
                System.out.println("文件创建成功");
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

Files 工具类(NIO)

java.nio.file.Files 是 Java NIO 提供的文件操作工具类,封装了更便捷的文件/目录操作方法,支持路径 Path 类型,功能比 File 类更强大。

Files 类核心功能

  • 检查文件/目录是否存在及属性;
  • 创建、删除、移动、复制文件/目录;
  • 读写文件内容;
  • 遍历目录;
  • 获取文件属性。

Files 类使用示例

package com.exception_io;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;

/**
 * @author Jing61
 */
public class FilesDemo {
    public static void main(String[] args) throws IOException {
        /*
         * 基本文件操作
         * 1. 检查 文件/目录 是否存在,以及属性操作
         * 2. 创建文件/目录
         * 3. 删除文件/目录 Files.delete(path) / Files.deleteIfExists(path) / Files.deleteOnExit(path)
         * 4. 读写文件
         * 5. 移动文件/目录
         * 6. 获取文件属性
         * 7. 遍历目录 Files.list
         * 8. 复制文件/目录
         * 等等
         */
        // 1.
        var path1 = Paths.get("E:/Home_java/Welcome.java");
        System.out.println("文件是否存在:" + Files.exists(path1));
        System.out.println("文件是否不存在:" + Files.notExists(path1));
        System.out.println("文件是否是文件:" + Files.isRegularFile(path1));
        System.out.println("文件是否是目录:" + Files.isDirectory(path1));
        System.out.println("文件是否可读:" + Files.isReadable(path1));
        System.out.println("文件是否可写:" + Files.isWritable(path1));
        System.out.println("文件是否可执行:" + Files.isExecutable(path1));
        System.out.println("文件是否隐藏:" + Files.isHidden(path1));
        System.out.println("文件大小:" + Files.size(path1));

        // 2.
        var path2 = Paths.get("E:/Home_java/Peppa.java");
        if(!Files.exists(path2)) {
            Files.createFile(path2);
        }
        var path3 = Paths.get("E:/Home_java/new");
        if(Files.notExists(path3)) {
            Files.createDirectory(path3);
        }
        var path4 = Paths.get("E:/Home_java/many_new/resume");
        if(Files.notExists(path4)) {
            Files.createDirectories(path4); // 创建层级目录
        }

        // 3.查看API即可

        // 4.
        // 写
        Files.write(Paths.get("text.txt"), "Hello, World".getBytes(StandardCharsets.UTF_8));
        List<String> con = List.of("1", "2", "3");
        Files.write(Paths.get("text.txt"), con); // 会覆盖之前的文件内容
        //读
        List<String> lines = Files.readAllLines(Paths.get("text.txt"), StandardCharsets.UTF_8);// 读取所有行, 返回List
        lines.forEach(System.out::println); 
    }
}

字符流:写入文本数据

字符流以字符(char)为单位处理数据,适用于文本文件(如 .txt.java),核心抽象类为 Writer,常用实现类有FileWriterPrintWriter等。

Writer 抽象类核心方法

  • write(String content):写入字符串;
  • write(char[] cbuf):写入字符数组;
  • flush():刷新流,将缓冲区数据写入目标(流未关闭时需手动调用);
  • close():关闭流,释放资源(会自动刷新缓冲区)。

FileWriter :基础文本写入

FileWriterWriter 的子类,直接关联文件,用于写入文本数据。注意Writer 是资源对象,不会被垃圾回收,需手动关闭或使用 try-with-resources 自动关闭。

示例 1:手动关闭流(finally 块)

package com.exception_io;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

/**
 * @author Jing61
 */
public class WriterDemo {
    public static void write(String content, File file) {
        Writer writer = null;
        try {
            writer = new FileWriter(file); // 关联目标文件
            writer.write(content); // 数据写入缓冲区(未实际写入文件)
            // writer.flush(); // 手动刷新(close() 会自动刷新,可省略)
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            // 确保流一定关闭,避免资源泄露
            if(writer != null) {
                try {
                    writer.close();
                } catch (IOException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

    public static void main(String[] args) {
        var file = new File("E:/Home_java/123.txt");
        // 多行字符串(Java 15+ 支持)
        String content = """
                peppa
                suzy
                emily
                jorge
                """;
        write(content, file);
        System.out.println("写入成功");
    }
}

示例 2:try-with-resources 自动关闭流(推荐)

try-with-resources 语法可自动关闭实现 AutoCloseable 接口的资源,简化代码且避免遗漏关闭操作:

package com.exception_io;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

/**
 * @author Jing61
 */
public class WriterDemo {
    // 推荐:使用 try-with-resources 自动关闭流
    public static void write2(String content, File file) {
        try (Writer writer = new FileWriter(file)) { // 资源声明在 try 括号内
            writer.write(content); // 无需手动 flush 和 close
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        var file = new File("E:/Home_java/123.txt");
        String content = """
                peppa
                suzy
                emily
                jorge
                """;
        write2(content, file);
        System.out.println("写入成功");
    }
}

PrintWriter :便捷打印写入

PrintWriter 提供了更灵活的文本写入方式,支持 println()(自动换行)、print()(不换行)等方法,可直接创建文件并写入。

示例:

package com.exception_io;

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

/**
 * @author Jing61
 */
public class WriterDemo {
    /**
     * PrintWriter 便捷打印输出(支持自动换行、多类型数据)
     * @param file 目标文件
     */
    public static void write3(File file) {
        try (PrintWriter writer = new PrintWriter(file)) {
            // 写入字符串(自动换行)
            writer.println("Emily 5 Female");
            writer.println("Peppa 5 Female");
            // 拼接写入(不自动换行)
            writer.print("Pedro ");
            writer.print(6); // 写入数字
            writer.println(" Male"); // 换行
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        var file = new File("E:/Home_java/123.txt");
        write3(file);
        System.out.println("写入成功");
    }
}

字符流:读取文本数据

字符流读取文本数据的核心抽象类为 Reader,常用实现类有 FileReader,结合 Scanner 可实现更灵活的读取。

Reader 抽象类核心方法

  • read():读取单个字符,返回字符的 ASCII 码(int 类型),读取到末尾返回 -1
  • read(char[] cbuf):读取字符到数组,返回实际读取的字符数,末尾返回 -1
  • close():关闭流,释放资源。

FileReader :基础文本读取

FileReaderReader 的子类,直接关联文件,用于读取文本数据。

示例 1:逐字符读取

package com.exception_io;

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

/**
 * @author Jing61
 */
public class ReaderDemo {
    public static void main(String[] args) {
        var file = new File("E:/Home_java/123.txt");

        try (var reader = new FileReader(file)) {
            int ch; // 存储读取的字符 ASCII 码
            // 逐字符读取,直到返回 -1(末尾)
            while((ch = reader.read()) != -1) { // 读到后,会自动后移,调用一次就会后移一次
                System.out.print((char)ch); // 转换为字符并打印
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

示例 2:缓冲区读取(优化性能)

逐字符读取效率较低,可通过字符数组作为缓冲区批量读取:

package com.exception_io;

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

/**
 * @author Jing61
 */
public class ReaderDemo {
    public static void main(String[] args) {
        var file = new File("E:/Home_java/123.txt");

        try (var reader = new FileReader(file)) {
            int len; // 实际读取的字符数
            char[] buffer = new char[5]; // 缓冲区大小(5 个字符)
            // 批量读取字符到缓冲区,返回实际读取的字符数
            while((len = reader.read(buffer)) != -1) {
                // 从缓冲区读取有效字符(0 到 len-1)
                System.out.print(new String(buffer, 0, len));
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

Scanner :灵活读取文本

java.util.Scanner 类可简化文本读取,支持按分隔符读取、逐行读取、读取基本数据类型等。

示例:

package com.exception_io;

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

/**
 * @author Jing61
 */
public class ReaderDemo {
    public static void input(String filename) {
        var file = new File(filename);
        try(var input = new Scanner(file)) { // Scanner 实现 AutoCloseable,支持 try-with-resources
            // 方式 1:按默认分隔符(空白字符)读取单词
            /*while(input.hasNext()) {
                System.out.println("单词:\n" + input.next());
            }*/

            // 方式 2:逐行读取
            /*while(input.hasNextLine()) {
                System.out.println("行内容:\n" + input.nextLine());
            }*/

            // 方式 3:自定义分隔符(逗号)
            input.useDelimiter(",");
            while(input.hasNext()) {
                System.out.println("按逗号分隔的内容:\n" + input.next());
            }
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        }
    }

    public static void main(String[] args) {
        String filename = "E:/Home_java/123.txt";
        input(filename);
    }
}

综合示例:复制文本文件

结合字符流的读取和写入,实现文本文件复制(使用 Scanner 读取、PrintStream 写入):

package com.exception_io;

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

/**
 * 复制文本文件:将 source 内容复制到 target
 * @author Jing61
 */
public class CopyFile {
    public static void main(String[] args) {
        var source = new File("E:/Home_java/123.txt"); // 源文件
        var target = new File("E:/Home_java/456.txt"); // 目标文件

        try (var input = new Scanner(source); var output = new PrintStream(target)) {
            // 逐行读取源文件并写入目标文件
            while(input.hasNextLine()) {
                output.println(input.nextLine());
            }
            System.out.println("文件复制成功");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
    }
}

字节流:处理二进制数据

字节流以字节(byte)为单位处理数据,适用于二进制文件(如图片、视频、音频、.class 文件),核心抽象类为 InputStream(输入)和 OutputStream(输出)。

字节流与字符流的区别

特性 字节流 字符流
处理单位 字节(1 byte = 8 bit) 字符(1 char = 2 byte,Unicode 编码)
适用场景 二进制文件(图片、视频、音频) 文本文件(.txt、.java、.csv)
核心类 InputStream/OutputStream Reader/Writer

字节流核心方法(以 OutputStream 为例)

  • write(byte[] b):写入字节数组;
  • write(byte[] b, int off, int len):写入字节数组的一部分(从 off 索引开始,共 len 个字节);
  • flush():刷新缓冲区;
  • close():关闭流,释放资源。

字节流编码相关方法

  • writeChar(char c):写入字符的 Unicode 编码(2 字节);
  • writeChars(String s):写入字符串中每个字符的 Unicode 低字节(丢弃高字节,适用于 ASCII 字符);
  • writeUTF(String s):将字符串转化成 UTF-8 格式的一串字节,然后将它们写入到输出流;
  • readUTF():读取 writeUTF() 写入的字符串(保持编码一致性)。

UTF-8 编码说明

UTF-8 是一种可变长度编码,兼容 ASCII 码:

  • ASCII 字符(编码 ≤ 0x7F):1 字节存储;
  • 中等字符(0x7F < 编码 ≤ 0x7FF):2 字节存储;
  • 复杂字符(编码 ≥ 0x7FF):3 字节存储。

适合存储包含大量 ASCII 字符的文本,比 Unicode 编码更节省空间。

posted @ 2025-11-08 10:51  Jing61  阅读(2)  评论(0)    收藏  举报