X Tutup
--- aliases: [] tags: - java - io - PL - base created: 2024-01-19 17:01:19 modified: 2024-07-13 11:24:13 --- # Java IO 笔记 --- ## IO 模型 Unix/[Linux](../Linux/Linux_Note.md) 下有 5 种 IO 模型: * Blocking IO * Nonblocking IO * IO multiplexing * Signal driven IO * Asynchronous IO ### 一些概念 ### BIO BIO(BLocking IO)顾名思义是一种阻塞型 IO 模型。 ### NIO NIO(Nonblocking IO ) 非阻塞 IO 模型。 ### 相关资料 * [看一遍就理解:IO模型详解 - 掘金](https://juejin.cn/post/7036518015462015006) * [5种网络IO模型(有图,很清楚) - 知乎](https://zhuanlan.zhihu.com/p/54580385) * [一文读懂网络 IO 模型原理 - 掘金](https://juejin.cn/post/7287791555623190568) ## 传统 IO java 传统 io 使用的是 [BIO](#BIO) 模型。 ### IO 相关类图 #### InputStream 类图 ```mermaid classDiagram class InputStream { <> +read(byte[] b) int +read(byte[] b, int off, int len) int } class FileInputStream { FileInputStream(File file) +read(byte[] b) int +read(byte[] b, int off, int len) int } class FilterInputStream { #FilterInputStream(InputStream in) +read(byte[] b) int +read(byte[] b, int off, int len) int } class BufferedInputStream { BufferedInputStream(InputStream in) BufferedInputStream(InputStream in, int size) +read(byte[] b, int off, int len) int } InputStream <|-- FileInputStream InputStream <|-- FilterInputStream FilterInputStream <| -- BufferedInputStream ``` #### OutputStream 类图 ```mermaid classDiagram class OutputStream { <> +write(byte[] b) void +write(byte[] b, int off, int len) void } class FileOutputStream { FileOutputStream(File file) FileOutputStream(File file, boolean append) FileOutputStream(String name) FileOutputStream(String name, boolean append) +write(byte[] b) void +write(byte[] b, int off, int len) void } class FilterOutputStream { FilterOutputStream(OutputStream out) +write(byte[] b) void +write(byte[] b, int off, int len) void } class BufferedOutputStream { BufferedOutputStream(OutputStream out) BufferedOutputStream(OutputStream out, int size) +write(byte[] b, int off, int len) void } OutputStream <| -- FileOutputStream OutputStream <| -- FilterOutputStream FilterOutputStream <| -- BufferedOutputStream ``` ### File Java 构造一个 `File` 对象,即使传入的文件或目录不存在,代码也不会出错,因为这个对象只是 Java 在内存中构建的一个对象。 因为构造一个 `File` 对象,并不会导致任何磁盘操作,而只有当调用 `File` 对象的某些方法的时候,才真正进行 IO 操作。 #### 常见问题 ##### 关于 File 的空指针异常 示例: ```java try{ // File d01 = new File(File_E01.class.getResource("t02.txt").getPath()); // File d01 = new File("t02.txt"); // 获取对象的路径 System.out.println(d01.getPath()); // 获取对象的父级路径 System.out.println(d01.getParent()); } catch (NullPointerException e) { logger.severe("NullPointerException! 文件对像为Null!\n" + e.getMessage()); } ``` 可以看文档知道,`File` 类在构建时有可能会抛空指针异常(`Throws: NullPointerException - If the pathname argument is null`),前提是那个 pathname 的字符串为 `null`。 但实际上,除非故意传个 null 值给 File 的构造方法,基本不可能让 File 「new 空」。即便如上面示例一样,使用 `类.class.getResource("xxx").getPath()` 这种方式,获取地址值。而上面示例却有可能会触发抛出空指针异常。触发的原因不是 File 的构造方法,而是 `类.class.getResource("xxx").getPath()` 这里触发的。 当执行到 `类.class.getResource("xxx")` 这里时,如果 `getResource()` 的参数是一个不存在的路径,那 `getResource()` 就会返回个 null,null 是不能继续 `getPath()` 的,所以触发了空指针异常的抛出。所以在这种方式构建 File 对象,空指针异常抛出在路径字符串「获取」时就有可能触发了,File 的构造方法根本没机会再触发空指针异常。 还有 `类.class.getResource("xxx").getPath()` 这代码中的 `getPath()` 方法是不会返回 Null 值,它最多只会返回一个空字符串(`Returns: the path part of this `URL`, or an empty string if one does not exist`)。 #### 路径相关 获取文件路径的方式有多种: ##### 方式 1 ```java IO_E01.class.getResource("t01.txt").getPath() // 结果与getPath()是一致的,但toString()一般用于调试 //File_E01.class.getResource("t01.txt").toString() File_E01.class.getResource("").getPaht() // 返回的是File_E01类所在的目录路径 ``` ##### 方式 2 ```java System.out.println(this.getClass().getResource("")); System.out.println(this.getClass().getResource("/")); System.out.println(this.getClass().getResource("t01.txt")); System.out.println(this.getClass().getResource("/t01.txt")); // 结果 // file:/home/silascript/DevWorkSpace/JavaExercise/io_exercise/ // file:/home/silascript/DevWorkSpace/JavaExercise/io_exercise/ // file:/home/silascript/DevWorkSpace/JavaExercise/io_exercise/t01.txt // file:/home/silascript/DevWorkSpace/JavaExercise/io_exercise/t01.txt ``` ##### 方式 3 ```java System.out.println(this.getClass().getClassLoader().getResource("")); System.out.println(this.getClass().getClassLoader().getResource("t01.txt")); System.out.println(this.getClass().getClassLoader().getResource("/")); System.out.println(this.getClass().getClassLoader().getResource("/t01.txt")); // 结果 // file:/home/silascript/DevWorkSpace/JavaExercise/io_exercise/ // file:/home/silascript/DevWorkSpace/JavaExercise/io_exercise/t01.txt // null // null ``` > [!tip] > `this.getClass().getClassLoader().getResource(路径字符串)`,使用这个方式获取文件地址,不能以 `/`(根路径)开始,不然返回值只能是 `null` 。 > > 因为 `getClass().getClassLoader()` 这已经是表示 `/` 目录。 > [!info] 相关链接 > > * [java获取文件路径总结\_inputstream获取文件路径-CSDN博客](https://blog.csdn.net/qq_38747892/article/details/126751734) > * [Java中的getResource()方法,及路径相关问题](https://cloud.tencent.com/developer/article/1901321) ### 遍历 File 类中有两组方法:`list()` 和 `listFiles()` 用来遍历。 `list()` 的返回值是字符串数组,即文件或目录的名称。 `listFiles()` 的返回值是 File 类型的数组,即返回的是 File 的对象。 这两组方法的重载方法,都能接收 [FileFilter](#javaio_io_filter_filefilter) 和 [FilenameFilter](#javaio_io_filter_filenamefilter) 这两种 [过滤器](#javaio_io_filter)。 ### 过滤器 #### FilterInputStream `FilterInputStream` 是所有过滤输入流的所有类的父类。 观察 [InputStream 类图](#InputStream%20类图),发现 `FilterInputStream` 的构造方法是 `protected` 的。也就是说除了其自身还有及子类外的第三方要创建它,是不能通过 `new FilterInputStream()` 来创建的,只能通过其子类,如 [BufferedInputStream](#字节缓冲流) 等子类来创建。 #### FilterOutputStream `FilterOutputStream` 是所有过滤输出流的所有类的父类。 #### FileFilter FileFilter 文件过滤器。 在 [JDK8](#JDK8) 后,FileFilter 接口被标记为 [函数式接口](#函数式接口),所以可以使用 [Lambda](Java_Note.md#java_lambda) 语法去快速实现这个接口。 FileFilter 接口只有一个方法:`boolean accept(File pathname)`。参数只有一个,即传入一个要过滤的目录 File 对象。 > [!example] 示例 > > ```java > public class File_E03 { > > static Logger logger = Logger.getLogger("File_E03"); > > public static void main(String[] args) { > > try { > > // 当前类所在的目录 > File dir01 = new File(File_E03.class.getResource("").getPath()); > > // 获取所有java文件 > // listFiles() 方法得到的是File的数组 > File[] files = dir01.listFiles((f) -> f.getPath().endsWith(".java")); > > // 遍历 > for (File f_temp : files) { > System.out.println("文件名:" + f_temp.getName()); > System.out.println("文件绝对路径:" + f_temp.getAbsolutePath()); > System.out.println("文件路径:" + f_temp.getPath()); > > System.out.println("---------------------------------------"); > } > > } catch (NullPointerException e) { > > logger.severe(e.getMessage()); > > } > > } > } > > > ``` > #### FilenameFilter FilenameFilter 文件名过滤器。 在 [JDK8](Java_Note.md#JDK8) 后,FilenameFilter 接口被标记为 [函数式接口](Java_Note.md#函数式接口),所以可以使用 [Lambda](Java_Note.md#java_lambda) 语法去快速实现这个接口。 这接口有且只有一个方法:`accept(File dir, String name)`。 > [!example] 列出某目录下所有 java 文件 > > ```java > public class File_E02 { > > static Logger logger = Logger.getLogger("File_E02"); > > public static void main(String[] args) { > > try { > > > // 当前类所在目录 > File f01 = new File(File_E02.class.getResource("").getPath()); > > // 列出此目录下所有后缀名为".java"的文件 > String[] files = f01.list((dir, fname) -> { > return fname.endsWith(".java"); > }); > > // 遍历文件名数组 > for (String fn_temp : files) { > > File f = new File(f01, fn_temp); > > if (f.isFile()) { > System.out.println("文件名:" + f.getName()); > System.out.println("文件绝对路径:" + f.getAbsolutePath()); > System.out.println("文件路径:" + f.getPath()); > } else { > System.out.println("子目录:" + f); > } > System.out.println("-----------------------------"); > } > > } catch (NullPointerException e) { > > logger.severe(e.getMessage()); > } > > } > > } > > ``` > 因为 FilenameFilter 接口已经被标记为函数式接口,所以示例代码中使用了 lambda 来定义这接口的实现。 > ### 字节流 #### 常用示例 ```java package io_exercise; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; public class IO_E01 { public static void main(String[] args) { try (InputStream inputStr = new FileInputStream( // 获取要读取的文件 IO_E01.class.getResource("t01.txt").getPath())) { // 用来装每次读取到的字符 // 字符数组大小决定每次读了多少字符 var bbuf = new byte[1024]; // 实际读取的字符数 var hasRead = 0; while ((hasRead = inputStr.read(bbuf)) > 0) { System.out.println(new String(bbuf, 0, hasRead)); } } catch (IOException e) { e.printStackTrace(); } } } ``` > [!tip] 关闭流 > > 在 [JDK1.7(即JDK7)](Java_Note#JDK) 之前的版本,需要手动关闭流,所以常常在 `finally` 代码块中执行 `inputStr.close()` 的代码。 > > 但从 1.7 开始,流接口已经实现了 `AutoCloseable` 接口,顾名思义,io 流已经可以「自动」关闭了,无须再手动写关闭流的代码了。 ### FileInputStream `FileInputStream` 是 [字节流](#javaio_io_bytestream) 抽象类 `InputStream` 的子类。 #### FileInputStream 与 InputStream 区别 `InputStream` 是字节流的抽象基类,它可以从任意的数据源中读取字节数据,包括内存、网络、文件等; `FileInputStream` 顾名思义,它只能从文件读取字节数据。 对比下两者部分源码对比: --- start-multi-column: ```column-settings Number of Columns: 2 Largest Column: standard ``` FileInputStream ```java private native int readBytes(byte[] b, int off, int len) throws IOException; @Override public int read(byte[] b) throws IOException { long comp = Blocker.begin(); try { return readBytes(b, 0, b.length); } finally { Blocker.end(comp); } } ``` --- end-column --- InputStream ```java public int read(byte[] b) throws IOException { return read(b, 0, b.length); } ``` --- end-multi-column 可发现,`FileInputStream` 的 `read(byte[] b)` 方法调了一个 native 方法 ### FileOutputStream `FileOutputStream` 比较重要的方法:`write(byte[] b, int off, int len)`。 #### FileInputStream 和 FileOutputStream 示例 ```java public class File_E05 { static Logger logger = Logger.getLogger("File_E05"); public static void main(String[] args) { // 要读取的文件 File fin = new File(File_E05.class.getResource("t01.txt").getPath()); // 要输出的文件 File fout = new File(File_E05.class.getResource("").getPath() + File.separator + "t01_backup.txt"); try (FileInputStream fis = new FileInputStream(fin); FileOutputStream fos = new FileOutputStream(fout)) { byte[] buffer = new byte[10]; int len = 0; while ((len = fis.read(buffer)) > 0) { String cpStr = new String(buffer); // 打印下 System.out.print(cpStr); // 写出 fos.write(buffer, 0, len); } } catch (FileNotFoundException fnfe) { logger.severe(fnfe.getMessage()); } catch (IOException ioe) { logger.severe(ioe.getMessage()); } catch (NullPointerException npe) { logger.severe(npe.getMessage()); } catch (Exception e) { logger.severe(e.getMessage()); } } } ``` ### 字节缓冲流 `BufferedInputStream` 与 `BufferedOutputStream` 称为字节缓冲流。使用内置的缓冲区对读取或输出的数据进行缓冲,以此减少直接读取数据源的次数。 `BufferedInputStream` 和 `BufferedOutputStream` 是 [FilterInputStream](#javaio_io_filter_filterinputstream) 与 [FilterOutputStream](#javaio_io_filter_filteroutputstream) 的子类。 字节缓冲流默认的缓冲区大小是**8M**。这个数值可能通过重载的构造方法进行设置:`public BufferedInputStream(InputStream in, int size)`。这个数值最好是 2 的 n 次幂。 > [!info] BufferedInputStream 源码 > > `private static final int DEFAULT_BUFFER_SIZE = 8192;` 这两类在对象实例化时,需要传入 [FileInputStream](#javaio_io_bytestream_fileinputstream) 和 [FileOutputStream](#javaio_io_bytestream_fileouputstream) 实例对象。 而其 `read()` 方法与 `write()` 方法用法与 `FileInputStream` 与 `FileOutputStream` 大同小异。 #### BufferedInputStream 与 BufferedOutputStream 示例 ```java // 类所在的目录路径 String current_path = File_E06.class.getResource("").getPath(); // 要读取的文件 File in_file = new File(current_path + File.separator + "t01.txt"); // 要输出的文件 File out_file = new File(current_path + File.separator + "t01_backup.txt"); // 读取数据字节缓存 byte[] buff = new byte[64]; int len = 0; try (FileInputStream fis = new FileInputStream(in_file); BufferedInputStream bis = new BufferedInputStream(fis); FileOutputStream fos = new FileOutputStream(out_file); BufferedOutputStream bos = new BufferedOutputStream(fos)) { // 读取 while ((len = bis.read(buff)) > 0) { // 输出 bos.write(buff, 0, len); } ``` --- ## NIO `java.nio` 是 [JDK 1.4](Java_Note.md#JDK) 就已经提供的。 ### Buffer `Buffer` 是 [NIO](#javaio_nio) 的核心。nio 的 API 都是围绕着 `Buffer` 而设计的。 ```mermaid classDiagram Buffer <|-- ByteBuffer class Buffer{ <> +position() int +position(int newPosition) Buffer +limit() int +limit(int newLimit) Buffer +flip() Buffer +remaining() int +capacity() int +clear() Buffer +hasRemaining() boolean +slice() Buffer +slice(int index, int length) Buffer +mark() Buffer +isDirect() boolean +isReadOnly() boolean } class ByteBuffer{ <> +allocate(int capacity) ByteBuffer +allocateDirect(int capacity) ByteBuffer +get() bype +get(byte[] dst) ByteBuffer +get(byte[] dst, int offset, int length) ByteBuffer +get(int index) bype +get(int index, bype[] dst) ByteBuffer +get(int index, byte[] dst, int offset, int length) ByteBuffer +getChar() char +getChar(int index) char +getDouble() double +getDouble(int index) double +getFloat() float +getFloat(int index) float +getInt() int +getInt(int index) int +getLong() long +getLong(int index) long +put(byte b) ByteBuffer +put(byte[] src) ByteBuffer +put(byte[] src, int offset, int length) ByteBuffer +put(int value) ByteBuffer +put(int index, int value) ByteBuffer } ``` --- ## 相关笔记 * [Java 笔记](Java_Note.md) * [Java 基础笔记](Java_Base_Note.md) * [Java 视频清单](Java_Videos.md)
X Tutup