`

Java NIO介绍

    博客分类:
  • Java
阅读更多
前言:
可悲的是我参考的这些技术资料是在02~04年的,那时侯还在上学….
服务器在合理的时间之内处理大量客户机请求的能力取决于服务器使用 I/O 流的效率。同时为成百上千个客户机提供服务的服务器必须能够并发地使用 I/O 服务。Java 平台直到 JDK 1.4(也就是 Merlin)才支持非阻塞 I/O 调用。用 Java 语言写的服务器,由于其线程与客户机之比几乎是一比一,因而易于受到大量线程开销的影响,其结果是既导致了性能问题又缺乏可伸缩性。
为了解决这个问题,Java 平台的最新发行版引入了一组新的类。Merlin 的 java.nio 包充满了解决线程开销问题的技巧,包中最重要的是新的 SelectableChannel 类和 Selector 类。 通道(channel)是客户机和服务器之间的一种通信方式。 选择器(selector)与 Windows 消息循环类似,它从不同客户机捕获各种事件并将它们分派到相应的事件处理程序。在本文,我们将向您展示这两个类如何协同工作,从而为 Java 平台创建非阻塞 I/O 机制。
 
一:传统的IO
在介绍NIO之前,有必要了解传统的I/O操作的方式。以网络应用为例,传统方式需要监听一个ServerSocket,接受请求的连接为其提供服务(服务通常包括了处理请求并发送响应)
可以分析创建服务器的每个具体步骤。首先创建ServerSocket
 ServerSocket server=new ServerSocket(10000);
然后接受新的连接请求
 Socket newConnection=server.accept();
对于accept方法的调用将造成阻塞,直到ServerSocket接受到一个连接请求为止。一旦连接请求被接受,服务器可以读客户socket中的请求。
InputStream in = newConnection.getInputStream();
InputStreamReader reader = new InputStreamReader(in);
BufferedReader buffer = new BufferedReader(reader);
Request request = new Request();
while(!request.isComplete()) {
 String line = buffer.readLine();
 request.addLine(line);
}
这样的操作有两个问题,首先BufferedReader类的readLine()方法在其缓冲区未满时会造成线程阻塞,只有一定数据填满了缓 冲区或者客户关闭了套接字,方法才会返回。其次,它会产生大量的垃圾,BufferedReader创建了缓冲区来从客户套接字读入数据,但是同样创建了 一些字符串存储这些数据。虽然BufferedReader内部提供了StringBuffer处理这一问题,但是所有的String很快变成了垃圾需要 回收。
同样的问题在发送响应代码中也存在
Response response = request.generateResponse();
OutputStream out = newConnection.getOutputStream();
InputStream in = response.getInputStream();
int ch;
while(-1 != (ch = in.read())) {
 out.write(ch);
}
newConnection.close();
类似的,读写操作被阻塞而且向流中一次写入一个字符会造成效率低下,所以应该使用缓冲区,但是一旦使用缓冲,流又会产生更多的垃圾。传统的解决 方法通常在Java中处理阻塞I/O要用到线程(大量的线程)。一般是实现一个线程池用来处理请求,线程使得服务器可以处理多个连接,但是它们也同样引发 了许多问题。每个线程拥有自己的栈空间并且占用一些CPU时间,耗费很大,而且很多时间是浪费在阻塞的I/O操作上,没有有效的利用CPU。
问题产生原因:
1。建立连接可能造成阻塞
2。IO处理造成阻塞或者占用大量的cpu和内存
二:改进的IO
1. Buffer
传统的I/O不断的浪费对象资源(通常是String)。新I/O通过使用Buffer读写数据避免了资源浪费。Buffer对象是线性的,有序的数据集合,它根据其类别只包含唯一的数据类型。
java.nio.Buffer 类描述
java.nio.ByteBuffer 包含字节类型。 可以从ReadableByteChannel中读在    WritableByteChannel 中写
java.nio.MappedByteBuffer 包含字节类型,直接在内存某一区域映射
java.nio.CharBuffer 包含字符类型,不能写入通道
java.nio.DoubleBuffer 包含double类型,不能写入通道
java.nio.FloatBuffer 包含float类型
java.nio.IntBuffer 包含int类型
java.nio.LongBuffer 包含long类型
java.nio.ShortBuffer 包含short类型
可以通过调用allocate(int capacity)方法或者allocateDirect(int capacity)方法分配一个Buffer。特别的,你可以创建MappedBytesBuffer通过调用FileChannel.map(int mode,long position,int size)。直接(direct)buffer在内存中分配一段连续的块并使用本地访问方法读写数据。非直接(nondirect)buffer通过使用 Java中的数组访问代码读写数据。有时候必须使用非直接缓冲例如使用任何的wrap方法(如ByteBuffer.wrap(byte[]))在 Java数组基础上创建buffer。
2. 字符编码
向ByteBuffer中存放数据涉及到两个问题:字节的顺序和字符转换。ByteBuffer内部通过ByteOrder类处理了字节顺序问题,但是并没有处理字符转换。事实上,ByteBuffer没有提供方法读写String。
 Java.nio.charset.Charset处理了字符转换问题。它通过构造CharsetEncoder和CharsetDecoder将字符序列转换成字节和逆转换。
3. 通道(Channel)
你可能注意到现有的java.io类中没有一个能够读写Buffer类型,所以NIO中提供了Channel类来读写Buffer。通道可以认为是一种连接,可以是到特定设备,程序或者是网络的连接。
ReadableByteChannel和WritableByteChannel分别用于读写。
GatheringByteChannel可以从使用一次将多个Buffer中的数据写入通道,相反的,ScatteringByteChannel则可以一次将数据从通道读入多个Buffer中。你还可以设置通道使其为阻塞或非阻塞I/O操作服务。
为了使通道能够同传统I/O类相容,Channel类提供了静态方法创建Stream或Reader
4. Selector
在过去的阻塞I/O中,我们一般知道什么时候可以向stream中读或写,因为方法调用直到stream准备好时返回。但是使用非阻塞通道,我 们需要一些方法来知道什么时候通道准备好了。在NIO包中,设计Selector就是为了这个目的。SelectableChannel可以注册特定的事 件,而不是在事件发生时通知应用,通道跟踪事件。然后,当应用调用Selector上的任意一个selection方法时,它查看注册了的通道看是否有任 何感兴趣的事件发生。
并不是所有的通道都支持所有的操作。SelectionKey类定义了所有可能的操作位,将要用两次。首先,当应用调用 SelectableChannel.register(Selector sel,int op)方法注册通道时,它将所需操作作为第二个参数传递到方法中。然后,一旦SelectionKey被选中了,SelectionKey的 readyOps()方法返回所有通道支持操作的数位的和。SelectableChannel的validOps方法返回每个通道允许的操作。注册通道 不支持的操作将引发IllegalArgumentException异常。下表列出了SelectableChannel子类所支持的操作。
ServerSocketChannel OP_ACCEPT
SocketChannel OP_CONNECT, OP_READ, OP_WRITE
DatagramChannel OP_READ, OP_WRITE
Pipe.SourceChannel OP_READ
Pipe.SinkChannel OP_WRITE
三 列一个通讯的例子:简单却实用
服务器:
package com.sz.nio;
 
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.net.InetSocketAddress;
import java.util.Iterator;
import java.util.Set;
import java.io.IOException;
 
/**
  * SumServer.java
  * Created: Fri April 20 13:41:52 2007
  * @author pineapple
  * @version 1.0
  */
public class Server {
 
    private ByteBuffer buffer = ByteBuffer.allocate (8); // 新缓冲区的容量,以字节为单位
 
    private IntBuffer intBuffer = buffer .asIntBuffer();
 
    private SocketChannel socketChannel = null ;
 
    private ServerSocketChannel serverChannel = null ;
 
    /**
      * 开启服务
      */
    public void start() {
        try {
            openChannel();
            waitForConnection();
        } catch (IOException e) {
            System. err .println(e.toString());
        }
    }
 
    /**
      * 打开连接
      *
      * @throws IOException
      */
    private void openChannel() throws IOException {
 
        serverChannel = ServerSocketChannel.open (); // 创建Socket连接
        serverChannel .socket().bind( new InetSocketAddress(10000)); // 开启端口
        serverChannel .configureBlocking( false ); // 设置成为非阻塞模式
        System. out .println( " 服务器通道已经打开" );
    }
 
    /**
      * 连接接入,处理
      *
      * @throws IOException
      */
    private void waitForConnection() throws IOException {
       
// 在服务器套接字上注册selector并设置为接受accept方法的通知。
// 这就告诉Selector,套接字想要在accept操作发生时被放在ready表 上,因此,允许多元非阻塞I/O发生。
//Selector selector = Selector.open(); // 这是最简单的创建方法
 
        Selector acceptSelector = SelectorProvider.provider ().openSelector();
       
       
 
// 每个要为客户机请求提供服务的 Channel 都必须接着将自己向Selector 注册。
//Channel 应根据它将处理的事件进行注册。例如,接受传入连接的 Channel应这样注册
//Channel 向 Selector 的注册用 SelectionKey 对象表示。满足以下三个条件之一, Key 就失效:Channel
// 被关闭。Selector 被关闭。通过调用 Key 的 cancel() 方法将 Key 本身取消。
// 如果关键字是可接受(acceptable)的,则接受连接,注册通道,以接受更多的事件(例如:读或写操作)。
// 如果关键字是可读的(readable)或可写的(writable),则服务器会指示它已经就绪于读写本端数据    
       
        SelectionKey acceptKey = serverChannel .register(acceptSelector,SelectionKey. OP_ACCEPT );
 
        int keysAdded = 0;
 
//Server 是那个将自己向 Selector 注册以接受所有传入连接的 ServerSocketChannel Server
// 被注册后,我们根据每个关键字(key)的类型以迭代方式对一组关键字进行处理。一个关键字被处理完成后,
// 就都被从就绪关键字(readykeys)列表中除去,如下所示
//select 方法在任何上面注册了的操作发生时返回
//Selector 在 select()调用时阻塞。接着,它开始等待,直到建立了一个新的连接,或者另一个线程将它唤醒,
// 或者另一个线程将原来的阻塞线程中断。某客户已经准备好可以进行I/O操作了,获取其ready键集合
 
        while ((keysAdded = acceptSelector.select()) > 0) {
            Set readyKeys = acceptSelector.selectedKeys();
            Iterator i = readyKeys.iterator();
            // 遍历ready键集合,并处理加法请求
            while (i.hasNext()) {
                SelectionKey selectionkey = (SelectionKey) i.next();
                i.remove();
                ServerSocketChannel nextReady = (ServerSocketChannel) selectionkey.channel();
                // 接受加法请求并处理它
                // socketChannel = nextReady.accept().socket();
                socketChannel = nextReady.accept();
                processRequest();
                socketChannel .close();
            }
        }
    }
 
    private void processRequest() throws IOException {
        buffer .clear();
        socketChannel .read( buffer );
        int result = intBuffer .get(0) + intBuffer .get(1);
        buffer .flip();
        buffer .clear();
        intBuffer .put(0, result);
        socketChannel .write( buffer );
    }
 
    public static void main(String[] args) {
        new Server().start();
    }
}
 
客户端:
package com.sz.nio;
 
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.channels.SocketChannel;
import java.net.InetSocketAddress;
import java.io.IOException;
 
/**
  * SumClient.java
  *
  *
  * Created: Thu Nov 06 11:26:06 2003
  *
  * @author starchu1981
  * @version 1.0
  */
public class Client {
 
    private ByteBuffer buffer = ByteBuffer.allocate (8);
 
    private IntBuffer intBuffer ;
 
    private SocketChannel channel ;
 
    /**
      * 客户端构造函数
      *
      */
    public Client() {
        intBuffer = buffer .asIntBuffer();
    } // SumClient constructor
 
    /**
      * 连接服务器 发送客户请求 收到服务器响应
      *
      * @param first
      *             被计算的数字一
      * @param second
      *             被计算的数字二
      * @return 计算结果
      */
    public int getSum( int first, int second) {
        int result = 0;
        try {
            channel = connect();
            sendSumRequest(first, second);
            result = receiveResponse();
        } catch (IOException e) {
            System. err .println(e.toString());
        } finally {
            if ( channel != null ) {
                try {
                    channel .close();
                } catch (IOException e) {
                }
            }
        }
        return result;
    }
 
    /**
      * 创建连接
      *
      * @return 连接
      * @throws IOException
      */
    private SocketChannel connect() throws IOException {
        InetSocketAddress socketAddress = new InetSocketAddress( "localhost" ,
                10000);
        return SocketChannel.open (socketAddress);
    }
 
    /**
      * 发送请求
      *
      * @param first
      * @param second
      * @throws IOException
      */
    private void sendSumRequest( int first, int second) throws IOException {
        buffer .clear();
        intBuffer .put(0, first);
        intBuffer .put(1, second);
        channel .write( buffer );
        System. out .println( " 发送加法请求 " + first + "+" + second);
    }
 
    /**
      * 收到响应
      *
      * @return
      * @throws IOException
      */
    private int receiveResponse() throws IOException {
        buffer .clear();
        channel .read( buffer );
        return intBuffer .get(0);
    }
 
    public static void main(String[] args) {
        Client sumClient = new Client();
        System. out .println( " 加法结果为 :" + sumClient.getSum(100, 324));
    }
} // SumClient
 


http://blog.csdn.net/PineApple0/archive/2007/04/20/1572394.aspx

分享到:
评论
1 楼 joeyon 2012-02-01  

相关推荐

    java NIO和java并发编程的书籍

    java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java NIO和java并发编程的书籍java...

    JavaNIO chm帮助文档

    Java NIO系列教程(一) Java NIO 概述 Java NIO系列教程(二) Channel Java NIO系列教程(三) Buffer Java NIO系列教程(四) Scatter/Gather Java NIO系列教程(五) 通道之间的数据传输 Java NIO系列教程(六)...

    java nio 包读取超大数据文件

    Java nio 超大数据文件 超大数据文件Java nio 超大数据文件 超大数据文件Java nio 超大数据文件 超大数据文件Java nio 超大数据文件 超大数据文件Java nio 超大数据文件 超大数据文件Java nio 超大数据文件 超大数据...

    Java NIO英文高清原版

    Java NIO英文高清原版

    java NIO 中文版

    讲解了 JavaIO 与 JAVA NIO区别,JAVA NIO设计理念,以及JDK中java NIO中语法的使用

    Java NIO 中文 Java NIO 中文 Java NIO 中文文档

    Java NIO 深入探讨了 1.4 版的 I/O 新特性,并告诉您如何使用这些特性来极大地提升您所写的 Java 代码的执行效率。这本小册子就程序员所面临的有代表性的 I/O 问题作了详尽阐述,并讲解了 如何才能充分利用新的 I/O ...

    java NIO 视频教程

    Java NIO(New IO)是一个可以替代标准Java IO API的IO API(从Java 1.4开始),Java NIO提供了与标准IO不同的IO工作方式。 Java NIO: Channels and Buffers(通道和缓冲区) 标准的IO基于字节流和字符流进行操作的,...

    java nio 实现socket

    java nio 实现socketjava nio 实现socketjava nio 实现socketjava nio 实现socketjava nio 实现socket

    java nio中文版

    java NIO是 java New IO 的简称,在 jdk1.4 里提供的新 api 。 Sun 官方标榜的特性如下: – 为所有的原始类型提供 (Buffer) 缓存支持。 – 字符集编码解码解决方案。 – Channel :一个新的原始 I/O 抽象。 – 支持...

    Java nio详细介绍 详细介绍java nio

    java nio入门知识 java nio详细介绍总结

    java NIO 的详细介绍文档

    java NIO 的详细介绍文档,javaNIO是从java1.4开始出现的,解决了javaI/O流的复杂度开销,提供了多种简洁高效的I/O操作类

    Java Nio selector例程

    java侧起server(NioUdpServer1.java),基于Java Nio的selector 阻塞等候,一个android app(NioUdpClient1文件夹)和一个java程序(UI.java)作为两个client分别向该server发数据,server收到后分别打印收到的消息...

    java NIO.zip

    java NIO.zip

    java NIO技巧及原理

    java NIO技巧及原理解析,java IO原理,NIO框架分析,性能比较

    java nio 读文件

    java nio 读文件,java nio 读文件

    java基于NIO实现Reactor模型源码.zip

    java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现Reactor模型源码java基于NIO实现...

    JAVA NIO 学习资料

    JAVA NIO学习资料JAVA NIO学习资料

    基于Java NIO实现五子棋游戏.zip

    基于Java NIO实现五子棋游戏.zip基于Java NIO实现五子棋游戏.zip 基于Java NIO实现五子棋游戏.zip基于Java NIO实现五子棋游戏.zip 基于Java NIO实现五子棋游戏.zip基于Java NIO实现五子棋游戏.zip 基于Java NIO实现...

    Java NIO测试示例

    Java NIO测试示例

    Java NIO.pdf

    java nio编程 非阻塞模式的通信 电子书 带目录标签

Global site tag (gtag.js) - Google Analytics