java基礎篇---新I/O技術(NIO)

在JDK1.4之前,I/O輸入輸出處理,咱們把它稱爲舊I/O處理,在JDK1.4開始,java提供了一系列改進的輸入/輸出新特性,這些功能被稱爲新I/O(NEW I/O),新添了許多用於處理輸入/輸出的類,這些類都被放在java.nio包及子包下,而且對原java.io包中的不少類以NIO爲基礎進行了改寫,新添了知足新I/O的功能。

Java NIO和IO的主要區別

IO  NIO
面向流
面向緩衝
阻塞IO 
 非阻塞IO
選擇器

面向緩衝(Buffer)

在整個Java的心I/O中,因此操做都是以緩衝區進行的,使操做的性能大大提升。java

操做

在Buffer中存在一系列的狀態變量,這狀態變量隨着寫入或讀取均可能會被概念,在緩衝區開元使用是三個值表示緩衝區的狀態。數組

  • position:表示下個緩衝區讀取或寫入的操做指針,沒向緩衝區中華寫入數據的時候 此指針就會改變,指針永遠放在寫入的最後一個元素以後。即:若是寫入了4個位置的數據,則posotion會指向第5個位置。
  • Limit:表示還有多少數據能夠存儲或讀取,position<=limit
  • capacity:表示緩衝區的最大容量,limit<=capacity,此值在分配緩衝區時被設置。通常不改變。

建立緩衝區:服務器

import java.nio.IntBuffer ;
public class IntBufferDemo{
    public static void main(String args[]){
        IntBuffer buf = IntBuffer.allocate(10) ;    // 準備出10個大小的緩衝區
        System.out.print("一、寫入數據以前的position、limit和capacity:") ;
        System.out.println("position = " + buf.position() + ",limit = " + buf.limit() + ",capacty = " + buf.capacity()) ;
        int temp[] = {5,7,9} ;// 定義一個int數組
        buf.put(3) ;    // 設置一個數據
        buf.put(temp) ;    // 此時已經存放了四個記錄
        System.out.print("二、寫入數據以後的position、limit和capacity:") ;
        System.out.println("position = " + buf.position() + ",limit = " + buf.limit() + ",capacty = " + buf.capacity()) ;

        buf.flip() ;    // 重設緩衝區
        // postion = 0 ,limit = 本來position
        System.out.print("三、準備輸出數據時的position、limit和capacity:") ;
        System.out.println("position = " + buf.position() + ",limit = " + buf.limit() + ",capacty = " + buf.capacity()) ;
        System.out.print("緩衝區中的內容:") ;
        while(buf.hasRemaining()){
            int x = buf.get() ;
            System.out.print(x + "、") ;
        }
    }
}

若是建立了緩衝區,則JVM可直接對其執行本機的IO操做網絡

import java.nio.ByteBuffer ;
public class ByteBufferDemo{
    public static void main(String args[]){
        ByteBuffer buf = ByteBuffer.allocateDirect(10) ;    // 準備出10個大小的緩衝區
        byte temp[] = {1,3,5,7,9} ;    // 設置內容
        buf.put(temp) ;    // 設置一組內容
        buf.flip() ;

        System.out.print("主緩衝區中的內容:") ;
        while(buf.hasRemaining()){
            int x = buf.get() ;
            System.out.print(x + "、") ;
        }
    }
}

通道(Channel)

Java NIO的非阻塞模式,使一個線程從某通道發送請求讀取數據,可是它僅能獲得目前可用的數據,若是目前沒有數據可用時,就什麼都不會獲取。而不是保持線程阻塞,因此直至數據變的能夠讀取以前,該線程能夠繼續作其餘的事情。 非阻塞寫也是如此。一個線程請求寫入一些數據到某通道,但不須要等待它徹底寫入,這個線程同時能夠去作別的事情。 線程一般將非阻塞IO的空閒時間用於在其它通道上執行IO操做,因此一個單獨的線程如今能夠管理多個輸入和輸出通道(channel)。多線程

Java NIO的通道相似流,但又有些不一樣:app

  • 既能夠從通道中讀取數據,又能夠寫數據到通道。但流的讀寫一般是單向的。
  • 通道能夠異步地讀寫。
  • 通道中的數據老是要先讀到一個Buffer,或者老是要從一個Buffer中寫入。

正如上面所說,從通道讀取數據到緩衝區,從緩衝區寫入數據到通道。dom

Channel的實現

這些是Java NIO中最重要的通道的實現:異步

  • FileChannel
  • DatagramChannel
  • SocketChannel
  • ServerSocketChannel

FileChannel 從文件中讀寫數據。socket

DatagramChannel 能經過UDP讀寫網絡中的數據。post

SocketChannel 能經過TCP讀寫網絡中的數據。

ServerSocketChannel能夠監聽新進來的TCP鏈接,像Web服務器那樣。對每個新進來的鏈接都會建立一個SocketChannel。

經過通道能夠完成雙向的輸入和輸出操做。在通道還有一種方式稱爲內存映射

幾種讀入的方式的比較

RandomAccessFile   較慢

FileInputStream     較慢

緩衝讀取      速度較快
內存映射      速度最快

FileChannel內存映射實例

import java.nio.ByteBuffer ;
import java.nio.MappedByteBuffer ;
import java.nio.channels.FileChannel ;
import java.io.File ;
import java.io.FileOutputStream ;
import java.io.FileInputStream ;
public class FileChannelDemo03{
    public static void main(String args[]) throws Exception{
        File file = new File("d:" + File.separator + "oumyye.txt") ;  
        FileInputStream input = null ;
        input = new FileInputStream(file) ;
        FileChannel fin = null ;    // 定義輸入的通道
        fin = input.getChannel() ;    // 獲得輸入的通道
        MappedByteBuffer mbb = null ; 
        mbb = fin.map(FileChannel.MapMode.READ_ONLY,0,file.length()) ;
        byte data[] = new byte[(int)file.length()] ;    // 開闢空間接收內容
        int foot = 0 ;
        while(mbb.hasRemaining()){
            data[foot++] = mbb.get() ;    // 讀取數據
        }
        System.out.println(new String(data)) ;    // 輸出內容
        fin.close() ;
        input.close() ;
    }
}

操做以上代碼的時候,執行的是寫入操做則多是很是危險的,由於僅僅只是改變數組中的單個元素這種簡單的操做,就可能直接修改磁盤上的文件,由於修改數據與數據保存在磁盤上是同樣的。

 

選擇器(Selectors

Selector(選擇器)是Java NIO中可以檢測一到多個NIO通道,並可以知曉通道是否爲諸如讀寫事件作好準備的組件。這樣,一個單獨的線程能夠管理多個channel,從而管理多個網絡鏈接。

爲何使用Selector?

僅用單個線程來處理多個Channels的好處是,只須要更少的線程來處理通道。事實上,能夠只用一個線程處理全部的通道。對於操做系統來講,線程之間上下文切換的開銷很大,並且每一個線程都要佔用系統的一些資源(如內存)。所以,使用的線程越少越好。

可是,須要記住,現代的操做系統和CPU在多任務方面表現的愈來愈好,因此多線程的開銷隨着時間的推移,變得愈來愈小了。實際上,若是一個CPU有多個內核,不使用多任務多是在浪費CPU能力。無論怎麼說,關於那種設計的討論應該放在另外一篇不一樣的文章中。在這裏,只要知道使用Selector可以處理多個通道就足夠了。

要點

使用Selector能夠構建一個非阻塞的網絡服務。

在新IO實現網絡程序須要依靠ServerSocketChannel類與SocketChannel

Selector實例

下面使用Selector完成一個簡單的服務器的操做,服務器能夠同時在多個端口進行監聽,此服務器的主要功能是返回當前時間。

import java.net.InetSocketAddress ;
import java.net.ServerSocket ;
import java.util.Set ;
import java.util.Iterator ;
import java.util.Date ;
import java.nio.channels.ServerSocketChannel ;
import java.nio.ByteBuffer ;
import java.nio.channels.SocketChannel ;
import java.nio.channels.Selector  ;
import java.nio.channels.SelectionKey  ;
public class DateServer{
    public static void main(String args[]) throws Exception {
        int ports[] = {8000,8001,8002,8003,8005,8006} ; // 表示五個監聽端口
        Selector selector = Selector.open() ;    // 經過open()方法找到Selector
        for(int i=0;i<ports.length;i++){
            ServerSocketChannel initSer = null ;
            initSer = ServerSocketChannel.open() ;    // 打開服務器的通道
            initSer.configureBlocking(false) ;    // 服務器配置爲非阻塞
            ServerSocket initSock = initSer.socket() ;
            InetSocketAddress address = null ;
            address = new InetSocketAddress(ports[i]) ;    // 實例化綁定地址
            initSock.bind(address) ;    // 進行服務的綁定
            initSer.register(selector,SelectionKey.OP_ACCEPT) ;    // 等待鏈接
            System.out.println("服務器運行,在" + ports[i] + "端口監聽。") ;
        }
        // 要接收所有生成的key,並經過鏈接進行判斷是否獲取客戶端的輸出
        int keysAdd = 0 ;
        while((keysAdd=selector.select())>0){    // 選擇一組鍵,而且相應的通道已經準備就緒
            Set<SelectionKey> selectedKeys = selector.selectedKeys() ;// 取出所有生成的key
            Iterator<SelectionKey> iter = selectedKeys.iterator() ;
            while(iter.hasNext()){
                SelectionKey key = iter.next() ;    // 取出每個key
                if(key.isAcceptable()){
                    ServerSocketChannel server = (ServerSocketChannel)key.channel() ;
                    SocketChannel client = server.accept() ;    // 接收新鏈接
                    client.configureBlocking(false) ;// 配置爲非阻塞
                    ByteBuffer outBuf = ByteBuffer.allocateDirect(1024) ;    //
                    outBuf.put(("當前的時間爲:" + new Date()).getBytes()) ;    // 向緩衝區中設置內容
                    outBuf.flip() ;
                    client.write(outBuf) ;    // 輸出內容
                    client.close() ;    // 關閉
                }
            }
            selectedKeys.clear() ;    // 清楚所有的key
        }
        
    }
}

服務器完成以後可使用Telnet命令完成,這樣就完成了一個一部的操做服務器。

相關文章
相關標籤/搜索