Loading... ## File&IO File 代表文件或文件夹 > File 类只能对文件本身进行操作,不能读写文件里面存储的数据 IO 流用于读写数据 --- ## File ```java File file1 = new File("D:/test.txt"); // 最常用的写法 File file2 = new File("D:\\test.txt"); File file3 = new File("D:" + File.separator + "test.txt"); // 根据文件路径创建文件对象 File file4 = new File("parent", "filename.txt"); // 根据父子路径创建文件对象 ... ``` 相对路径是将模块作为父目录 ```java File file = new File("[模块名]/file.txt") ``` --- ### 信息获取 ```java // 判断文件类型, 返回值为布尔 file.exists() // 判断是否为文件 | 是否为文件夹 file.isFile(); file.isDirectory(); // 获取文件名 file.getName(); // 获取大小 file.length(); // 获取最后修改时间 long time = file.lastModified(); // 获取路径, 根据创建 File 对象时的文件路径决定返回的相对/绝对路径 file.getPath(); // 获取绝对路径 file.getAbsolutePath(); ``` ### 创建&删除 ```java // 创建文件 file.createNewFile(); // 创建目录,(不能创建多级文件夹) file.mkdir(); // 多级创建目录 file.mkdirs(); // 删除文件/空目录 file.delete(); ``` ### 遍历 遍历一级文件夹: ```java // 遍历文件名 for(String name : dir.list()){ // 文件名 } // 遍历文件对象 for(File file : dir.listFiles()){ // 文件对象 } ``` --- ## IO 流 ### 字符集 **ASCII**,可表示 128 个字符,首位是 0 **GBK** (汉字内码扩展规范,国标)GBK 兼容 ASCII 字符集,GBK 中一个中文字符编码成两个字节的形式存储 > GBK 规定:汉字的第一个字节的第一位必须是 1 **Unicode** 字符集 (统一码,也叫万国码) Unicode 由国际组织制定,可以容纳世界上所有文字,符号的字符集 - `UTF-32` 4 字节表示一个字符 (空间浪费大) - `UTF-8` 可变长编码方案,分四个长度区:1字节,2字节,3字节,4字节 在 UTF-8 中,英文,数字等只占 1 个字节(兼容 ASCII 标准),汉字字符占用 3 个字节 UTF-8 编码与解码时会要求用第一个二进制位表示字节大小: 若只有 1 字节,则二进制位开头为 0,即 `0XXXXXXX`, 2 字节则为 `110XXXXX 10XXXXXX`,3 字节 `1110XXXX 10XXXXXX 10XXXXXX `, 4 字节 `11110XXX 10XXXXXX 10XXXXXX 10XXXXXX` --- #### 编码,解码操作 通过 String 类提供的方法,对字符编码: ```java String str = "Hi 你好"; byte[] strBytes = str.getBytes(); // 按平台默认字符集将字符串编码为一系列字节 byte[] strBytes2 = str.getBytes("UTF-8"); // 按指定字符集将字符串编码 ``` 解码: ```java byte[] strBytes = str.getBytes(); // 默认平台字符集解码 String(strBytes); // 按指定字符集解码 String(strBytes, "UTF-8"); ``` --- ### 概述 - input 输入流 - output 输出流 #### 分类 按流的方向分类: - 输入流 - 输出流 按流中数据的最小单位分: - 字节流 (适合操作所有类型的文件) - 字符流(只适合操作纯文本文件) --- 总体看来就有四大流: - 字节输入流 - 字节输出流 - 字符输入流 - 字符输出流 > 输入流是以 XX 形式读入到内存中,输出则为写出,(写出到磁盘或网络中) #### 体系 抽象类: - 字节输入/输出流:`InputStream`, `OutputStream` - 字符输入/输出流:`Reader`, `Writer` 抽象实现: - `FileInputStream` - `FileOutputStream` - `FileReader` - ... ```mermaid --- title: IO 流的体系 --- flowchart TB 1((IO 流体系)) 1 --> 2[字节流] 1 --> 3[字符流] 2 --> 4[字节输入流] 2 --> 5[字节输出流] 3 --> 6[字符输入流] 3 --> 7[字符输出流] ``` --- ## 字节流 ### 文件字节输入流 #### 读取单个字节流 逐个读取字节 ```java InputStream fis = FileInputStream("test.txt"); // 常用写法 // 每次读取一个字节 int b = fis.read(); ... // 若文件已读完,返回 -1 int bn = fis.read(); ``` 循环读取字节: > 这种方法读取性能差, 每次循环都会调用系统 IO > > 该方法没法读取中文字符 ```java InputStream fis = FileInputStream("test.txt"); int b; while ((b = fis.read()) != -1){ // 优先执行括号中的代码 System.out.print((char) b); } // 释放系统资源 fis.close(); ``` #### 读取多个字节流 单次读取多个字节: ```java InputStream fis = FileInputStream("test.txt"); byte[] buffer = new byte[3]; // 单次读取 3 个字节 int len = fis.read(buffer); // 再次读取 3 个字节,覆盖之前的 byte 数组 int len2 = fis.read(buffer); // 按读取的长度构建字符串 String(buffer, offset, length) String str = String(buffer, 0, len2); ``` 循环读取多个字节: ```java InputStream fis = FileInputStream("test.txt"); byte[] buffer = new byte[3]; int len; while((len = fis.read(buffer)) != -1){ String s = String(buffer, 0, len); } fis.close(); ``` --- #### 一次性读取完全部字节 方法一: ``` File f = new File("test.txt"); InputStream fis = FileInputStream("test.txt"); long size = f.length(); byte[] buffer = new byte[(int) size]; int len = fis.read(buffer); String str = String(buffer); fis.close(); ``` 方法二: > 该方法适用于 JDK 9 以上 ```java InputStream fis = FileInputStream("test.txt"); byte[] buffer = fis.readAllBytes(); String str = String(buffer); ``` --- ### 文件字节输出流 ```java OutputStream fos = FileOutputStream("test.txt"); // new FileOutputStream("test.txt", true); // 追加数据 fos.write(97); // 写入单字节 a fos.write("b"); // 写入单字节 b // 写入换行 fos.write("\r\n".getBytes()); // 写入中文 byte[] bytes = "你好".getBytes(); fos.write(bytes); // 按指定长度写入文件 // .write(byte[], offset, length); fos.write(bytes, 0, 6); fos.close(); ``` --- ### 文件复制 通过字节 IO 流复制文件: ```java InputStream fis = FileInputStream("girl.png"); OutputStream fos = FileOutputStream("/temp/girl.png"); byte[] buffer = new byte[1024]; //1 KB int len; while ((len = fis.read(buffer)) != -1) { fos.write(buffer, 0, len); } fis.close(); fos.close(); ``` --- ### 资源释放 如果简单的在最后使用 `.close()` 方法释放资源,会有问题。当操作 IO 流出现异常时,代码无法执行到 `.close()`,从而导致资源无法正常释放。 使用 `try-catch-finally` 释放资源,避免程序异常导致资源无法释放 ```java try { ... return; }catch (Exception e){ ... } finally { // 无论发生什么 // finally 代码块的代码最终都会被执行一次 } ``` 一个例子: ```java InputStream fis = null; OutputStream fos = null; try { fis = FileInputStream("girl.png"); fos = FileOutputStream("/temp/girl.png"); ... }catch (IOException e){ System.out.println(e.getMessage()); } finally { try{ if (fis != null) fis.close() } catch(IOException e) { System.out.println(e.getMessage()); } try{ if (fos != null) fos.close() } catch(IOException e) { System.out.println(e.getMessage()); } } ``` --- 自 JDK 7 起,使用 `try-with-resource` 会更简洁。 该语句会自动释放资源 `try` 括号中的资源 (资源对象, 及实现了 `AutoCloseable` 接口的对象) ```java try (资源1, 资源2, ...) { ... } catch (IOException e) { ... } ``` --- ## 字符流 字符流读写文本文件内容 字符流抽象类: - 字符输出流 `Writer` - 字符输入流 `Reader` 字符流实现: - 文件字符输出流 `FileWriter` - 文件字符输入流 `FileReader` 写入文本: ```java try (FileWriter writer = new FileWriter(filePath + "writer.txt")) { writer.write("String from FileWriter!"); } catch (IOException e) { System.out.println(e.getMessage()); } ``` > 若是要循环读写,写法与字节流类似 字符输出流写出数据后,若未刷新 `.flush()` 或关闭流 `.close()`,则不生效 --- ## 缓冲流 缓冲流通过包装原始输入输出流,提高原始流读写性能 - 原始流 (低级流)例:`FileInputStream` - 包装流 (处理流)例:`BufferInputStream` 缓冲流带有**缓冲池** (默认 8 KB),提高单次读写量,减少系统调用以提高 IO 性能 --- ### 字节缓冲流 - `BufferInputStream` 字节缓冲输入流 - `BufferOutputStream` 字节缓冲输出流 --- ### 字符缓冲流 `BufferedReader` 字符输入流 字符缓冲输入流提供了一个新方法,`.readLine()` 读取一行字符 字符缓冲输出流提供了方法 `.newLine()`, 用于换行 --- ## 转换流 如果代码编码和被读取的文本文件的编码是不一致的,使用字符流读取文本文件时就会出现乱码。 --- ### 字符输入转换流 `InputStreamReader` 字符输入转换流 解决乱码的步骤: 1. 先获取文件的原始字节流 2. 再将其按真实的字符集编码转换为字符输入流 ```java // (字符输入流, 编码) new InputStreamReader(fis, "UTF-8") ``` --- ## 打印流 - `PrintStream` (实现 `OutputStream` 抽象类) - `PrintWriter` (实现 `Writer` 抽象类) 打印流可以同时打印数据并将打印数据存入文件 ```java try (PrintStream ps = new PrintStream(filePath + "print-stream.txt")) { ps.println("这是一条信息, 来自: " + LocalDate.now()); } catch (IOException e) { System.out.println(e.getMessage()); } ``` --- ### 应用 打印流可将输出语句重定向到文件当中 (输出语句重定向) `System.out` 实际就是打印流对象 可通过 `System.setOut()` 方法将输出重定向至文件 ```java try (PrintStream ps = new PrintStream(testPath)) { System.setOut(ps); System.out.println("Hello from System.out.println()"); } catch (IOException e) { System.err.println(e.getMessage()); } ``` --- ## 数据流 - `DataInputStream` - `DataOutputStream` 数据输出流 - 允许把数据和其类型一并写出去 ```java // 创建数据输出流包装基础字节输出流 new DataOutputStream(out) ``` 向文件中写入 Java 数据类型: ```java try ( FileOutputStream fos = new FileOutputStream(testPath); DataOutputStream dos = new DataOutputStream(fos) ) { dos.write(125); dos.writeUTF("This is good data type, true or false, 回答我!!!"); dos.writeBoolean(true); dos.writeFloat(233.22F); } catch (IOException e) { System.err.println(e.getMessage()); } ``` 读取数据, 读取数据顺序**必须**与写入数据时的顺序一致: ```java try ( FileInputStream fis = new FileInputStream(testPath); DataInputStream dis = new DataInputStream(fis) ) { int b = dis.read(); String utf = dis.readUTF(); boolean bool = dis.readBoolean(); float f = dis.readFloat(); System.out.println("--------读取数据为-----------"); System.out.println(b); System.out.println(utf); System.out.println(bool); System.out.println(f); } catch (IOException e) { System.err.println(e.getMessage()); } ``` --- ## 序列化流 对象序列化就是把 Java 对象写入到文件当中去 对象反序列化就是把 Java 对象从文件中读取出来 - `ObjectInputStream` 对象字节输入流 (对象反序列化) - `ObjectOutputStream` 对象字节输出流 (对象序列化) 创建一个对象 (对象必须实现 `Serializeable` 接口), 用于序列化 > 在 Java 中, 如果想让对象序列化, 必须让对象实现接口 `Serializeable`, 否则无法序列化 `Serializeable` 接口没有任何实现方法, 是作为**标记接口**, 标记对象可被序列化 对象序列化: ```java try ( FileOutputStream fos = new FileOutputStream(filePath); ObjectOutputStream oos = new ObjectOutputStream(fos) ) { SerializeObj sObj = new SerializeObj("Jone"); sObj.setAge(24); oos.writeObject(sObj); } catch (IOException e) { System.err.println(e.getMessage()); } ``` 对象反序列化: ```java try (FileInputStream fis = new FileInputStream(filePath); ObjectInputStream ois = new ObjectInputStream(fis); ) { SerializeObj sObj = (SerializeObj) ois.readObject(); System.out.println(sObj.toString()); } catch (IOException | ClassNotFoundException e) { System.err.println(e.getMessage()); } ``` 如果我们不想让对象中的某个字段参与序列化, 可以使用 `transient` 关键字进行修饰 ``` // 让成员变量不参与序列化 public transient String password; ``` > ArrayList 实现了 `Serializeable` 接口 --- ## IO 框架 Commons-io: [官网](https://commons.apache.org/proper/commons-io/) 最后修改:2025 年 07 月 16 日 © 允许规范转载 赞 如果觉得我的文章对你有用,请随意赞赏