Java IO.md 20.5 KB
Newer Older
C
CyC2018 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
<!-- GFM-TOC -->
* [一、概览](#一概览)
* [二、磁盘操作](#二磁盘操作)
* [三、字节操作](#三字节操作)
* [四、字符操作](#四字符操作)
* [五、对象操作](#五对象操作)
* [六、网络操作](#六网络操作)
    * [InetAddress](#inetaddress)
    * [URL](#url)
    * [Sockets](#sockets)
    * [Datagram](#datagram)
* [七、NIO](#七nio)
    * [流与块](#流与块)
    * [通道与缓冲区](#通道与缓冲区)
    * [缓冲区状态变量](#缓冲区状态变量)
    * [文件 NIO 实例](#文件-nio-实例)
    * [选择器](#选择器)
    * [套接字 NIO 实例](#套接字-nio-实例)
    * [内存映射文件](#内存映射文件)
    * [对比](#对比)
* [八、参考资料](#八参考资料)
<!-- GFM-TOC -->
C
CyC2018 已提交
23 24


C
CyC2018 已提交
25
# 一、概览
C
CyC2018 已提交
26

C
CyC2018 已提交
27
Java 的 I/O 大概可以分成以下几类:
C
CyC2018 已提交
28

C
CyC2018 已提交
29 30 31 32 33 34
- 磁盘操作:File
- 字节操作:InputStream 和 OutputStream
- 字符操作:Reader 和 Writer
- 对象操作:Serializable
- 网络操作:Socket
- 新的输入/输出:NIO
C
CyC2018 已提交
35

C
CyC2018 已提交
36
# 二、磁盘操作
C
CyC2018 已提交
37

C
CyC2018 已提交
38
File 类可以用于表示文件和目录的信息,但是它不表示文件的内容。
C
CyC2018 已提交
39

C
CyC2018 已提交
40 41 42
递归地输出一个目录下所有文件:

```java
C
CyC2018 已提交
43
public static void listAllFiles(File dir) {
C
CyC2018 已提交
44 45 46
    if (dir == null || !dir.exists()) {
        return;
    }
C
CyC2018 已提交
47 48 49 50
    if (dir.isFile()) {
        System.out.println(dir.getName());
        return;
    }
C
CyC2018 已提交
51
    for (File file : dir.listFiles()) {
C
CyC2018 已提交
52 53 54 55 56
        listAllFiles(file);
    }
}
```

C
CyC2018 已提交
57
# 三、字节操作
C
CyC2018 已提交
58

C
CyC2018 已提交
59 60 61
使用字节流操作进行文件复制:

```java
C
CyC2018 已提交
62
public static void copyFile(String src, String dist) throws IOException {
C
CyC2018 已提交
63 64
    FileInputStream in = new FileInputStream(src);
    FileOutputStream out = new FileOutputStream(dist);
C
CyC2018 已提交
65
    byte[] buffer = new byte[20 * 1024];
C
CyC2018 已提交
66 67 68
    // read() 最多读取 buffer.length 个字节
    // 返回的是实际读取的个数
    // 返回 -1 的时候表示读到 eof,即文件尾
C
CyC2018 已提交
69 70 71 72 73 74 75 76
    while (in.read(buffer, 0, buffer.length) != -1) {
        out.write(buffer);
    }
    in.close();
    out.close();
}
```

C
CyC2018 已提交
77 78
<div align="center"> <img src="../pics//DP-Decorator-java.io.png" width="500"/> </div><br>

C
CyC2018 已提交
79 80 81 82 83
Java I/O 使用了装饰者模式来实现。以 InputStream 为例,

- InputStream 是抽象组件;
- FileInputStream 是 InputStream 的子类,属于具体组件,提供了字节流的输入操作;
- FilterInputStream 属于抽象装饰者,装饰者用于装饰组件,为组件提供额外的功能,例如 BufferedInputStream 为 FileInputStream 提供缓存的功能。
C
CyC2018 已提交
84 85

实例化一个具有缓存功能的字节流对象时,只需要在 FileInputStream 对象上再套一层 BufferedInputStream 对象即可。
C
CyC2018 已提交
86 87

```java
C
CyC2018 已提交
88
FileInputStream fileInputStream = new FileInputStream(filePath);
C
CyC2018 已提交
89
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);
C
CyC2018 已提交
90 91
```

C
CyC2018 已提交
92
DataInputStream 装饰者提供了对更多数据类型进行输入的操作,比如 int、double 等基本类型。
C
CyC2018 已提交
93

C
CyC2018 已提交
94
# 四、字符操作
C
CyC2018 已提交
95

C
CyC2018 已提交
96
不管是磁盘还是网络传输,最小的存储单元都是字节,而不是字符。但是在程序中操作的通常是字符形式的数据,因此需要提供对字符进行操作的方法。
C
CyC2018 已提交
97

C
CyC2018 已提交
98 99
- InputStreamReader 实现从字节流解码成字符流;
- OutputStreamWriter 实现字符流编码成为字节流。
C
CyC2018 已提交
100

C
CyC2018 已提交
101
逐行输出文本文件的内容:
C
CyC2018 已提交
102 103

```java
C
CyC2018 已提交
104
public static void readFileContent(String filePath) throws IOException {
C
CyC2018 已提交
105 106 107 108 109 110 111 112 113 114
    FileReader fileReader = new FileReader(filePath);
    BufferedReader bufferedReader = new BufferedReader(fileReader);
    String line;
    while ((line = bufferedReader.readLine()) != null) {
        System.out.println(line);
    }
    // 装饰者模式使得 BufferedReader 组合了一个 Reader 对象
    // 在调用 BufferedReader 的 close() 方法时会去调用 fileReader 的 close() 方法
    // 因此只要一个 close() 调用即可
    bufferedReader.close();
C
CyC2018 已提交
115
}
C
CyC2018 已提交
116 117
```

C
CyC2018 已提交
118 119
编码就是把字符转换为字节,而解码是把字节重新组合成字符。

C
CyC2018 已提交
120 121
如果编码和解码过程使用不同的编码方式那么就出现了乱码。

C
CyC2018 已提交
122 123 124
- GBK 编码中,中文字符占 2 个字节,英文字符占 1 个字节;
- UTF-8 编码中,中文字符占 3 个字节,英文字符占 1 个字节;
- UTF-16be 编码中,中文字符和英文字符都占 2 个字节。
C
CyC2018 已提交
125

C
CyC2018 已提交
126
UTF-16be 中的 be 指的是 Big Endian,也就是大端。相应地也有 UTF-16le,le 指的是 Little Endian,也就是小端。
C
CyC2018 已提交
127

C
CyC2018 已提交
128 129
Java 使用双字节编码 UTF-16be,这不是指 Java 只支持这一种编码方式,而是说 char 这种类型使用 UTF-16be 进行编码。char 类型占 16 位,也就是两个字节,Java 使用这种双字节编码是为了让一个中文或者一个英文都能使用一个 char 来存储。

C
CyC2018 已提交
130
String 可以看成一个字符序列,可以指定一个编码方式将它编码为字节序列,也可以指定一个编码方式将一个字节序列解码为 String。
C
CyC2018 已提交
131 132 133 134 135 136 137 138 139 140 141 142 143

```java
String str1 = "中文";
byte[] bytes = str1.getBytes("UTF-8");
String str2 = new String(bytes, "UTF-8");
System.out.println(str2);
```

在调用无参数 getBytes() 方法时,默认的编码方式不是 UTF-16be。双字节编码的好处是可以使用一个 char 存储中文和英文,而将 String 转为 bytes[] 字节数组就不再需要这个好处,因此也就不再需要双字节编码。getBytes() 的默认编码方式与平台有关,一般为 UTF-8。

```java
byte[] bytes = str1.getBytes();
```
C
CyC2018 已提交
144

C
CyC2018 已提交
145
# 五、对象操作
C
CyC2018 已提交
146 147 148

序列化就是将一个对象转换成字节序列,方便存储和传输。

C
CyC2018 已提交
149 150 151 152
- 序列化:ObjectOutputStream.writeObject()
- 反序列化:ObjectInputStream.readObject()

序列化的类需要实现 Serializable 接口,它只是一个标准,没有任何方法需要实现,但是如果不去实现它的话而进行序列化,会抛出异常。
C
CyC2018 已提交
153

C
CyC2018 已提交
154
```java
C
CyC2018 已提交
155
public static void main(String[] args) throws IOException, ClassNotFoundException {
C
CyC2018 已提交
156 157 158 159 160
    A a1 = new A(123, "abc");
    String objectFile = "file/a1";
    ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream(objectFile));
    objectOutputStream.writeObject(a1);
    objectOutputStream.close();
C
CyC2018 已提交
161

C
CyC2018 已提交
162 163 164 165 166 167
    ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream(objectFile));
    A a2 = (A) objectInputStream.readObject();
    objectInputStream.close();
    System.out.println(a2);
}

C
CyC2018 已提交
168 169
private static class A implements Serializable
{
C
CyC2018 已提交
170 171 172
    private int x;
    private String y;

C
CyC2018 已提交
173 174
    A(int x, String y)
    {
C
CyC2018 已提交
175 176 177 178 179
        this.x = x;
        this.y = y;
    }

    @Override
C
CyC2018 已提交
180 181 182
    public String toString()
    {
        return "x = " + x + "  " + "y = " + y;
C
CyC2018 已提交
183 184 185
    }
}
```
C
CyC2018 已提交
186

C
CyC2018 已提交
187
不会对静态变量进行序列化,因为序列化只是保存对象的状态,静态变量属于类的状态。
C
CyC2018 已提交
188

C
CyC2018 已提交
189
transient 关键字可以使一些属性不会被序列化。
C
CyC2018 已提交
190

C
CyC2018 已提交
191
ArrayList 中存储数据的数组是用 transient 修饰的,因为这个数组是动态扩展的,并不是所有的空间都被使用,因此就不需要所有的内容都被序列化。通过重写序列化和反序列化方法,使得可以只序列化数组中有内容的那部分数据。
C
CyC2018 已提交
192 193

```java
C
CyC2018 已提交
194
private transient Object[] elementData;
C
CyC2018 已提交
195 196
```

C
CyC2018 已提交
197
# 六、网络操作
C
CyC2018 已提交
198

C
CyC2018 已提交
199
Java 中的网络支持:
C
CyC2018 已提交
200

C
CyC2018 已提交
201
- InetAddress:用于表示网络上的硬件资源,即 IP 地址;
C
CyC2018 已提交
202
- URL:统一资源定位符;
C
CyC2018 已提交
203 204
- Sockets:使用 TCP 协议实现网络通信;
- Datagram:使用 UDP 协议实现网络通信。
C
CyC2018 已提交
205

C
CyC2018 已提交
206
## InetAddress
C
CyC2018 已提交
207

C
CyC2018 已提交
208
没有公有的构造函数,只能通过静态方法来创建实例。
C
CyC2018 已提交
209 210

```java
C
CyC2018 已提交
211
InetAddress.getByName(String host);
C
CyC2018 已提交
212
InetAddress.getByAddress(byte[] address);
C
CyC2018 已提交
213 214
```

C
CyC2018 已提交
215
## URL
C
CyC2018 已提交
216

C
CyC2018 已提交
217
可以直接从 URL 中读取字节流数据。
C
CyC2018 已提交
218 219

```java
C
CyC2018 已提交
220 221
public static void main(String[] args) throws IOException {

C
CyC2018 已提交
222
    URL url = new URL("http://www.baidu.com");
C
CyC2018 已提交
223 224

    /* 字节流 */
C
CyC2018 已提交
225
    InputStream is = url.openStream();
C
CyC2018 已提交
226 227

    /* 字符流 */
C
CyC2018 已提交
228
    InputStreamReader isr = new InputStreamReader(is, "utf-8");
C
CyC2018 已提交
229 230

    /* 提供缓存功能 */
C
CyC2018 已提交
231
    BufferedReader br = new BufferedReader(isr);
C
CyC2018 已提交
232 233 234

    String line;
    while ((line = br.readLine()) != null) {
C
CyC2018 已提交
235 236
        System.out.println(line);
    }
C
CyC2018 已提交
237

C
CyC2018 已提交
238
    br.close();
C
CyC2018 已提交
239 240 241
}
```

C
CyC2018 已提交
242
## Sockets
C
CyC2018 已提交
243

C
CyC2018 已提交
244 245 246
- ServerSocket:服务器端类
- Socket:客户端类
- 服务器和客户端通过 InputStream 和 OutputStream 进行输入输出。
C
CyC2018 已提交
247

C
CyC2018 已提交
248
<div align="center"> <img src="../pics//ClienteServidorSockets1521731145260.jpg"/> </div><br>
C
CyC2018 已提交
249

C
CyC2018 已提交
250
## Datagram
C
CyC2018 已提交
251

C
CyC2018 已提交
252 253
- DatagramPacket:数据包类
- DatagramSocket:通信类
C
CyC2018 已提交
254

C
CyC2018 已提交
255
# 七、NIO
C
CyC2018 已提交
256

C
CyC2018 已提交
257 258 259
- [Java NIO Tutorial](http://tutorials.jenkov.com/java-nio/index.html)
- [Java NIO 浅析](https://tech.meituan.com/nio.html)
- [IBM: NIO 入门](https://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html)
C
CyC2018 已提交
260

C
CyC2018 已提交
261
新的输入/输出 (NIO) 库是在 JDK 1.4 中引入的,弥补了原来的 I/O 的不足,提供了高速的、面向块的 I/O。
C
CyC2018 已提交
262

C
CyC2018 已提交
263
## 流与块
C
CyC2018 已提交
264

C
CyC2018 已提交
265
I/O 与 NIO 最重要的区别是数据打包和传输的方式,I/O 以流的方式处理数据,而 NIO 以块的方式处理数据。
C
CyC2018 已提交
266

C
CyC2018 已提交
267
面向流的 I/O 一次处理一个字节数据:一个输入流产生一个字节数据,一个输出流消费一个字节数据。为流式数据创建过滤器非常容易,链接几个过滤器,以便每个过滤器只负责复杂处理机制的一部分。不利的一面是,面向流的 I/O 通常相当慢。
C
CyC2018 已提交
268

C
CyC2018 已提交
269
面向块的 I/O 一次处理一个数据块,按块处理数据比按流处理数据要快得多。但是面向块的 I/O 缺少一些面向流的 I/O 所具有的优雅性和简单性。
C
CyC2018 已提交
270

C
CyC2018 已提交
271
I/O 包和 NIO 已经很好地集成了,java.io.\* 已经以 NIO 为基础重新实现了,所以现在它可以利用 NIO 的一些特性。例如,java.io.\* 包中的一些类包含以块的形式读写数据的方法,这使得即使在面向流的系统中,处理速度也会更快。
C
CyC2018 已提交
272

C
CyC2018 已提交
273
## 通道与缓冲区
C
CyC2018 已提交
274

C
CyC2018 已提交
275
### 1. 通道
C
CyC2018 已提交
276

C
CyC2018 已提交
277
通道 Channel 是对原 I/O 包中的流的模拟,可以通过它读取和写入数据。
C
CyC2018 已提交
278

C
CyC2018 已提交
279
通道与流的不同之处在于,流只能在一个方向上移动(一个流必须是 InputStream 或者 OutputStream 的子类),而通道是双向的,可以用于读、写或者同时用于读写。
C
CyC2018 已提交
280 281 282

通道包括以下类型:

C
CyC2018 已提交
283 284 285 286
- FileChannel:从文件中读写数据;
- DatagramChannel:通过 UDP 读写网络中数据;
- SocketChannel:通过 TCP 读写网络中数据;
- ServerSocketChannel:可以监听新进来的 TCP 连接,对每一个新进来的连接都会创建一个 SocketChannel。
C
CyC2018 已提交
287

C
CyC2018 已提交
288
### 2. 缓冲区
C
CyC2018 已提交
289

C
CyC2018 已提交
290
发送给一个通道的所有数据都必须首先放到缓冲区中,同样地,从通道中读取的任何数据都要先读到缓冲区中。也就是说,不会直接对通道进行读写数据,而是要先经过缓冲区。
C
CyC2018 已提交
291 292 293 294 295

缓冲区实质上是一个数组,但它不仅仅是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。

缓冲区包括以下类型:

C
CyC2018 已提交
296 297 298 299 300 301 302
- ByteBuffer
- CharBuffer
- ShortBuffer
- IntBuffer
- LongBuffer
- FloatBuffer
- DoubleBuffer
C
CyC2018 已提交
303

C
CyC2018 已提交
304
## 缓冲区状态变量
C
CyC2018 已提交
305

C
CyC2018 已提交
306 307 308
- capacity:最大容量;
- position:当前已经读写的字节数;
- limit:还可以读写的字节数。
C
CyC2018 已提交
309 310 311

状态变量的改变过程举例:

C
CyC2018 已提交
312
① 新建一个大小为 8 个字节的缓冲区,此时 position 为 0,而 limit = capacity = 8。capacity 变量不会改变,下面的讨论会忽略它。
C
CyC2018 已提交
313

C
CyC2018 已提交
314
<div align="center"> <img src="../pics//1bea398f-17a7-4f67-a90b-9e2d243eaa9a.png"/> </div><br>
C
CyC2018 已提交
315

C
CyC2018 已提交
316
② 从输入通道中读取 5 个字节数据写入缓冲区中,此时 position 移动设置为 5,limit 保持不变。
C
CyC2018 已提交
317

C
CyC2018 已提交
318
<div align="center"> <img src="../pics//80804f52-8815-4096-b506-48eef3eed5c6.png"/> </div><br>
C
CyC2018 已提交
319

C
CyC2018 已提交
320
③ 在将缓冲区的数据写到输出通道之前,需要先调用 flip() 方法,这个方法将 limit 设置为当前 position,并将 position 设置为 0。
C
CyC2018 已提交
321

C
CyC2018 已提交
322
<div align="center"> <img src="../pics//952e06bd-5a65-4cab-82e4-dd1536462f38.png"/> </div><br>
C
CyC2018 已提交
323

C
CyC2018 已提交
324
④ 从缓冲区中取 4 个字节到输出缓冲中,此时 position 设为 4。
C
CyC2018 已提交
325

C
CyC2018 已提交
326
<div align="center"> <img src="../pics//b5bdcbe2-b958-4aef-9151-6ad963cb28b4.png"/> </div><br>
C
CyC2018 已提交
327

C
CyC2018 已提交
328
⑤ 最后需要调用 clear() 方法来清空缓冲区,此时 position 和 limit 都被设置为最初位置。
C
CyC2018 已提交
329

C
CyC2018 已提交
330
<div align="center"> <img src="../pics//67bf5487-c45d-49b6-b9c0-a058d8c68902.png"/> </div><br>
C
CyC2018 已提交
331

C
CyC2018 已提交
332
## 文件 NIO 实例
C
CyC2018 已提交
333

C
CyC2018 已提交
334
以下展示了使用 NIO 快速复制文件的实例:
C
CyC2018 已提交
335 336

```java
C
CyC2018 已提交
337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353
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);

C
CyC2018 已提交
354
    while (true) {
C
CyC2018 已提交
355 356 357 358 359 360

        /* 从输入通道中读取数据到缓冲区中 */
        int r = fcin.read(buffer);

        /* read() 返回 -1 表示 EOF */
        if (r == -1) {
C
CyC2018 已提交
361
            break;
C
CyC2018 已提交
362
        }
C
CyC2018 已提交
363 364 365 366 367 368 369 370 371

        /* 切换读写 */
        buffer.flip();

        /* 把缓冲区的内容写入输出文件中 */
        fcout.write(buffer);
        
        /* 清空缓冲区 */
        buffer.clear();
C
CyC2018 已提交
372
    }
C
CyC2018 已提交
373 374 375
}
```

C
CyC2018 已提交
376
## 选择器
C
CyC2018 已提交
377

C
CyC2018 已提交
378 379 380 381 382
NIO 常常被叫做非阻塞 IO,主要是因为 NIO 在网络通信中的非阻塞特性被广泛使用。

NIO 实现了 IO 多路复用中的 Reactor 模型,一个线程 Thread 使用一个选择器 Selector 通过轮询的方式去监听多个通道 Channel 上的事件,从而让一个线程就可以处理多个事件。

通过配置监听的通道 Channel 为非阻塞,那么当 Channel 上的 IO 事件还未到达时,就不会进入阻塞状态一直等待,而是继续轮询其它 Channel,找到 IO 事件已经到达的 Channel 执行。
C
CyC2018 已提交
383

C
CyC2018 已提交
384
因为创建和切换线程的开销很大,因此使用一个线程来处理多个事件而不是一个线程处理一个事件具有更好的性能。
C
CyC2018 已提交
385

C
CyC2018 已提交
386 387
应该注意的是,只有套接字 Channel 才能配置为非阻塞,而 FileChannel 不能,为 FileChannel 配置非阻塞也没有意义。

C
CyC2018 已提交
388
<div align="center"> <img src="../pics//4d930e22-f493-49ae-8dff-ea21cd6895dc.png"/> </div><br>
C
CyC2018 已提交
389

C
CyC2018 已提交
390
### 1. 创建选择器
C
CyC2018 已提交
391 392

```java
C
CyC2018 已提交
393
Selector selector = Selector.open();
C
CyC2018 已提交
394 395
```

C
CyC2018 已提交
396
### 2. 将通道注册到选择器上
C
CyC2018 已提交
397 398

```java
C
CyC2018 已提交
399
ServerSocketChannel ssChannel = ServerSocketChannel.open();
C
CyC2018 已提交
400
ssChannel.configureBlocking(false);
C
CyC2018 已提交
401
ssChannel.register(selector, SelectionKey.OP_ACCEPT);
C
CyC2018 已提交
402 403
```

C
CyC2018 已提交
404
通道必须配置为非阻塞模式,否则使用选择器就没有任何意义了,因为如果通道在某个事件上被阻塞,那么服务器就不能响应其它事件,必须等待这个事件处理完毕才能去处理其它事件,显然这和选择器的作用背道而驰。
C
CyC2018 已提交
405

C
CyC2018 已提交
406
在将通道注册到选择器上时,还需要指定要注册的具体事件,主要有以下几类:
C
CyC2018 已提交
407

C
CyC2018 已提交
408 409 410 411
- SelectionKey.OP_CONNECT
- SelectionKey.OP_ACCEPT
- SelectionKey.OP_READ
- SelectionKey.OP_WRITE
C
CyC2018 已提交
412

C
CyC2018 已提交
413
它们在 SelectionKey 的定义如下:
C
CyC2018 已提交
414

C
CyC2018 已提交
415
```java
C
CyC2018 已提交
416 417 418 419
public static final int OP_READ = 1 << 0;
public static final int OP_WRITE = 1 << 2;
public static final int OP_CONNECT = 1 << 3;
public static final int OP_ACCEPT = 1 << 4;
C
CyC2018 已提交
420 421
```

C
CyC2018 已提交
422
可以看出每个事件可以被当成一个位域,从而组成事件集整数。例如:
C
CyC2018 已提交
423 424

```java
C
CyC2018 已提交
425
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
C
CyC2018 已提交
426 427
```

C
CyC2018 已提交
428
### 3. 监听事件
C
CyC2018 已提交
429 430

```java
C
CyC2018 已提交
431
int num = selector.select();
C
CyC2018 已提交
432 433
```

C
CyC2018 已提交
434
使用 select() 来监听到达的事件,它会一直阻塞直到有至少一个事件到达。
C
CyC2018 已提交
435

C
CyC2018 已提交
436
### 4. 获取到达的事件
C
CyC2018 已提交
437 438

```java
C
CyC2018 已提交
439 440 441 442 443 444 445 446 447 448
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = keys.iterator();
while (keyIterator.hasNext()) {
    SelectionKey key = keyIterator.next();
    if (key.isAcceptable()) {
        // ...
    } else if (key.isReadable()) {
        // ...
    }
    keyIterator.remove();
C
CyC2018 已提交
449 450 451
}
```

C
CyC2018 已提交
452
### 5. 事件循环
C
CyC2018 已提交
453

C
CyC2018 已提交
454
因为一次 select() 调用不能处理完所有的事件,并且服务器端有可能需要一直监听事件,因此服务器端处理事件的代码一般会放在一个死循环内。
C
CyC2018 已提交
455 456

```java
C
CyC2018 已提交
457 458 459 460 461 462 463 464 465 466 467 468 469
while (true) {
    int num = selector.select();
    Set<SelectionKey> keys = selector.selectedKeys();
    Iterator<SelectionKey> keyIterator = keys.iterator();
    while (keyIterator.hasNext()) {
        SelectionKey key = keyIterator.next();
        if (key.isAcceptable()) {
            // ...
        } else if (key.isReadable()) {
            // ...
        }
        keyIterator.remove();
    }
C
CyC2018 已提交
470
}
C
CyC2018 已提交
471 472
```

C
CyC2018 已提交
473
## 套接字 NIO 实例
C
CyC2018 已提交
474 475

```java
C
CyC2018 已提交
476 477 478 479
public class NIOServer {

    public static void main(String[] args) throws IOException {

C
CyC2018 已提交
480 481 482 483 484 485 486 487 488 489 490
        Selector selector = Selector.open();

        ServerSocketChannel ssChannel = ServerSocketChannel.open();
        ssChannel.configureBlocking(false);
        ssChannel.register(selector, SelectionKey.OP_ACCEPT);

        ServerSocket serverSocket = ssChannel.socket();
        InetSocketAddress address = new InetSocketAddress("127.0.0.1", 8888);
        serverSocket.bind(address);

        while (true) {
C
CyC2018 已提交
491

C
CyC2018 已提交
492 493 494
            selector.select();
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> keyIterator = keys.iterator();
C
CyC2018 已提交
495

C
CyC2018 已提交
496
            while (keyIterator.hasNext()) {
C
CyC2018 已提交
497

C
CyC2018 已提交
498
                SelectionKey key = keyIterator.next();
C
CyC2018 已提交
499

C
CyC2018 已提交
500
                if (key.isAcceptable()) {
C
CyC2018 已提交
501

C
CyC2018 已提交
502
                    ServerSocketChannel ssChannel1 = (ServerSocketChannel) key.channel();
C
CyC2018 已提交
503

C
CyC2018 已提交
504 505 506
                    // 服务器会为每个新连接创建一个 SocketChannel
                    SocketChannel sChannel = ssChannel1.accept();
                    sChannel.configureBlocking(false);
C
CyC2018 已提交
507

C
CyC2018 已提交
508 509
                    // 这个新连接主要用于从客户端读取数据
                    sChannel.register(selector, SelectionKey.OP_READ);
C
CyC2018 已提交
510

C
CyC2018 已提交
511
                } else if (key.isReadable()) {
C
CyC2018 已提交
512

C
CyC2018 已提交
513 514 515 516
                    SocketChannel sChannel = (SocketChannel) key.channel();
                    System.out.println(readDataFromSocketChannel(sChannel));
                    sChannel.close();
                }
C
CyC2018 已提交
517

C
CyC2018 已提交
518 519 520 521 522 523
                keyIterator.remove();
            }
        }
    }

    private static String readDataFromSocketChannel(SocketChannel sChannel) throws IOException {
C
CyC2018 已提交
524

C
CyC2018 已提交
525
        ByteBuffer buffer = ByteBuffer.allocate(1024);
C
CyC2018 已提交
526
        StringBuilder data = new StringBuilder();
C
CyC2018 已提交
527

C
CyC2018 已提交
528
        while (true) {
C
CyC2018 已提交
529

C
CyC2018 已提交
530 531
            buffer.clear();
            int n = sChannel.read(buffer);
C
CyC2018 已提交
532
            if (n == -1) {
C
CyC2018 已提交
533
                break;
C
CyC2018 已提交
534
            }
C
CyC2018 已提交
535 536 537
            buffer.flip();
            int limit = buffer.limit();
            char[] dst = new char[limit];
C
CyC2018 已提交
538
            for (int i = 0; i < limit; i++) {
C
CyC2018 已提交
539
                dst[i] = (char) buffer.get(i);
C
CyC2018 已提交
540
            }
C
CyC2018 已提交
541 542 543 544 545
            data.append(dst);
            buffer.clear();
        }
        return data.toString();
    }
C
CyC2018 已提交
546
}
C
CyC2018 已提交
547 548 549
```

```java
C
CyC2018 已提交
550 551 552
public class NIOClient {

    public static void main(String[] args) throws IOException {
C
CyC2018 已提交
553 554 555 556 557 558
        Socket socket = new Socket("127.0.0.1", 8888);
        OutputStream out = socket.getOutputStream();
        String s = "hello world";
        out.write(s.getBytes());
        out.close();
    }
C
CyC2018 已提交
559 560 561
}
```

C
CyC2018 已提交
562
## 内存映射文件
C
CyC2018 已提交
563

C
CyC2018 已提交
564
内存映射文件 I/O 是一种读和写文件数据的方法,它可以比常规的基于流或者基于通道的 I/O 快得多。
C
CyC2018 已提交
565

C
CyC2018 已提交
566
向内存映射文件写入可能是危险的,只是改变数组的单个元素这样的简单操作,就可能会直接修改磁盘上的文件。修改数据与将数据保存到磁盘是没有分开的。
C
CyC2018 已提交
567

C
CyC2018 已提交
568
下面代码行将文件的前 1024 个字节映射到内存中,map() 方法返回一个 MappedByteBuffer,它是 ByteBuffer 的子类。因此,可以像使用其他任何 ByteBuffer 一样使用新映射的缓冲区,操作系统会在需要时负责执行映射。
C
CyC2018 已提交
569 570

```java
C
CyC2018 已提交
571
MappedByteBuffer mbb = fc.map(FileChannel.MapMode.READ_WRITE, 0, 1024);
C
CyC2018 已提交
572 573
```

C
CyC2018 已提交
574 575 576 577
## 对比

NIO 与普通 I/O 的区别主要有以下两点:

C
CyC2018 已提交
578 579
- NIO 是非阻塞的
- NIO 面向块,I/O 面向流
C
CyC2018 已提交
580 581 582 583 584

# 八、参考资料

- Eckel B, 埃克尔, 昊鹏, 等. Java 编程思想 [M]. 机械工业出版社, 2002.
- [IBM: NIO 入门](https://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html)
C
CyC2018 已提交
585 586 587
- [IBM: 深入分析 Java I/O 的工作机制](https://www.ibm.com/developerworks/cn/java/j-lo-javaio/index.html)
- [IBM: 深入分析 Java 中的中文编码问题](https://www.ibm.com/developerworks/cn/java/j-lo-chinesecoding/index.htm)
- [IBM: Java 序列化的高级认识](https://www.ibm.com/developerworks/cn/java/j-lo-serial/index.html)
C
CyC2018 已提交
588 589 590
- [NIO 与传统 IO 的区别](http://blog.csdn.net/shimiso/article/details/24990499)
- [Decorator Design Pattern](http://stg-tud.github.io/sedc/Lecture/ws13-14/5.3-Decorator.html#mode=document)
- [Socket Multicast](http://labojava.blogspot.com/2012/12/socket-multicast.html)