1. io分类:
    基于字节操作的io接口:InputStream 和 OutputStream
    基于字符操作的io接口:Writer 和 Reader
    数据的传输方式,也就是数据写到哪里的问题,主要是以下两种:
    基于磁盘操作的io接口:File
    基于网络操作的io接口:Socket
    参考:1

  2. nio带来了什么:
    1)面向块的I/O
    传统JavaIO是面向流的I/O。流I/O一次处理一个字节。NIO则是面向块的I/O,每次操作都是以数据块为单位。 NIO中引入了缓冲区(Buffer)的概念,缓冲区作为传输数据的基本单位块,所有对数据的操作都是基于将数据移进/移出缓冲区而来; 读数据的时候从缓冲区中取,写的时候将数据填入缓冲区。尽管传统JavaIO中也有相应的缓冲区过滤器流(BufferedInputStream等), 但是移进/移出的操作是由程序员来包装的,它本质是对数据结构化和积累达到处理时的方便,并不是一种提高I/O效率的措施。 NIO的缓冲区则不然,对缓冲区的移进/移出操作是由底层操作系统来实现的。通常一次缓冲区操作是这样的:某个进程需要进行I/O操作, 它执行了一次读(read)或者写(write)的系统调用,向底层操作系统发出了请求,操作系统会按要求把数据缓冲区填满或者排干。 说起来简单,其实很复杂。但至少我们知道了这事是由操作系统干的,比我们代码级的实现要高效的多。 除了效率上的差别外,缓冲区在数据分析和处理上也带来的很大的便利和灵活性。
    2)非阻塞的I/O + 就绪性选择
    传统JavaIO是基于阻塞I/O模型的:当发起一个I/O请求时,如果数据没有准备好(read时无可读数据,write时数据不可写入), 那么线程便会阻塞,直到数据准备好,导致线程大部分的时间都在阻塞。 而非阻塞I/O则允许线程在有数据的时候处理数据,没有数据的时候干点别的,提高了资源利用率。 就绪性选择通常是建立在非阻塞的基础上,并且更进一步,它把检查哪些I/O请求的数据准备好这个任务交给了底层操作系统, 操作系统会去查看并返回结果集合,这样我们只需要关心那些准备好进行操作的IO通道。
    3)文件锁定和内存映射文件等操作系统特性
    NIO同时带来了很多当今操作系统大都支持的特性。 文件锁定是多个进程协同工作的情况下,要协调进程间对共享数据的访问必不可少的工具。 内存映射利用虚拟内存技术提供对文件的高速缓存,使读取磁盘文件就像从内存中读取一样高效, 但是却不会有内存泄漏的危险,因为在内存中不会存在文件的完整拷贝。
    参考:1

  3. nio和io的差异:
    1)面向流与面向缓冲
    Java NIO和IO之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。 此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。 Java NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。 这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。 而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。
    2)阻塞与非阻塞IO Java IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞, 直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 Java NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据, 如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变的可以读取之前, 该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道, 但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。
    参考:
    1

  4. 通道和缓冲区
    通道 和 缓冲区 是 NIO 中的核心对象,几乎在每一个 I/O 操作中都要使用它们。 通道是对原 I/O 包中的流的模拟。到任何目的地(或来自任何地方)的所有数据都必须通过一个 Channel 对象。 一个 Buffer 实质上是一个容器对象。发送给一个通道的所有对象都必须首先放到缓冲区中; 同样地,从通道中读取的任何数据都要读到缓冲区中。
    1)缓冲区
    Buffer 是一个对象,它包含一些要写入或者刚读出的数据。 在 NIO 中加入 Buffer 对象,体现了新库与原 I/O 的一个重要区别。 在面向流的 I/O 中,您将数据直接写入或者将数据直接读到 Stream 对象中。 在 NIO 库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的。 在写入数据时,它是写入到缓冲区中的。任何时候访问 NIO 中的数据,您都是将它放到缓冲区中。 缓冲区实质上是一个数组。通常它是一个字节数组,但是也可以使用其他种类的数组。 但是一个缓冲区不 仅仅 是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。
    缓冲区类型:最常用的缓冲区类型是 ByteBuffer。 一个 ByteBuffer 可以在其底层字节数组上进行 get/set 操作(即字节的获取和设置)。 每一个 Buffer 类都是 Buffer 接口的一个实例。 除了 ByteBuffer,每一个 Buffer 类都有完全一样的操作, 只是它们所处理的数据类型不一样。因为大多数标准 I/O 操作都使用 ByteBuffer, 所以它具有所有共享的缓冲区操作以及一些特有的操作。
    2)通道
    Channel是一个对象,可以通过它读取和写入数据。拿 NIO 与原来的 I/O 做个比较,通道就像是流。 正如前面提到的,所有数据都通过 Buffer 对象来处理。您永远不会将字节直接写入通道中, 相反,您是将数据写入包含一个或者多个字节的缓冲区。同样,您不会直接从通道中读取字节, 而是将数据从通道读入缓冲区,再从缓冲区获取这个字节。
    通道类型
    通道与流的不同之处在于通道是双向的。 而流只是在一个方向上移动(一个流必须是 InputStream 或者 OutputStream 的子类), 而 通道 可以用于读、写或者同时用于读写。因为它们是双向的,所以通道可以比流更好地反映底层操作系统的真实情况。 特别是在 UNIX 模型中,底层操作系统通道是双向的。

  5. 从文件中读取
    在 NIO 系统中,任何时候执行一个读操作,您都是从通道中读取,但是您不是 直接 从通道读取。 因为所有数据最终都驻留在缓冲区中,所以您是从通道读到缓冲区中。因此读取文件涉及三个步骤: 1) 从 FileInputStream 获取 Channel: FileInputStream fin = new FileInputStream( "readandshow.txt" ); FileChannel fc = fin.getChannel();
    2) 创建(缓冲区)Buffer:ByteBuffer buffer = ByteBuffer.allocate( 1024 ); 3) 将数据从 Channel 读到 Buffer 中:fc.read( buffer );

  6. 写入文件
    在 NIO 中写入文件类似于从文件中读取。首先从 FileOutputStream 获取一个通道: FileOutputStream fout = new FileOutputStream( "writesomebytes.txt" ); FileChannel fc = fout.getChannel();
    下一步是创建一个缓冲区并在其中放入一些数据, ByteBuffer buffer = ByteBuffer.allocate( 1024 ); buffer.put("info"); buffer.flip();
    最后一步是写入缓冲区中: fc.write( buffer );

  7. 读写结合
    clear() 方法重设缓冲区,使它可以接受读入的数据。 它将 limit 设置为与 capacity 相同。 它设置 position 为 0。这个操作相当于复位。
    flip() 方法让缓冲区可以将新读入的数据写入另一个通道。 它将 limit 设置为当前 position。 它将 position 设置为 0。
    下面的内部循环概括了使用缓冲区将数据从输入通道拷贝到输出通道的过程。

while (true) {
     buffer.clear();
     int r = fcin.read( buffer );

     if (r==-1) {
       break;
     }

     buffer.flip();
     fcout.write( buffer );
}

read() 和 write() 调用得到了极大的简化,因为许多工作细节都由缓冲区完成了。 clear() 和 flip() 方法用于让缓冲区在读和写之间切换。

  1. 缓冲区内部细节
    1)状态变量:position 变量跟踪已经写了多少数据。更准确地说,它指定了下一个字节将放到数组的哪一个元素中。 limit 变量表明还有多少数据需要取出(在从缓冲区写入通道时),或者还有多少空间可以放入数据(在从通道读入缓冲区时)。 position 总是小于或者等于 limit。缓冲区的 capacity 表明可以储存在缓冲区中的最大数据容量。 实际上,它指定了底层数组的大小 ― 或者至少是指定了准许我们使用的底层数组的容量。 limit 决不能大于 capacity。
    2)缓冲区分配和包装:
    在能够读和写之前,必须有一个缓冲区。要创建缓冲区,您必须 分配 它。我们使用静态方法 allocate() 来分配缓冲区: ByteBuffer buffer = ByteBuffer.allocate( 1024 ); allocate() 方法分配一个具有指定大小的底层数组,并将它包装到一个缓冲区对象中 ― 在本例中是一个 ByteBuffer。 您还可以将一个现有的数组转换为缓冲区,如下所示: byte array[] = new byte[1024]; ByteBuffer buffer = ByteBuffer.wrap( array ); 本例使用了 wrap() 方法将一个数组包装为缓冲区。必须非常小心地进行这类操作。一旦完成包装,底层数据就可以通过缓冲区或者直接访问。

  2. 异步 I/O
    异步 I/O 是一种 没有阻塞地 读写数据的方法。通常,在代码进行 read() 调用时,代码会阻塞直至有可供读取的数据。 同样, write() 调用将会阻塞直至数据能够写入。另一方面,异步 I/O 调用不会阻塞。 相反,您将注册对特定 I/O 事件的兴趣 ― 可读的数据的到达、新的套接字连接,等等,而在发生这样的事件时,系统将会告诉您。 异步 I/O 的一个优势在于,它允许您同时根据大量的输入和输出执行 I/O。 同步程序常常要求助于轮询,或者创建许许多多的线程以处理大量的连接。 使用异步 I/O,您可以监听任何数量的通道上的事件,不用轮询,也不用额外的线程。
    异步 I/O 中的核心对象名为 Selector。Selector 就是您注册对各种 I/O 事件的兴趣的地方, 而且当那些事件发生时,就是这个对象告诉您所发生的事件。所以,我们需要做的第一件事就是创建一个 Selector: Selector selector = Selector.open(); 然后,我们将对不同的通道对象调用 register() 方法,以便注册我们对这些对象中发生的 I/O 事件的兴趣。 register() 的第一个参数总是这个 Selector。
    参考:IBM



blog comments powered by Disqus

Published

20 September 2015

Category

tech_world

Tags