NIO与标准IO不同

来源:互联网 发布:x77论坛大陆永久域名 编辑:程序博客网 时间:2024/06/13 07:08
Java NIO提供了与标准IO不同的IO工作方式: 

  • Channels and Buffers(通道和缓冲区):标准的IO基于字节流和字符流进行操作的,而NIO是基于通道(Channel)和缓冲区(Buffer)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。
  • Asynchronous IO(异步IO):Java NIO可以让你异步的使用IO,例如:当线程从通道读取数据到缓冲区时,线程还是可以进行其他事情。当数据被写入到缓冲区时,线程可以继续处理它。从缓冲区写入通道也类似。

  • Selectors(选择器):Java NIO引入了选择器的概念,选择器用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个的线程可以监听多个数据通道。
  • java NIO 和阻塞I/O的区别 

    1. 阻塞I/O通信模型
     

    假如现在你对阻塞I/O已有了一定了解,我们知道阻塞I/O在调用InputStream.read()方法时是阻塞的,它会一直等到数据到来时(或超时)才会返回;同样,在调用ServerSocket.accept()方法时,也会一直阻塞到有客户端连接才会返回,每个客户端连接过来后,服务端都会启动一个线程去处理该客户端的请求。阻塞I/O的通信模型示意图如下:

     

     

    如果你细细分析,一定会发现阻塞I/O存在一些缺点。根据阻塞I/O通信模型,我总结了它的两点缺点:
    1. 当客户端多时,会创建大量的处理线程。且每个线程都要占用栈空间和一些CPU时间

    2. 阻塞可能带来频繁的上下文切换,且大部分上下文切换可能是无意义的。

    在这种情况下非阻塞式I/O就有了它的应用前景。

    2. 
    java NIO原理及通信模型 

    Java NIO是在jdk1.4开始使用的,它既可以说成“新I/O”,也可以说成非阻塞式I/O。下面是java NIO的工作原理:

    1. 由一个专门的线程来处理所有的 IO 事件,并负责分发。 
    2. 事件驱动机制:事件到的时候触发,而不是同步的去监视事件。 
    3. 线程通讯:线程之间通过 wait,notify 等方式通讯。保证每次上下文切换都是有意义的。减少无谓的线程切换。 

    阅读过一些资料之后,下面贴出我理解的java NIO的工作原理图:

     

     

    (注:每个线程的处理流程大概都是读取数据、解码、计算处理、编码、发送响应。)


  • Java NIO vs. IO

    Evolve your approach to Application Performance Monitoring by adopting five best practices that are outlined and explored in this e-book, brought to you in partnership with BMC.

    When studying both the Java NIO and IO API's, a question quickly pops into mind:

    When should I use IO and when should I use NIO?

    In this text I will try to shed some light on the differences between Java NIO and IO, their use cases, and how they affect the design of your code.

    Main Differences of Java NIO and IO

    The table below summarizes the main differences between Java NIO and IO. I will get into more detail about each difference in the sections following the table.

    IONIOStream orientedBuffer orientedBlocking IONon blocking IO
    Selectors

    Stream Oriented vs. Buffer Oriented

    The first big difference between Java NIO and IO is that IO is stream oriented, where NIO is buffer oriented. So, what does that mean?

    Java IO being stream oriented means that you read one or more bytes at a time, from a stream. What you do with the read bytes is up to you. They are not cached anywhere. Furthermore, you cannot move forth and back in the data in a stream. If you need to move forth and back in the data read from a stream, you will need to cache it in a buffer first.

    Java NIO's buffer oriented approach is slightly different. Data is read into a buffer from which it is later processed. You can move forth and back in the buffer as you need to. This gives you a bit more flexibility during processing. However, you also need to check if the buffer contains all the data you need in order to fully process it. And, you need to make sure that when reading more data into the buffer, you do not overwrite data in the buffer you have not yet processed.

    Blocking vs. Non-blocking IO

    Java IO's various streams are blocking. That means, that when a thread invokes a read() or write(), that thread is blocked until there is some data to read, or the data is fully written. The thread can do nothing else in the meantime.

    Java NIO's non-blocking mode enables a thread to request reading data from a channel, and only get what is currently available, or nothing at all, if no data is currently available. Rather than remain blocked until data becomes available for reading, the thread can go on with something else.

    The same is true for non-blocking writing. A thread can request that some data be written to a channel, but not wait for it to be fully written. The thread can then go on and do something else in the mean time.

    What threads spend their idle time on when not blocked in IO calls, is usually performing IO on other channels in the meantime. That is, a single thread can now manage multiple channels of input and output.

    Selectors

    Java NIO's selectors allow a single thread to monitor multiple channels of input. You can register multiple channels with a selector, then use a single thread to "select" the channels that have input available for processing, or select the channels that are ready for writing. This selector mechanism makes it easy for a single thread to manage multiple channels.

    How NIO and IO Influences Application Design

    Whether you choose NIO or IO as your IO toolkit may impact the following aspects of your application design:

    1. The API calls to the NIO or IO classes.
    2. The processing of data.
    3. The number of thread used to process the data.

    The API Calls

    Of course the API calls when using NIO look different than when using IO. This is no surprise. Rather than just read the data byte for byte from e.g. an InputStream, the data must first be read into a buffer, and then be processed from there.

    The Processing of Data

    The processing of the data is also affected when using a pure NIO design, vs. an IO design.

    In an IO design you read the data byte for byte from an InputStream or a Reader. Imagine you were processing a stream of line based textual data. For instance:

    Name: AnnaAge: 25Email: anna@mailserver.comPhone: 1234567890

    This stream of text lines could be processed like this:

    InputStream input = ... ; // get the InputStream from the client socketBufferedReader reader = new BufferedReader(new InputStreamReader(input));String nameLine   = reader.readLine();String ageLine    = reader.readLine();String emailLine  = reader.readLine();String phoneLine  = reader.readLine();

    Notice how the processing state is determined by how far the program has executed. In other words, once the first reader.readLine() method returns, you know for sure that a full line of text has been read. The readLine() blocks until a full line is read, that's why. You also know that this line contains the name. Similarly, when the second readLine() call returns, you know that this line contains the age etc.

    As you can see, the program progresses only when there is new data to read, and for each step you know what that data is. Once the executing thread have progressed past reading a certain piece of data in the code, the thread is not going backwards in the data (mostly not). This principle is also illustrated in this diagram:

    Java IO: Reading data from a blocking stream.Java IO: Reading data from a blocking stream.

    A NIO implementation would look different. Here is a simplified example:

    ByteBuffer buffer = ByteBuffer.allocate(48);int bytesRead = inChannel.read(buffer);

    Notice the second line which reads bytes from the channel into the ByteBuffer. When that method call returns you don't know if all the data you need is inside the buffer. All you know is that the buffer contains some bytes. This makes processing somewhat harder.

    Imagine if, after the first read(buffer) call, that all what was read into the buffer was half a line. For instance, "Name: An". Can you process that data? Not really. You need to wait until at leas a full line of data has been into the buffer, before it makes sense to process any of the data at all.

    So how do you know if the buffer contains enough data for it to make sense to be processed? Well, you don't. The only way to find out, is to look at the data in the buffer. The result is, that you may have to inspect the data in the buffer several times before you know if all the data is inthere. This is both inefficient, and can become messy in terms of program design. For instance:

    ByteBuffer buffer = ByteBuffer.allocate(48);int bytesRead = inChannel.read(buffer);while(! bufferFull(bytesRead) ) {    bytesRead = inChannel.read(buffer);}

    The bufferFull() method has to keep track of how much data is read into the buffer, and return either true or false, depending on whether the buffer is full. In other words, if the buffer is ready for processing, it is considered full.

    The bufferFull() method scans through the buffer, but must leave the buffer in the same state as before the bufferFull() method was called. If not, the next data read into the buffer might not be read in at the correct location. This is not impossible, but it is yet another issue to watch out for.

    If the buffer is full, it can be processed. If it is not full, you might be able to partially process whatever data is there, if that makes sense in your particular case. In many cases it doesn't.

    The is-data-in-buffer-ready loop is illustrated in this diagram:

    Java NIO: Reading data from a channel until all needed data is in buffer.Java NIO: Reading data from a channel until all needed data is in buffer.


    Summary

    NIO allows you to manage multiple channels (network connections or files) using only a single (or few) threads, but the cost is that parsing the data might be somewhat more complicated than when reading data from a blocking stream.

    If you need to manage thousands of open connections simultanously, which each only send a little data, for instance a chat server, implementing the server in NIO is probably an advantage. Similarly, if you need to keep a lot of open connections to other computers, e.g. in a P2P network, using a single thread to manage all of your outbound connections might be an advantage. This one thread, multiple connections design is illustrated in this diagram:

    Java NIO: A single thread managing multiple connections.Java NIO: A single thread managing multiple connections.

    If you have fewer connections with very high bandwidth, sending a lot of data at a time, perhaps a classic IO server implementation might be the best fit. This diagram illustrates a classic IO server design:

    Java IO: A classic IO server design - one connection handled by one thread.Java IO: A classic IO server design - one connection handled by one thread.

    From http://tutorials.jenkov.com/java-nio/nio-vs-io.html

    Learn tips and best practices for optimizing your capacity management strategy with the Market Guide for Capacity Management, brought to you in partnership with BMC.

0 0
原创粉丝点击