Java NIO基礎(chǔ)

NIO是jdk1.4版本以后發(fā)布的新特性,主要類位于包java.nio下黎棠,NIO主要由以下幾個(gè)部分組成:

  • Buffer
    緩沖區(qū)晋渺,通常用于普通類與通道之間交換數(shù)據(jù)。緩沖區(qū)提供了一個(gè)會(huì)合點(diǎn):通道既可提取放在緩沖區(qū)中的數(shù)據(jù)(寫)脓斩,也可向緩沖區(qū)存入數(shù)據(jù)供讀饶疚鳌(讀)。
  • Channel
    通道随静,和傳統(tǒng)IO類中的流概念有些相似八千,但I(xiàn)O流是單向的,通道則是雙向的燎猛,基于NIO的數(shù)據(jù)交換都從通道中經(jīng)過(guò)恋捆,可以將通道中的數(shù)據(jù)寫入緩沖區(qū),也可以讀取緩沖區(qū)的數(shù)據(jù)進(jìn)通道重绷。
  • Selector
    選擇器沸停,基于多路復(fù)用的IO模型,作用類似于多路復(fù)用器昭卓,允許通過(guò)一個(gè)線程處理多個(gè)注冊(cè)的Channel愤钾。下圖表示了單線程一個(gè)選擇器同時(shí)處理三個(gè)通道的情況:
process.png

Buffer

緩沖區(qū)是包在一個(gè)對(duì)象內(nèi)的基本數(shù)據(jù)元素?cái)?shù)組。Buffer類相比一個(gè)簡(jiǎn)單數(shù)組的優(yōu)點(diǎn)是它將關(guān)于數(shù)據(jù)的數(shù)據(jù)內(nèi)容和信息包含在一個(gè)單一的對(duì)象中候醒。Buffer類以及它專有的子類定義了一個(gè)用于處理數(shù)據(jù)緩沖區(qū)的API能颁。
緩沖區(qū)維護(hù)了四個(gè)屬性來(lái)操作內(nèi)部數(shù)據(jù):
1.Capacity
容量,緩沖區(qū)所能容納的最大數(shù)據(jù)字節(jié)值倒淫,一旦固定即不可改變伙菊。
2.Limit
上界,緩沖區(qū)中第一個(gè)不能被操作的位置,可等同于現(xiàn)存數(shù)據(jù)計(jì)數(shù)占业。
3.Position
下一個(gè)操作位绒怨,由put和get方法更新。
4.Mark
備忘位置谦疾,不太常用南蹂。
緩沖區(qū)有讀和寫兩種模式,一般通過(guò)filp()clear()來(lái)切換這兩種操作模式念恍。

public abstract class Buffer {
    
    public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }
    
    public final Buffer clear() {
        position = 0;
        limit = capacity;
        mark = -1;
        return this;
    }
}    

position和limit的含義取決于Buffer處在讀模式還是寫模式:

modes.png

Channel

Channel是一個(gè)通道六剥,類似于輸入輸出流的概念。區(qū)別在于流是單向的峰伙,一般在傳統(tǒng)IO模型中進(jìn)行數(shù)據(jù)讀寫需要輸入輸出流搭配使用疗疟,但是Channel則提供了雙向數(shù)據(jù)通信的機(jī)制。
Channel的繼承關(guān)系比Buffer要復(fù)雜瞳氓,其具體實(shí)現(xiàn)依賴于不同的操作系統(tǒng)策彤。大體上來(lái)說(shuō),IO通道可以分為兩大類:文件通道和socket通道匣摘,常用的Channel類有FileChannel,SocketChannel,ServerSocketChannelDatagramChannel店诗,適用于文件IO和網(wǎng)絡(luò)流IO。
常用的Channel類一般都實(shí)現(xiàn)了ReadableByteChannelWriteableByteChannel音榜,因此他們具有雙向讀寫能力:

public class NIOTest {

    public static void main(String[] args) throws IOException {
        NIOTest nt = new NIOTest();
        
        ReadableByteChannel src = Channels.newChannel(System.in);
        WritableByteChannel dest = Channels.newChannel(System.out);
        nt.channelCopy(src, dest);
        src.close();
        dest.close();
    }
    
    private void channelCopy(ReadableByteChannel src, WritableByteChannel dest) throws IOException {
         ByteBuffer buffer = ByteBuffer.allocate(1024);
         while(src.read(buffer) != -1){
             buffer.flip();
             while (buffer.hasRemaining()) {
                 dest.write(buffer);
             }
             buffer.clear();
         }
         
    }
}

通道內(nèi)容的復(fù)制即可通過(guò)這兩個(gè)類來(lái)實(shí)現(xiàn)庞瘸。

Selector

選擇器類管理著一個(gè)被注冊(cè)的通道集合的信息和它們的就緒狀態(tài)。通道是和選擇器一起被注冊(cè)的赠叼,并且使用選擇器來(lái)更新通道的就緒狀態(tài)擦囊。
SelectableChannel提供了實(shí)現(xiàn)通道的可選擇性所需要的公共方法,FileChannel并未繼承此抽象類嘴办,因此不具有可選擇性瞬场。SelectableChannel可以被注冊(cè)到Selector對(duì)象上,同時(shí)可以指定對(duì)那個(gè)選擇器而言涧郊,應(yīng)該關(guān)注何種IO操作泌类。
SelectionKey封裝了特定的通道與特定的選擇器的注冊(cè)關(guān)系,指示了該注冊(cè)關(guān)系所關(guān)心的通道操作底燎,以及通道已經(jīng)準(zhǔn)備好的操作刃榨。
以下通過(guò)簡(jiǎn)單的回聲服務(wù)器來(lái)實(shí)例NIO的基本編程模型:

  • 服務(wù)器端,接收客戶端發(fā)送的消息并發(fā)送回給客戶端
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

/**
 * Created by Administrator on 2017/7/19.
 * Intellij IDEA
 */
public class EchoServer {

    private Selector selector;
    private int port;
    private ByteBuffer buffer = ByteBuffer.allocate(1024);

    public EchoServer(int port) {
        this.port = port;
    }

    public static void main(String[] args) throws IOException {
        new EchoServer(8000).start();
    }

    private void start() throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        serverSocketChannel.configureBlocking(false);

        this.selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        //ServerHandler handler = new ServerHandler();
        while (true) {
            selector.select();

            Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
            while (keys.hasNext()) {
                SelectionKey key = keys.next();

                try {
                    if (key.isAcceptable())
                        handleAccept(key);
                    else if (key.isReadable())
                        handleRead(key);
                    else if (key.isWritable())
                        handleWrite(key);
                } catch (Exception e) {
                    keys.remove();
                    continue;
                }
                keys.remove();
            }
        }
    }

    private void handleWrite(SelectionKey key) throws IOException {
        buffer.clear();
        SocketChannel socketChannel = (SocketChannel) key.channel();
        String message = "[message from server]";
        buffer.put(message.getBytes());
        buffer.flip();//prepare for write
        socketChannel.write(buffer);//write to client channel
        System.out.println("Server send message to client"+message);
        socketChannel.register(selector, SelectionKey.OP_READ);
    }

    private void handleRead(SelectionKey key) throws IOException {
        SocketChannel socketChannel = (SocketChannel) key.channel();
        buffer.clear();//clear this buffer for next read event
        int length = socketChannel.read(buffer);
        if (length > 0){
            String message = new String(buffer.array(), 0, length);
            System.out.println("Server received message from client:" + message);
            socketChannel.register(selector, SelectionKey.OP_WRITE);
        }

    }

    private void handleAccept(SelectionKey key) throws IOException {
        //waiting for client to connect
        ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
        SocketChannel socketChannel = serverSocketChannel.accept();
        socketChannel.configureBlocking(false);
        socketChannel.register(selector, SelectionKey.OP_READ);
    }

}
  • 客戶端双仍,接收服務(wù)端發(fā)送的消息并發(fā)送消息給服務(wù)端
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

/**
 * Created by Administrator on 2017/7/19.
 * Intellij IDEA
 */
public class EchoClient {
    private int port;
    private Selector selector;
    private ByteBuffer buffer = ByteBuffer.allocate(1024);

    public static void main(String[] args) throws IOException {
        new EchoClient(8000).start();
    }

    public EchoClient(int port) {
        this.port = port;
    }

    public void start() throws IOException {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false);

        this.selector = Selector.open();
        socketChannel.register(selector, SelectionKey.OP_CONNECT);
        socketChannel.connect(new InetSocketAddress(8000));//connect to server.

        while (true){
            selector.select();
            Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
            while (keys.hasNext()) {
                SelectionKey key = keys.next();
                if (key.isConnectable())
                    handleConnect(key);
                else if (key.isReadable())
                    handleRead(key);
                else if (key.isWritable())
                    handleWrite(key);
                keys.remove();
            }
        }
    }

    private void handleWrite(SelectionKey key) throws IOException {
        buffer.clear();
        SocketChannel socketChannel = (SocketChannel) key.channel();
        String message = "#message from client#";
        buffer.put(message.getBytes());
        buffer.flip();
        socketChannel.write(buffer);
        System.out.println("Client send message to server:" + message);
        socketChannel.register(selector, SelectionKey.OP_READ);
    }

    private void handleRead(SelectionKey key) throws IOException {
        SocketChannel socketChannel = (SocketChannel) key.channel();
        buffer.clear();
        int length = socketChannel.read(buffer);//read data from channel
        if (length > 0) {
            String serverMessage = new String(buffer.array(), 0, length);
            System.out.println("Client received message from server::" + serverMessage);
            socketChannel.register(selector, SelectionKey.OP_WRITE);
        }

    }

    private void handleConnect(SelectionKey key) throws IOException {
        SocketChannel socketChannel = (SocketChannel) key.channel();
        if (socketChannel.isConnectionPending()) {
            socketChannel.finishConnect();
            buffer.clear();
            buffer.put("Message from client".getBytes());
            buffer.flip();
            socketChannel.write(buffer);
        }
        socketChannel.register(selector, SelectionKey.OP_READ);
    }
}

可見(jiàn)枢希,基于NIO的socket編程確實(shí)比傳統(tǒng)Blocking IO模型要復(fù)雜,但無(wú)疑效率更高朱沃。這個(gè)實(shí)例中苞轿,Server端僅開(kāi)啟了一個(gè)線程用于處理IO操作茅诱,由于Selector的引入使得IO操作不再需要頻繁切換線程上下文,該Selector不斷輪詢注冊(cè)在它上面的key搬卒,完成相應(yīng)的IO操作瑟俭。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市契邀,隨后出現(xiàn)的幾起案子摆寄,更是在濱河造成了極大的恐慌,老刑警劉巖坯门,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件微饥,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡古戴,警方通過(guò)查閱死者的電腦和手機(jī)欠橘,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)现恼,“玉大人肃续,你說(shuō)我怎么就攤上這事〔媾郏” “怎么了始锚?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)畦韭。 經(jīng)常有香客問(wèn)我疼蛾,道長(zhǎng)肛跌,這世上最難降的妖魔是什么艺配? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮衍慎,結(jié)果婚禮上转唉,老公的妹妹穿的比我還像新娘。我一直安慰自己稳捆,他們只是感情好赠法,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著乔夯,像睡著了一般砖织。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上末荐,一...
    開(kāi)封第一講書(shū)人閱讀 51,115評(píng)論 1 296
  • 那天侧纯,我揣著相機(jī)與錄音,去河邊找鬼甲脏。 笑死眶熬,一個(gè)胖子當(dāng)著我的面吹牛妹笆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播娜氏,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼拳缠,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了贸弥?” 一聲冷哼從身側(cè)響起窟坐,我...
    開(kāi)封第一講書(shū)人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎茂腥,沒(méi)想到半個(gè)月后狸涌,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡最岗,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年帕胆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片般渡。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡懒豹,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出驯用,到底是詐尸還是另有隱情脸秽,我是刑警寧澤,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布蝴乔,位于F島的核電站记餐,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏薇正。R本人自食惡果不足惜片酝,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望挖腰。 院中可真熱鬧雕沿,春花似錦、人聲如沸猴仑。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)辽俗。三九已至疾渣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間崖飘,已是汗流浹背榴捡。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留坐漏,地道東北人薄疚。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓碧信,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親街夭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子砰碴,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容