排版格式调整

This commit is contained in:
LingCoder
2019-04-13 16:47:14 +08:00
parent cbd52bb4a1
commit bd9b6b2d6c
6 changed files with 93 additions and 100 deletions

View File

@@ -6,11 +6,11 @@
> Java 新I/O 库是在 1.4 版本引入到 `Java .nio.* package` 中的,旨在更快速。
实际上,新 I/O 使用 **NIO**(同步非阻塞)的方式重写了老的 I/O 了,因此它获得了 **NIO** 的种种优点。即使我们不显式地使用 **NIO** 方式来编写代码,也能带来性能和速度的提高。这种提升不仅仅体现在文件读写(*File I/O*),同时也体现在网络读写(*Network I/O*)中。例如,网络编程。
实际上,新 I/O 使用 **NIO**(同步非阻塞)的方式重写了老的 I/O 了,因此它获得了 **NIO** 的种种优点。即使我们不显式地使用 **NIO** 方式来编写代码也能带来性能和速度的提高。这种提升不仅仅体现在文件读写File I/O同时也体现在网络读写Network I/O中。例如网络编程。
速度的提升来自于使用了更接近操作系统 I/O 执行方式的结构:**Channel**(通道) 和 **Buffer**(缓冲区)。我们可以想象一个煤矿:通道就是连接矿层(数据)的矿井,缓冲区是运送煤矿的小车。通过小车装煤,再从车里取矿。换句话说,我们不能直接和 **Channel** 交互; 我们需要与 **Buffer** 交互并将 **Buffer** 中的数据发送到**Channel** 中;**Channel** 需要从 **Buffer** 中提取或放入数据。
速度的提升来自于使用了更接近操作系统 I/O 执行方式的结构:**Channel**(通道) 和 **Buffer**(缓冲区)。我们可以想象一个煤矿:通道就是连接矿层(数据)的矿井,缓冲区是运送煤矿的小车。通过小车装煤,再从车里取矿。换句话说,我们不能直接和 **Channel** 交互; 我们需要与 **Buffer** 交互并将 **Buffer** 中的数据发送到 **Channel** 中;**Channel** 需要从 **Buffer** 中提取或放入数据。
本篇我们将深入探讨 `nio` 包。虽然 像 I/O 流这样的高级库使用了 **NIO**但多数时候我们考虑这个层次的问题。使用Java 7 和 8 版本,理想情况下我们甚至不必费心去处理 I/O 流。当然,一些特殊情况除外。在[文件](./17-Files.md)*File*)一章中基本涵盖了我们日常使用的相关内容。只有在遇到性能瓶颈(例如内存映射文件)或创建自己的 I/O 库时,我们才需要去理解 **NIO**
本篇我们将深入探讨 `nio` 包。虽然 像 I/O 流这样的高级库使用了 **NIO**但多数时候我们考虑这个层次的问题。使用Java 7 和 8 版本,理想情况下我们甚至不必费心去处理 I/O 流。当然,一些特殊情况除外。在[文件](./17-Files.md)**File**)一章中基本涵盖了我们日常使用的相关内容。只有在遇到性能瓶颈(例如内存映射文件)或创建自己的 I/O 库时,我们才需要去理解 **NIO**
<!-- ByteBufferS -->
@@ -344,7 +344,7 @@ gb2312-1980
回到 **BufferToText.java** 中,如果你 `rewind()` 缓冲区(回到数据的开头),使用该平台的默认字符集 `decode()` 数据,那么生成的 **CharBuffer** 数据将在控制台上正常显示。可以通过 **System.getProperty(“file.encoding”)** 方法来查看平台默认字符集名称。传递该名称参数到 **Charset.forName()** 方法可以生成对应的 `Charset` 对象用于解码字符串。
回到 **BufferToText.java** 中,如果你 `rewind()` 缓冲区(回到数据的开头),使用该平台的默认字符集 `decode()` 数据,那么生成的 **CharBuffer** 数据将在控制台上正常显示。可以通过 `System.getProperty(“file.encoding”)` 方法来查看平台默认字符集名称。传递该名称参数到 `Charset.forName()` 方法可以生成对应的 `Charset` 对象用于解码字符串。
另一种方法是使用字符集 `encode()` 方法,该字符集在读取文件时生成可打印的内容,如你在 **BufferToText.java** 的第三部分中所看到的。上例中,**UTF-16BE** 被用于将文本写入文件,当文本被读取时,你所要做的就是将其转换为 **CharBuffer**,并生成预期的文本。
@@ -421,7 +421,7 @@ H o w d y !
在分配 **ByteBuffer** 之后,我们检查并确认它的 1,024 元素被初始化为 0。截至到达 `limit()` 结果的位置)。
将基本类型数据插入 **ByteBuffer** 的最简单方法就是使用 `asCharBuffer()``asShortBuffer()` 等方法获取该缓冲区适当的“视图”(*View*),然后调用该“视图”的 `put()` 方法。
将基本类型数据插入 **ByteBuffer** 的最简单方法就是使用 `asCharBuffer()``asShortBuffer()` 等方法获取该缓冲区适当的“视图”View然后调用该“视图”的 `put()` 方法。
这是针对每种基本数据类型执行的。其中唯一有点奇怪的是 **ShortBuffer**`put()`,它需要类型强制转换。其他视图缓冲区不需要在其 `put()` 方法中进行转换。
@@ -430,7 +430,7 @@ H o w d y !
## 视图缓冲区
“视图缓冲区”(*view buffer*)是通过特定的基本类型的窗口来查看底层 **ByteBuffer**。**ByteBuffer** 仍然是“支持”视图的实际存储,因此对视图所做的任何更改都反映在对 **ByteBuffer** 中的数据的修改中。
“视图缓冲区”view buffer是通过特定的基本类型的窗口来查看底层 **ByteBuffer**。**ByteBuffer** 仍然是“支持”视图的实际存储,因此对视图所做的任何更改都反映在对 **ByteBuffer** 中的数据的修改中。
如前面的示例所示,这方便地将基本类型插入 **ByteBuffer**。视图缓冲区还可以从 **ByteBuffer** 读取基本类型数据,每次单个(**ByteBuffer** 规定),或者批量读取到数组。下面是一个通过 **IntBuffer****ByteBuffer** 中操作 **int** 的例子:
@@ -563,7 +563,7 @@ Double Buffer 0 -> 4.8E-322,
<!-- Endians -->
### 字节存储次序
不同的机器可以使用不同的字节存储顺序(*Endians*)来存储数据。“高位优先”(*Big Endian*):将最重要的字节放在最低内存地址中,而“低位优先”(*Little Endian*):将最重要的字节放在最高内存地址中。
不同的机器可以使用不同的字节存储顺序Endians来存储数据。“高位优先”Big Endian将最重要的字节放在最低内存地址中而“低位优先”Little Endian将最重要的字节放在最高内存地址中。
当存储大于单字节的数据时,如 **int**、**float** 等,我们可能需要考虑字节排序问题。**ByteBuffer** 以“高位优先”形式存储数据;通过网络发送的数据总是使用“高位优先”形式。我们可以 使用 **ByteOrder**`order()` 方法和参数 **ByteOrder.BIG_ENDIAN****ByteOrder.LITTLE_ENDIAN** 来改变它的字节存储次序。
@@ -572,7 +572,7 @@ Double Buffer 0 -> 4.8E-322,
![1554546378822](../images/1554546378822.png)
将数据作为 **short** 型来读取(**ByteBuffer.asshortbuffer()**)),生成数字 97 00000000 01100001。更改为“低位优先”后 将生成数字 24832 01100001 00000000
将数据作为 **short** 型来读取(`ByteBuffer.asshortbuffer()`)),生成数字 97 00000000 01100001。更改为“低位优先”后 将生成数字 24832 01100001 00000000
这显示了字节顺序的变化取决于字节存储次序设置:
@@ -628,20 +628,20 @@ public class Endians {
### 缓冲区细节
缓冲区由数据和四个索引组成,以有效地访问和操作该数据:*mark*、*position*、*limit**capacity*(标记、位置、限制和容量)。伴随着的还有一组方法可以设置和重置这些索引,并可查询它们的值。
缓冲区由数据和四个索引组成以有效地访问和操作该数据markpositionlimit 和 capacity标记、位置、限制和容量。伴随着的还有一组方法可以设置和重置这些索引并可查询它们的值。
| | |
| :----- | :----- |
| **capacity()** | 返回缓冲区的 *capacity* |
|**clear()** |清除缓冲区,将 *position* 设置为零并 设 *limit**capacity*;可调用此方法来覆盖现有缓冲区|
|**flip()** | 将 *limit* 设置为 *position*,并将 *position* 设置为 0;此方法用于准备缓冲区,以便在数据写入缓冲区后进行读取|
|**limit()** |返回 *limit* 的值|
|**limit(int limit)**| 重设 *limit*|
|**mark()** |设置 *mark* 为当前的 *position* |
|**position()** |返回 *position* |
|**position(int pos)**| 设置 *position*|
|**remaining()** |返回 *limit**position* |
|**hasRemaining()**| 如果在 *position**limit* 中间有元素,返回 `true`|
| **capacity()** | 返回缓冲区的 capacity |
|**clear()** |清除缓冲区,将 position 设置为零并 设 limitcapacity;可调用此方法来覆盖现有缓冲区|
|**flip()** | 将 limit 设置为 position,并将 position 设置为 0;此方法用于准备缓冲区,以便在数据写入缓冲区后进行读取|
|**limit()** |返回 limit 的值|
|**limit(int limit)**| 重设 limit|
|**mark()** |设置 mark 为当前的 position |
|**position()** |返回 position |
|**position(int pos)**| 设置 position|
|**remaining()** |返回 limitposition |
|**hasRemaining()**| 如果在 positionlimit 中间有元素,返回 `true`|
从缓冲区插入和提取数据的方法通过更新索引来反映所做的更改。下例使用一种非常简单的算法(交换相邻字符)来对 **CharBuffer** 中的字符进行加扰和解扰。代码示例:
@@ -689,21 +689,21 @@ UsingBuffers
虽然可以通过使用 **char** 数组调用 `wrap()` 直接生成 **CharBuffer**,但是底层的 **ByteBuffer** 将被分配,而 **CharBuffer** 将作为 **ByteBuffer** 上的视图生成。这强调了目标始终是操作 **ByteBuffer**,因为它与通道交互。
下面是程序在 **symmetricgrab()** 方法入口时缓冲区的样子:
下面是程序在 `symmetricgrab()` 方法入口时缓冲区的样子:
![1554546627710](../images/1554546627710.png)
*position* 指向缓冲区中的第一个元素,*capacity**limit* 紧接在最后一个元素之后。在`symmetricgrab()` 中,**while** 循环迭代到 *position* 等于 *limit*。当在缓冲区上调用相对位置的 `get()``put()` 函数时,缓冲区的位置会发生变化。你可以调用绝对位置的 `get()``put()` 方法,它们包含索引参数:`get()``put()` 发生的位置。这些方法不修改缓冲区 *position* 的值。
position 指向缓冲区中的第一个元素capacity 和 limit 紧接在最后一个元素之后。在`symmetricgrab()` 中,**while** 循环迭代到 position 等于 limit。当在缓冲区上调用相对位置的 `get()``put()` 函数时,缓冲区的位置会发生变化。你可以调用绝对位置的 `get()``put()` 方法,它们包含索引参数:`get()``put()` 发生的位置。这些方法不修改缓冲区 position 的值。
当控件进入 **while** 循环时,使用 `mark()` 设置 *mark* 的值。缓冲区的状态为:
当控件进入 **while** 循环时,使用 `mark()` 设置 mark 的值。缓冲区的状态为:
![1554546666685](../images/1554546666685.png)
两个相对 *get()* 调用将前两个字符的值保存在变量 `c1``c2` 中。在这两个调用之后,缓冲区看起来是这样的:
两个相对 `get()` 调用将前两个字符的值保存在变量 `c1``c2` 中。在这两个调用之后,缓冲区看起来是这样的:
![1554546693664](../images/1554546693664.png)
为了执行交换,我们在位置 0 处编写 `c2`,在位置 1 处编写 `c1`。我们可以使用绝对 `put()` 方法来实现这一点,或者用 `reset()` 方法,将 *position* 的值设置为 *mark*
为了执行交换,我们在位置 0 处编写 `c2`,在位置 1 处编写 `c1`。我们可以使用绝对 `put()` 方法来实现这一点,或者用 `reset()` 方法,将 position 的值设置为 mark
![1554546847181](../images/1554546847181.png)
@@ -711,11 +711,11 @@ UsingBuffers
![1554546861836](../images/1554546861836.png)
在下一次循环中,将 *mark* 设置为 *position* 的当前值:
在下一次循环中,将 mark 设置为 position 的当前值:
![1554546881189](../images/1554546881189.png)
该过程将继续,直到遍历整个缓冲区为止。在 **while** 循环的末尾,*position* 位于缓冲区的末尾。如果显示缓冲区,则只显示位置和限制之间的字符。因此,要显示缓冲区的全部内容,必须使用 `rewind()`*position* 设置为缓冲区的开始位置。这是 `rewind()` 调用后缓冲区的状态(*mark* 的值变成未定义):
该过程将继续,直到遍历整个缓冲区为止。在 **while** 循环的末尾position 位于缓冲区的末尾。如果显示缓冲区,则只显示位置和限制之间的字符。因此,要显示缓冲区的全部内容,必须使用 `rewind()` 将 position 设置为缓冲区的开始位置。这是 `rewind()` 调用后缓冲区的状态mark 的值变成未定义):
![1554546890132](../images/1554546890132.png)
@@ -927,7 +927,7 @@ Stream Read/Write: 4.069
Mapped Read/Write: 0.013
```
**Tester** 使用了模板方法(*Template Method*)模式,它为匿名内部子类中定义的 `test()` 的各种实现创建一个测试框架。每个子类都执行一种测试,因此 `test()` 方法还提供了执行各种I/O 活动的原型。
**Tester** 使用了模板方法Template Method模式它为匿名内部子类中定义的 `test()` 的各种实现创建一个测试框架。每个子类都执行一种测试,因此 `test()` 方法还提供了执行各种I/O 活动的原型。
虽然映射的写似乎使用 **FileOutputStream**,但是文件映射中的所有输出必须使用 **RandomAccessFile**,就像前面代码中的读/写一样。
@@ -990,7 +990,7 @@ Released Lock
锁定文件的一部分,锁住 **size-position** 区域。第三个参数指定是否共享此锁。
虽然零参数锁定方法适应文件大小的变化,但是如果文件大小发生变化,具有固定大小的锁不会发生变化。如果从一个位置到另一个位置获得一个锁,并且文件的增长超过了 *position* + *size* ,那么超出 *position* + *size* 的部分没有被锁定。零参数锁定方法锁定整个文件,即使它在增长。
虽然零参数锁定方法适应文件大小的变化,但是如果文件大小发生变化,具有固定大小的锁不会发生变化。如果从一个位置到另一个位置获得一个锁,并且文件的增长超过了 position + size ,那么超出 position + size 的部分没有被锁定。零参数锁定方法锁定整个文件,即使它在增长。
底层操作系统必须提供对独占锁或共享锁的支持。如果操作系统不支持共享锁并且对一个操作系统发出请求,则使用独占锁。可以使用 **FileLock.**`isShared()` 查询锁的类型(共享或独占)。