<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    耐心無止境 成功一瞬間

    BlogJava 聯系 聚合 管理
      31 Posts :: 5 Stories :: 25 Comments :: 0 Trackbacks

    NIO的使用
    (轉自starchu1981)

    導讀
     J2SE1.4以上版本中發布了全新的I/O類庫。本文將通過一些實例來簡單介紹NIO庫提供的一些新特性:非阻塞I/O,字符轉換,緩沖以及通道。

    一. 介紹NIO
    NIO包(java.nio.*)引入了四個關鍵的抽象數據類型,它們共同解決傳統的I/O類中的一些問題。
    1. Buffer:它是包含數據且用于讀寫的線形表結構。其中還提供了一個特殊類用于內存映射文件的I/O操作。
    2. Charset:它提供Unicode字符串影射到字節序列以及逆影射的操作。
    3. Channels:包含socket,file和pipe三種管道,它實際上是雙向交流的通道。
    4. Selector:它將多元異步I/O操作集中到一個或多個線程中(它可以被看成是Unix中select()函數或Win32中WaitForSingleEvent()函數的面向對象版本)。
    二. 回顧傳統
    在介紹NIO之前,有必要了解傳統的I/O操作的方式。以網絡應用為例,傳統方式需要監聽一個ServerSocket,接受請求的連接為其提供服務(服務通常包括了處理請求并發送響應)圖一是服務器的生命周期圖,其中標有粗黑線條的部分表明會發生I/O阻塞。
     
       圖一
    可以分析創建服務器的每個具體步驟。首先創建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要用到線程(大量的線程)。一般是實現一個線程池用來處理請求,如圖二
     
           圖二
    線程使得服務器可以處理多個連接,但是它們也同樣引發了許多問題。每個線程擁有自己的??臻g并且占用一些CPU時間,耗費很大,而且很多時間是浪費在阻塞的I/O操作上,沒有有效的利用CPU。
    三. 新I/O
    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方法時,它查看注冊了的通道看是否有任何感興趣的事件發生。圖四是selector和兩個已注冊的通道的例子
     

            圖四
    并不是所有的通道都支持所有的操作。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 

    四. 舉例說明
    1. 簡單網頁內容下載
    這個例子非常簡單,類SocketChannelReader使用SocketChannel來下載特定網頁的HTML內容。
    package examples.nio;

    import java.nio.ByteBuffer;
    import java.nio.channels.SocketChannel;
    import java.nio.charset.Charset;
    import java.net.InetSocketAddress;
    import java.io.IOException;

    public class SocketChannelReader{
       
        private Charset charset=Charset.forName("UTF-8");//創建UTF-8字符集
        private SocketChannel channel;

        public void getHTMLContent(){
        try{
           connect();
           sendRequest();
           readResponse();
        }catch(IOException e){
           System.err.println(e.toString());
        }finally{
         if(channel!=null){
         try{
          channel.close();
      }catch(IOException e){}
         }
     }
        }
        private void connect()throws IOException{//連接到CSDN
     InetSocketAddress socketAddress=
         new InetSocketAddress("www.csdn.net",80);
     channel=SocketChannel.open(socketAddress);
     //使用工廠方法open創建一個channel并將它連接到指定地址上
     //相當與SocketChannel.open().connect(socketAddress);調用
    }

    private void sendRequest()throws IOException{
     channel.write(charset.encode("GET "
            +"/document"
            +"\r\n\r\n"));//發送GET請求到CSDN的文檔中心
     //使用channel.write方法,它需要CharByte類型的參數,使用
     //Charset.encode(String)方法轉換字符串。
        }

        private void readResponse()throws IOException{//讀取應答
     ByteBuffer buffer=ByteBuffer.allocate(1024);//創建1024字節的緩沖
     while(channel.read(buffer)!=-1){
         buffer.flip();//flip方法在讀緩沖區字節操作之前調用。
         System.out.println(charset.decode(buffer));
    //使用Charset.decode方法將字節轉換為字符串
         buffer.clear();//清空緩沖
     }
        }

        public static void main(String [] args){
     new SocketChannelReader().getHTMLContent();
        }
    2. 簡單的加法服務器和客戶機
    服務器代碼
    package examples.nio;

    import java.nio.ByteBuffer;
    import java.nio.IntBuffer;
    import java.nio.channels.ServerSocketChannel;
    import java.nio.channels.SocketChannel;
    import java.net.InetSocketAddress;
    import java.io.IOException;

    /**
     * SumServer.java
     *
     *
     * Created: Thu Nov 06 11:41:52 2003
     *
     * @author starchu1981
     * @version 1.0
     */
    public class SumServer {

        private ByteBuffer _buffer=ByteBuffer.allocate(8);
        private IntBuffer _intBuffer=_buffer.asIntBuffer();
        private SocketChannel _clientChannel=null;
        private ServerSocketChannel _serverChannel=null;

        public void start(){
     try{
         openChannel();
         waitForConnection();
     }catch(IOException e){
         System.err.println(e.toString());
     }
        }

        private void openChannel()throws IOException{
     _serverChannel=ServerSocketChannel.open();
     _serverChannel.socket().bind(new InetSocketAddress(10000));
     System.out.println("服務器通道已經打開");
        }

        private void waitForConnection()throws IOException{
     while(true){
         _clientChannel=_serverChannel.accept();
         if(_clientChannel!=null){
    System.out.println("新的連接加入");
    processRequest();
    _clientChannel.close();
         }
     }
        }

        private void processRequest()throws IOException{
     _buffer.clear();
     _clientChannel.read(_buffer);
     int result=_intBuffer.get(0)+_intBuffer.get(1);
     _buffer.flip();
     _buffer.clear();
     _intBuffer.put(0,result);
     _clientChannel.write(_buffer);
        }

        public static void main(String [] args){
     new SumServer().start();
        }
    } // SumServer
    客戶代碼
    package examples.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 SumClient {

        private ByteBuffer _buffer=ByteBuffer.allocate(8);
        private IntBuffer _intBuffer;
        private SocketChannel _channel;

        public SumClient() {
          _intBuffer=_buffer.asIntBuffer();
        } // SumClient constructor
       
        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;
        }

        private SocketChannel connect()throws IOException{
     InetSocketAddress socketAddress=
         new InetSocketAddress("localhost",10000);
     return SocketChannel.open(socketAddress);
        }
       
        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);
        }
       
        private int receiveResponse()throws IOException{
     _buffer.clear();
     _channel.read(_buffer);
     return _intBuffer.get(0);
        }

        public static void main(String [] args){
     SumClient sumClient=new SumClient();
     System.out.println("加法結果為 :"+sumClient.getSum(100,324));
        }
    } // SumClient
    3. 非阻塞的加法服務器
    首先在openChannel方法中加入語句
     _serverChannel.configureBlocking(false);//設置成為非阻塞模式

    重寫WaitForConnection方法的代碼如下,使用非阻塞方式
    private void waitForConnection()throws IOException{
     Selector acceptSelector = SelectorProvider.provider().openSelector(); 

     /*在服務器套接字上注冊selector并設置為接受accept方法的通知。
     這就告訴Selector,套接字想要在accept操作發生時被放在ready表
     上,因此,允許多元非阻塞I/O發生。*/
     SelectionKey acceptKey = ssc.register(acceptSelector,
               SelectionKey.OP_ACCEPT);
     int keysAdded = 0;
     
     /*select方法在任何上面注冊了的操作發生時返回*/
     while ((keysAdded = acceptSelector.select()) > 0) {
         // 某客戶已經準備好可以進行I/O操作了,獲取其ready鍵集合
         Set readyKeys = acceptSelector.selectedKeys();
         Iterator i = readyKeys.iterator();

         // 遍歷ready鍵集合,并處理加法請求
         while (i.hasNext()) {
      SelectionKey sk = (SelectionKey)i.next();
      i.remove();
      ServerSocketChannel nextReady =
          (ServerSocketChannel)sk.channel();
      // 接受加法請求并處理它
      _clientSocket = nextReady.accept().socket();
       processRequest();
       _clientSocket.close();
         }
      }
        }

    posted on 2005-07-21 17:49 Joshua Yan 閱讀(996) 評論(0)  編輯  收藏

    只有注冊用戶登錄后才能發表評論。


    網站導航:
     
    主站蜘蛛池模板: 亚洲国产超清无码专区| 亚洲精品A在线观看| 亚洲VA中文字幕无码一二三区| 亚洲成a人无码亚洲成www牛牛| 免费精品人在线二线三线区别| 亚洲国产精品午夜电影| 国内精自视频品线六区免费 | 亚洲成年人电影在线观看| 免费观看91视频| 亚洲av之男人的天堂网站| 成全视频高清免费观看电视剧| 亚洲中文久久精品无码| 丝袜捆绑调教视频免费区| 亚洲午夜国产精品无码 | 亚洲综合一区二区国产精品| 日韩视频在线观看免费| 亚洲一区二区三区高清| 久久午夜夜伦鲁鲁片免费无码影视 | 好看的亚洲黄色经典| 免费视频一区二区| 亚洲最大成人网色| 国产精品久久免费| 亚洲av无码av在线播放| 亚洲AV成人潮喷综合网| 精品免费久久久久国产一区 | 一区二区三区免费在线观看| 亚洲中文字幕无码一区| 99免费观看视频| 自拍偷区亚洲国内自拍| 亚洲区日韩区无码区| 免费无码又爽又刺激高潮视频| 亚洲专区中文字幕| 亚洲精品成人网久久久久久| 成人片黄网站色大片免费观看APP| 亚洲网站在线免费观看| 国产美女无遮挡免费视频| 国产成人无码精品久久久免费| 亚洲av无码不卡一区二区三区| 成年美女黄网站色大免费视频| 亚洲黄片手机免费观看| 亚洲精品午夜视频|