diff --git a/notes/Java IO.md b/notes/Java IO.md index 08dc80f463a484db4980b01721d42576228f8574..d93ff47c87f0d3df11b73cfa97861ad232c944a0 100644 --- a/notes/Java IO.md +++ b/notes/Java IO.md @@ -40,8 +40,7 @@ File 类可以用于表示文件和目录的信息,但是它不表示文件的 递归地输出一个目录下所有文件: ```java -public static void listAllFiles(File dir) -{ +public static void listAllFiles(File dir) { if (dir == null || !dir.exists()) { return; } @@ -60,8 +59,7 @@ public static void listAllFiles(File dir) 使用字节流操作进行文件复制: ```java -public static void copyFile(String src, String dist) throws IOException -{ +public static void copyFile(String src, String dist) throws IOException { FileInputStream in = new FileInputStream(src); FileOutputStream out = new FileOutputStream(dist); byte[] buffer = new byte[20 * 1024]; @@ -78,7 +76,11 @@ public static void copyFile(String src, String dist) throws IOException

-Java I/O 使用了装饰者模式来实现。以 InputStream 为例,InputStream 是抽象组件,FileInputStream 是 InputStream 的子类,属于具体组件,提供了字节流的输入操作。FilterInputStream 属于抽象装饰者,装饰者用于装饰组件,为组件提供额外的功能,例如 BufferedInputStream 为 FileInputStream 提供缓存的功能。 +Java I/O 使用了装饰者模式来实现。以 InputStream 为例, + +- InputStream 是抽象组件; +- FileInputStream 是 InputStream 的子类,属于具体组件,提供了字节流的输入操作; +- FilterInputStream 属于抽象装饰者,装饰者用于装饰组件,为组件提供额外的功能,例如 BufferedInputStream 为 FileInputStream 提供缓存的功能。 实例化一个具有缓存功能的字节流对象时,只需要在 FileInputStream 对象上再套一层 BufferedInputStream 对象即可。 @@ -99,8 +101,7 @@ DataInputStream 装饰者提供了对更多数据类型进行输入的操作, 逐行输出文本文件的内容: ```java -public static void readFileContent(String filePath) throws IOException -{ +public static void readFileContent(String filePath) throws IOException { FileReader fileReader = new FileReader(filePath); BufferedReader bufferedReader = new BufferedReader(fileReader); String line; @@ -126,7 +127,7 @@ UTF-16be 中的 be 指的是 Big Endian,也就是大端。相应地也有 UTF- Java 使用双字节编码 UTF-16be,这不是指 Java 只支持这一种编码方式,而是说 char 这种类型使用 UTF-16be 进行编码。char 类型占 16 位,也就是两个字节,Java 使用这种双字节编码是为了让一个中文或者一个英文都能使用一个 char 来存储。 -String 可以看成一个字符序列,可以指定一个编码方式将它转换为字节序列,也可以指定一个编码方式将一个字节序列转换为 String。 +String 可以看成一个字符序列,可以指定一个编码方式将它编码为字节序列,也可以指定一个编码方式将一个字节序列解码为 String。 ```java String str1 = "中文"; @@ -151,8 +152,7 @@ byte[] bytes = str1.getBytes(); 序列化的类需要实现 Serializable 接口,它只是一个标准,没有任何方法需要实现,但是如果不去实现它的话而进行序列化,会抛出异常。 ```java -public static void main(String[] args) throws IOException, ClassNotFoundException -{ +public static void main(String[] args) throws IOException, ClassNotFoundException { A a1 = new A(123, "abc"); String objectFile = "file/a1"; ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(objectFile)); @@ -188,7 +188,7 @@ private static class A implements Serializable transient 关键字可以使一些属性不会被序列化。 -**ArrayList 序列化和反序列化的实现** :ArrayList 中存储数据的数组是用 transient 修饰的,因为这个数组是动态扩展的,并不是所有的空间都被使用,因此就不需要所有的内容都被序列化。通过重写序列化和反序列化方法,使得可以只序列化数组中有内容的那部分数据。 +ArrayList 中存储数据的数组是用 transient 修饰的,因为这个数组是动态扩展的,并不是所有的空间都被使用,因此就不需要所有的内容都被序列化。通过重写序列化和反序列化方法,使得可以只序列化数组中有内容的那部分数据。 ```java private transient Object[] elementData; @@ -205,7 +205,7 @@ Java 中的网络支持: ## InetAddress -没有公有构造函数,只能通过静态方法来创建实例。 +没有公有的构造函数,只能通过静态方法来创建实例。 ```java InetAddress.getByName(String host); @@ -217,19 +217,24 @@ InetAddress.getByAddress(byte[] address); 可以直接从 URL 中读取字节流数据。 ```java -public static void main(String[] args) throws IOException -{ +public static void main(String[] args) throws IOException { + URL url = new URL("http://www.baidu.com"); - // 字节流 + + /* 字节流 */ InputStream is = url.openStream(); - // 字符流 + + /* 字符流 */ InputStreamReader isr = new InputStreamReader(is, "utf-8"); + + /* 提供缓存功能 */ BufferedReader br = new BufferedReader(isr); - String line = br.readLine(); - while (line != null) { + + String line; + while ((line = br.readLine()) != null) { System.out.println(line); - line = br.readLine(); } + br.close(); } ``` @@ -253,7 +258,7 @@ public static void main(String[] args) throws IOException - [Java NIO 浅析](https://tech.meituan.com/nio.html) - [IBM: NIO 入门](https://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html) -新的输入/输出 (NIO) 库是在 JDK 1.4 中引入的。NIO 弥补了原来的 I/O 的不足,提供了高速的、面向块的 I/O。 +新的输入/输出 (NIO) 库是在 JDK 1.4 中引入的,弥补了原来的 I/O 的不足,提供了高速的、面向块的 I/O。 ## 流与块 @@ -271,7 +276,7 @@ I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重 通道 Channel 是对原 I/O 包中的流的模拟,可以通过它读取和写入数据。 -通道与流的不同之处在于,流只能在一个方向上移动,(一个流必须是 InputStream 或者 OutputStream 的子类),而通道是双向的,可以用于读、写或者同时用于读写。 +通道与流的不同之处在于,流只能在一个方向上移动(一个流必须是 InputStream 或者 OutputStream 的子类),而通道是双向的,可以用于读、写或者同时用于读写。 通道包括以下类型: @@ -329,21 +334,41 @@ I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重 以下展示了使用 NIO 快速复制文件的实例: ```java -public static void fastCopy(String src, String dist) throws IOException -{ - FileInputStream fin = new FileInputStream(src); /* 获取源文件的输入字节流 */ - FileChannel fcin = fin.getChannel(); /* 获取输入字节流的文件通道 */ - FileOutputStream fout = new FileOutputStream(dist); /* 获取目标文件的输出字节流 */ - FileChannel fcout = fout.getChannel(); /* 获取输出字节流的通道 */ - ByteBuffer buffer = ByteBuffer.allocateDirect(1024); /* 为缓冲区分配 1024 个字节 */ +public static void fastCopy(String src, String dist) throws IOException { + + /* 获得源文件的输入字节流 */ + FileInputStream fin = new FileInputStream(src); + + /* 获取输入字节流的文件通道 */ + FileChannel fcin = fin.getChannel(); + + /* 获取目标文件的输出字节流 */ + FileOutputStream fout = new FileOutputStream(dist); + + /* 获取输出字节流的通道 */ + FileChannel fcout = fout.getChannel(); + + /* 为缓冲区分配 1024 个字节 */ + ByteBuffer buffer = ByteBuffer.allocateDirect(1024); + while (true) { - int r = fcin.read(buffer); /* 从输入通道中读取数据到缓冲区中 */ - if (r == -1) { /* read() 返回 -1 表示 EOF */ + + /* 从输入通道中读取数据到缓冲区中 */ + int r = fcin.read(buffer); + + /* read() 返回 -1 表示 EOF */ + if (r == -1) { break; } - buffer.flip(); /* 切换读写 */ - fcout.write(buffer); /* 把缓冲区的内容写入输出文件中 */ - buffer.clear(); /* 清空缓冲区 */ + + /* 切换读写 */ + buffer.flip(); + + /* 把缓冲区的内容写入输出文件中 */ + fcout.write(buffer); + + /* 清空缓冲区 */ + buffer.clear(); } } ``` @@ -448,10 +473,10 @@ while (true) { ## 套接字 NIO 实例 ```java -public class NIOServer -{ - public static void main(String[] args) throws IOException - { +public class NIOServer { + + public static void main(String[] args) throws IOException { + Selector selector = Selector.open(); ServerSocketChannel ssChannel = ServerSocketChannel.open(); @@ -463,32 +488,45 @@ public class NIOServer serverSocket.bind(address); while (true) { + selector.select(); Set keys = selector.selectedKeys(); Iterator keyIterator = keys.iterator(); + while (keyIterator.hasNext()) { + SelectionKey key = keyIterator.next(); + if (key.isAcceptable()) { + ServerSocketChannel ssChannel1 = (ServerSocketChannel) key.channel(); + // 服务器会为每个新连接创建一个 SocketChannel SocketChannel sChannel = ssChannel1.accept(); sChannel.configureBlocking(false); + // 这个新连接主要用于从客户端读取数据 sChannel.register(selector, SelectionKey.OP_READ); + } else if (key.isReadable()) { + SocketChannel sChannel = (SocketChannel) key.channel(); System.out.println(readDataFromSocketChannel(sChannel)); sChannel.close(); } + keyIterator.remove(); } } } private static String readDataFromSocketChannel(SocketChannel sChannel) throws IOException { + ByteBuffer buffer = ByteBuffer.allocate(1024); StringBuilder data = new StringBuilder(); + while (true) { + buffer.clear(); int n = sChannel.read(buffer); if (n == -1) { @@ -509,10 +547,9 @@ public class NIOServer ``` ```java -public class NIOClient -{ - public static void main(String[] args) throws IOException - { +public class NIOClient { + + public static void main(String[] args) throws IOException { Socket socket = new Socket("127.0.0.1", 8888); OutputStream out = socket.getOutputStream(); String s = "hello world"; @@ -526,9 +563,9 @@ public class NIOClient 内存映射文件 I/O 是一种读和写文件数据的方法,它可以比常规的基于流或者基于通道的 I/O 快得多。 -向内存映射文件写入可能是危险的,仅只是改变数组的单个元素这样的简单操作,就可能会直接修改磁盘上的文件。修改数据与将数据保存到磁盘是没有分开的。 +向内存映射文件写入可能是危险的,只是改变数组的单个元素这样的简单操作,就可能会直接修改磁盘上的文件。修改数据与将数据保存到磁盘是没有分开的。 -下面代码行将文件的前 1024 个字节映射到内存中,map() 方法返回一个 MappedByteBuffer,它是 ByteBuffer 的子类。因此,您可以像使用其他任何 ByteBuffer 一样使用新映射的缓冲区,操作系统会在需要时负责执行映射。 +下面代码行将文件的前 1024 个字节映射到内存中,map() 方法返回一个 MappedByteBuffer,它是 ByteBuffer 的子类。因此,可以像使用其他任何 ByteBuffer 一样使用新映射的缓冲区,操作系统会在需要时负责执行映射。 ```java MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024);