mirror of
https://github.com/LingCoder/OnJava8.git
synced 2026-09-04 13:52:47 +08:00
排版格式调整
This commit is contained in:
@@ -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,
|
||||

|
||||
|
||||
|
||||
将数据作为 **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*(标记、位置、限制和容量)。伴随着的还有一组方法可以设置和重置这些索引,并可查询它们的值。
|
||||
缓冲区由数据和四个索引组成,以有效地访问和操作该数据:mark、position、limit 和 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 设置为零并 设 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`|
|
||||
|
||||
从缓冲区插入和提取数据的方法通过更新索引来反映所做的更改。下例使用一种非常简单的算法(交换相邻字符)来对 **CharBuffer** 中的字符进行加扰和解扰。代码示例:
|
||||
|
||||
@@ -689,21 +689,21 @@ UsingBuffers
|
||||
|
||||
虽然可以通过使用 **char** 数组调用 `wrap()` 直接生成 **CharBuffer**,但是底层的 **ByteBuffer** 将被分配,而 **CharBuffer** 将作为 **ByteBuffer** 上的视图生成。这强调了目标始终是操作 **ByteBuffer**,因为它与通道交互。
|
||||
|
||||
下面是程序在 **symmetricgrab()** 方法入口时缓冲区的样子:
|
||||
下面是程序在 `symmetricgrab()` 方法入口时缓冲区的样子:
|
||||
|
||||

|
||||
|
||||
*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 的值。缓冲区的状态为:
|
||||
|
||||

|
||||
|
||||
两个相对 *get()* 调用将前两个字符的值保存在变量 `c1` 和 `c2` 中。在这两个调用之后,缓冲区看起来是这样的:
|
||||
两个相对 `get()` 调用将前两个字符的值保存在变量 `c1` 和 `c2` 中。在这两个调用之后,缓冲区看起来是这样的:
|
||||
|
||||

|
||||
|
||||
为了执行交换,我们在位置 0 处编写 `c2`,在位置 1 处编写 `c1`。我们可以使用绝对 `put()` 方法来实现这一点,或者用 `reset()` 方法,将 *position* 的值设置为 *mark*:
|
||||
为了执行交换,我们在位置 0 处编写 `c2`,在位置 1 处编写 `c1`。我们可以使用绝对 `put()` 方法来实现这一点,或者用 `reset()` 方法,将 position 的值设置为 mark:
|
||||
|
||||

|
||||
|
||||
@@ -711,11 +711,11 @@ UsingBuffers
|
||||
|
||||

|
||||
|
||||
在下一次循环中,将 *mark* 设置为 *position* 的当前值:
|
||||
在下一次循环中,将 mark 设置为 position 的当前值:
|
||||
|
||||

|
||||
|
||||
该过程将继续,直到遍历整个缓冲区为止。在 **while** 循环的末尾,*position* 位于缓冲区的末尾。如果显示缓冲区,则只显示位置和限制之间的字符。因此,要显示缓冲区的全部内容,必须使用 `rewind()` 将 *position* 设置为缓冲区的开始位置。这是 `rewind()` 调用后缓冲区的状态(*mark* 的值变成未定义):
|
||||
该过程将继续,直到遍历整个缓冲区为止。在 **while** 循环的末尾,position 位于缓冲区的末尾。如果显示缓冲区,则只显示位置和限制之间的字符。因此,要显示缓冲区的全部内容,必须使用 `rewind()` 将 position 设置为缓冲区的开始位置。这是 `rewind()` 调用后缓冲区的状态(mark 的值变成未定义):
|
||||
|
||||

|
||||
|
||||
@@ -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()` 查询锁的类型(共享或独占)。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user