JDK10都發(fā)布了攻询,nio你了解多少?

前言

只有光頭才能變強

回顧前面:

本來我預(yù)想是先來回顧一下傳統(tǒng)的IO模式的州弟,將傳統(tǒng)的IO模式的相關(guān)類理清楚(因為IO的類很多)钧栖。

但是低零,發(fā)現(xiàn)在整理的過程已經(jīng)有很多優(yōu)秀的文章了,而我自己來整理的話可能達不到他們的水平拯杠。并且傳統(tǒng)的IO估計大家都會用掏婶,而NIO就不一定了

下面我就貼幾張我認為整理比較優(yōu)秀的思維導(dǎo)圖(下面會給出圖片來源地址潭陪,大家可前往閱讀):

按操作方式分類結(jié)構(gòu)圖:

image

字節(jié)流的輸入和輸出對照圖:

GVkFLAA.jpg

字符流的輸入和輸出對照圖:

GxGq3GL.jpg

按操作對象分類結(jié)構(gòu)圖:

image

上述圖片原文地址雄妥,知乎作者@小明

還有閱讀傳統(tǒng)IO源碼的優(yōu)秀文章:

相信大家看完上面兩個給出的鏈接+理解了包裝模式就是這么簡單啦,傳統(tǒng)的IO應(yīng)該就沒什么事啦~~

而NIO對于我來說可以說是挺陌生的依溯,在當初學(xué)的時候是接觸過的茎芭。但是一直沒有用它,所以停留認知:nio是jdk1.4開始有的誓沸,比傳統(tǒng)IO高級梅桩。

相信很多初學(xué)者都跟我一樣,對NIO是不太了解的拜隧。而我們現(xiàn)在jdk10都已經(jīng)發(fā)布了宿百,jdk1.4的nio都不知道,這有點說不過去了洪添。

所以我花了幾天去了解NIO的核心知識點垦页,期間看了《Java 編程思想》和《瘋狂Java 講義》的nio模塊。但是干奢,會發(fā)現(xiàn)看完了之后還是很痊焊,不知道NIO這是干嘛用的,而網(wǎng)上的資料與書上的知識點沒有很好地對應(yīng)忿峻。

  • 網(wǎng)上的資料很多都以IO的五種模型為基礎(chǔ)來講解NIO薄啥,而IO這五種模型其中又涉及到了很多概念:同步/異步/阻塞/非阻塞/多路復(fù)用而不同的人又有不同的理解方式逛尚。
  • 還有涉及到了unix的select/epoll/poll/pselect垄惧,fd這些關(guān)鍵字,沒有相關(guān)基礎(chǔ)的人看起來簡直是天書
  • 這就導(dǎo)致了在初學(xué)時認為nio遠不可及

我在找資料的過程中也收藏了好多講解NIO的資料绰寞,這篇文章就是以初學(xué)的角度來理解NIO到逊。也算是我這兩天看NIO的一個總結(jié)吧。

  • 希望大家可以看了之后知道什么是NIO滤钱,NIO的核心知識點是什么觉壶,會使用NIO~

那么接下來就開始吧,如果文章有錯誤的地方請大家多多包涵件缸,不吝在評論區(qū)指正哦~

聲明:本文使用JDK1.8

一铜靶、NIO的概述

JDK 1.4中的java.nio.*包中引入新的Java I/O庫,其目的是提高速度停团。實際上旷坦,“舊”的I/O包已經(jīng)使用NIO重新實現(xiàn)過掏熬,即使我們不顯式的使用NIO編程,也能從中受益秒梅。

  • nio翻譯成 no-blocking io 或者 new io 都無所謂啦旗芬,都說得通~

在《Java編程思想》讀到“即使我們不顯式的使用NIO編程,也能從中受益”的時候捆蜀,我是挺在意的疮丛,所以:我們測試一下使用NIO復(fù)制文件和傳統(tǒng)IO復(fù)制文件的性能:


import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;

public class SimpleFileTransferTest {

    private long transferFile(File source, File des) throws IOException {
        long startTime = System.currentTimeMillis();

        if (!des.exists())
            des.createNewFile();

        BufferedInputStream bis = new BufferedInputStream(new FileInputStream(source));
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(des));

        //將數(shù)據(jù)源讀到的內(nèi)容寫入目的地--使用數(shù)組
        byte[] bytes = new byte[1024 * 1024];
        int len;
        while ((len = bis.read(bytes)) != -1) {
            bos.write(bytes, 0, len);
        }

        long endTime = System.currentTimeMillis();
        return endTime - startTime;
    }

    private long transferFileWithNIO(File source, File des) throws IOException {
        long startTime = System.currentTimeMillis();

        if (!des.exists())
            des.createNewFile();

        RandomAccessFile read = new RandomAccessFile(source, "rw");
        RandomAccessFile write = new RandomAccessFile(des, "rw");

        FileChannel readChannel = read.getChannel();
        FileChannel writeChannel = write.getChannel();

        ByteBuffer byteBuffer = ByteBuffer.allocate(1024 * 1024);//1M緩沖區(qū)

        while (readChannel.read(byteBuffer) > 0) {
            byteBuffer.flip();
            writeChannel.write(byteBuffer);
            byteBuffer.clear();
        }

        writeChannel.close();
        readChannel.close();
        long endTime = System.currentTimeMillis();
        return endTime - startTime;
    }

    public static void main(String[] args) throws IOException {
        SimpleFileTransferTest simpleFileTransferTest = new SimpleFileTransferTest();
        File sourse = new File("F:\\電影\\[電影天堂www.dygod.cn]猜火車-cd1.rmvb");
        File des = new File("X:\\Users\\ozc\\Desktop\\io.avi");
        File nio = new File("X:\\Users\\ozc\\Desktop\\nio.avi");

        long time = simpleFileTransferTest.transferFile(sourse, des);
        System.out.println(time + ":普通字節(jié)流時間");

        long timeNio = simpleFileTransferTest.transferFileWithNIO(sourse, nio);
        System.out.println(timeNio + ":NIO時間");

    }

}

我分別測試了文件大小為13M,40M辆它,200M的:

D3rUfWn.png
oKeyMcp.png
tFLM6cu.png

1.1為什么要使用NIO

可以看到使用過NIO重新實現(xiàn)過的傳統(tǒng)IO根本不虛誊薄,在大文件下效果還比NIO要好(當然了,個人幾次的測試锰茉,或許不是很準)

  • 而NIO要有一定的學(xué)習(xí)成本呢蔫,也沒有傳統(tǒng)IO那么好理解。

那這意味著我們可以不使用/學(xué)習(xí)NIO了嗎飒筑?

答案是否定的片吊,IO操作往往在兩個場景下會用到:

  • 文件IO
  • 網(wǎng)絡(luò)IO

NIO的魅力:在網(wǎng)絡(luò)中使用IO就可以體現(xiàn)出來了

  • 后面會說到網(wǎng)絡(luò)中使用NIO协屡,不急哈~

二俏脊、NIO快速入門

首先我們來看看IO和NIO的區(qū)別

image
  • 可簡單認為:IO是面向流的處理,NIO是面向塊(緩沖區(qū))的處理
    • 面向流的I/O 系統(tǒng)一次一個字節(jié)地處理數(shù)據(jù)肤晓。
    • 一個面向塊(緩沖區(qū))的I/O系統(tǒng)以塊的形式處理數(shù)據(jù)爷贫。

NIO主要有三個核心部分組成

  • buffer緩沖區(qū)
  • Channel管道
  • Selector選擇器

2.1buffer緩沖區(qū)和Channel管道

在NIO中并不是以流的方式來處理數(shù)據(jù)的,而是以buffer緩沖區(qū)和Channel管道配合使用來處理數(shù)據(jù)补憾。

簡單理解一下:

  • Channel管道比作成鐵路漫萄,buffer緩沖區(qū)比作成火車(運載著貨物)

而我們的NIO就是通過Channel管道運輸著存儲數(shù)據(jù)的Buffer緩沖區(qū)的來實現(xiàn)數(shù)據(jù)的處理

  • 要時刻記子嘈贰:Channel不與數(shù)據(jù)打交道卷胯,它只負責運輸數(shù)據(jù)。與數(shù)據(jù)打交道的是Buffer緩沖區(qū)
    • Channel-->運輸
    • Buffer-->數(shù)據(jù)

相對于傳統(tǒng)IO而言威酒,流是單向的。對于NIO而言挺峡,有了Channel管道這個概念葵孤,我們的讀寫都是雙向的(鐵路上的火車能從廣州去北京、自然就能從北京返還到廣州)橱赠!

2.1.1buffer緩沖區(qū)核心要點

我們來看看Buffer緩沖區(qū)有什么值得我們注意的地方尤仍。

Buffer是緩沖區(qū)的抽象類:

fcrYLrh.png

其中ByteBuffer是用得最多的實現(xiàn)類(在管道中讀寫字節(jié)數(shù)據(jù))。

Pmu4TRr.png

拿到一個緩沖區(qū)我們往往會做什么狭姨?很簡單宰啦,就是讀取緩沖區(qū)的數(shù)據(jù)/寫數(shù)據(jù)到緩沖區(qū)中苏遥。所以,緩沖區(qū)的核心方法就是:

  • put()
  • get()
cRZirXk.png
0TJJCEp.png

Buffer類維護了4個核心變量屬性來提供關(guān)于其所包含的數(shù)組的信息赡模。它們是:

  • 容量Capacity
    • 緩沖區(qū)能夠容納的數(shù)據(jù)元素的最大數(shù)量田炭。容量在緩沖區(qū)創(chuàng)建時被設(shè)定,并且永遠不能被改變漓柑。(不能被改變的原因也很簡單教硫,底層是數(shù)組嘛)
  • 上界Limit
    • 緩沖區(qū)里的數(shù)據(jù)的總數(shù),代表了當前緩沖區(qū)中一共有多少數(shù)據(jù)辆布。
  • 位置Position
    • 下一個要被讀或?qū)懙脑氐奈恢?/strong>瞬矩。Position會自動由相應(yīng)的 get( )put( )函數(shù)更新。
  • 標記Mark
    • 一個備忘位置锋玲。用于記錄上一次讀寫的位置景用。
image

2.1.2buffer代碼演示

首先展示一下是如何創(chuàng)建緩沖區(qū)的,核心變量的值是怎么變化的惭蹂。


    public static void main(String[] args) {

        // 創(chuàng)建一個緩沖區(qū)
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

        // 看一下初始時4個核心變量的值
        System.out.println("初始時-->limit--->"+byteBuffer.limit());
        System.out.println("初始時-->position--->"+byteBuffer.position());
        System.out.println("初始時-->capacity--->"+byteBuffer.capacity());
        System.out.println("初始時-->mark--->" + byteBuffer.mark());

        System.out.println("--------------------------------------");

        // 添加一些數(shù)據(jù)到緩沖區(qū)中
        String s = "Java3y";
        byteBuffer.put(s.getBytes());

        // 看一下初始時4個核心變量的值
        System.out.println("put完之后-->limit--->"+byteBuffer.limit());
        System.out.println("put完之后-->position--->"+byteBuffer.position());
        System.out.println("put完之后-->capacity--->"+byteBuffer.capacity());
        System.out.println("put完之后-->mark--->" + byteBuffer.mark());
    }

運行結(jié)果:

1ZRbWTf.png

現(xiàn)在我想要從緩存區(qū)拿數(shù)據(jù)丛肢,怎么拿呀?剿干?NIO給了我們一個flip()方法蜂怎。這個方法可以改動position和limit的位置

還是上面的代碼置尔,我們flip()一下后杠步,再看看4個核心屬性的值會發(fā)生什么變化:

image

很明顯的是:

  • limit變成了position的位置了
  • 而position變成了0

看到這里的同學(xué)可能就會想到了:當調(diào)用完filp()時:limit是限制讀到哪里,而position是從哪里讀

一般我們稱filp()“切換成讀模式”

  • 每當要從緩存區(qū)的時候讀取數(shù)據(jù)時榜轿,就調(diào)用filp()“切換成讀模式”幽歼。
image

切換成讀模式之后,我們就可以讀取緩沖區(qū)的數(shù)據(jù)了:


        // 創(chuàng)建一個limit()大小的字節(jié)數(shù)組(因為就只有l(wèi)imit這么多個數(shù)據(jù)可讀)
        byte[] bytes = new byte[byteBuffer.limit()];

        // 將讀取的數(shù)據(jù)裝進我們的字節(jié)數(shù)組中
        byteBuffer.get(bytes);

        // 輸出數(shù)據(jù)
        System.out.println(new String(bytes, 0, bytes.length));
image

隨后輸出一下核心變量的值看看:

image

讀完我們還想寫數(shù)據(jù)到緩沖區(qū)谬盐,那就使用clear()函數(shù)甸私,這個函數(shù)會“清空”緩沖區(qū):

  • 數(shù)據(jù)沒有真正被清空,只是被遺忘掉了
image

2.1.3FileChannel通道核心要點

8qQ8iH0.png

Channel通道只負責傳輸數(shù)據(jù)飞傀、不直接操作數(shù)據(jù)的皇型。操作數(shù)據(jù)都是通過Buffer緩沖區(qū)來進行操作!


        // 1\. 通過本地IO的方式來獲取通道
        FileInputStream fileInputStream = new FileInputStream("F:\\3yBlog\\JavaEE常用框架\\Elasticsearch就是這么簡單.md");

        // 得到文件的輸入通道
        FileChannel inchannel = fileInputStream.getChannel();

        // 2\. jdk1.7后通過靜態(tài)方法.open()獲取通道
        FileChannel.open(Paths.get("F:\\3yBlog\\JavaEE常用框架\\Elasticsearch就是這么簡單2.md"), StandardOpenOption.WRITE);

使用FileChannel配合緩沖區(qū)實現(xiàn)文件復(fù)制的功能:

kQujJ3b.png

使用內(nèi)存映射文件的方式實現(xiàn)文件復(fù)制的功能(直接操作緩沖區(qū)):

aDR7cWl.png

通道之間通過transfer()實現(xiàn)數(shù)據(jù)的傳輸(直接操作緩沖區(qū)):

3L5y8mZ.png

2.1.4直接與非直接緩沖區(qū)

  • 非直接緩沖區(qū)是需要經(jīng)過一個:copy的階段的(從內(nèi)核空間copy到用戶空間)
  • 直接緩沖區(qū)不需要經(jīng)過copy階段砸烦,也可以理解成--->內(nèi)存映射文件弃鸦,(上面的圖片也有過例子)。
image
4jtMYGh.png

使用直接緩沖區(qū)有兩種方式:

  • 緩沖區(qū)創(chuàng)建的時候分配的是直接緩沖區(qū)
  • 在FileChannel上調(diào)用map()方法幢痘,將文件直接映射到內(nèi)存中創(chuàng)建
image

2.1.5scatter和gather唬格、字符集

這個知識點我感覺用得挺少的,不過很多教程都有說這個知識點,我也拿過來說說吧:

  • 分散讀取(scatter):將一個通道中的數(shù)據(jù)分散讀取到多個緩沖區(qū)中
  • 聚集寫入(gather):將多個緩沖區(qū)中的數(shù)據(jù)集中寫入到一個通道中
image
image

分散讀取

knSBEN3.png

聚集寫入

image

字符集(只要編碼格式和解碼格式一致购岗,就沒問題了)

pk6Mdmz.png

三汰聋、IO模型理解

文件的IO就告一段落了,我們來學(xué)習(xí)網(wǎng)絡(luò)中的IO~~為了更好地理解NIO喊积,**我們先來學(xué)習(xí)一下IO的模型**

根據(jù)UNIX網(wǎng)絡(luò)編程對I/O模型的分類烹困,在UNIX可以歸納成5種I/O模型

  • 阻塞I/O
  • 非阻塞I/O
  • I/O多路復(fù)用
  • 信號驅(qū)動I/O
  • 異步I/O

3.0學(xué)習(xí)I/O模型需要的基礎(chǔ)

3.0.1文件描述符

Linux 的內(nèi)核將所有外部設(shè)備都看做一個文件來操作,對一個文件的讀寫操作會調(diào)用內(nèi)核提供的系統(tǒng)命令(api)注服,返回一個file descriptor(fd韭邓,文件描述符)。而對一個socket的讀寫也會有響應(yīng)的描述符溶弟,稱為socket fd(socket文件描述符)女淑,描述符就是一個數(shù)字,指向內(nèi)核中的一個結(jié)構(gòu)體(文件路徑辜御,數(shù)據(jù)區(qū)等一些屬性)鸭你。

  • 所以說:在Linux下對文件的操作是利用文件描述符(file descriptor)來實現(xiàn)的

3.0.2用戶空間和內(nèi)核空間

為了保證用戶進程不能直接操作內(nèi)核(kernel)擒权,保證內(nèi)核的安全袱巨,操心系統(tǒng)將虛擬空間劃分為兩部分

  • 一部分為內(nèi)核空間
  • 一部分為用戶空間碳抄。

3.0.3I/O運行過程

我們來看看IO在系統(tǒng)中的運行是怎么樣的(我們以read為例)

image

可以發(fā)現(xiàn)的是:當應(yīng)用程序調(diào)用read方法時愉老,是需要等待的--->從內(nèi)核空間中找數(shù)據(jù),再將內(nèi)核空間的數(shù)據(jù)拷貝到用戶空間的剖效。

  • 這個等待是必要的過程嫉入!

下面只講解用得最多的3個I/0模型:

  • 阻塞I/O
  • 非阻塞I/O
  • I/O多路復(fù)用

3.1阻塞I/O模型

在進程(用戶)空間中調(diào)用recvfrom,其系統(tǒng)調(diào)用直到數(shù)據(jù)包到達且被復(fù)制到應(yīng)用進程的緩沖區(qū)中或者發(fā)生錯誤時才返回璧尸,在此期間一直等待咒林。

vdKFA3D.png

3.2非阻塞I/O模型

recvfrom從應(yīng)用層到內(nèi)核的時候,如果沒有數(shù)據(jù)就直接返回一個EWOULDBLOCK錯誤爷光,一般都對非阻塞I/O模型進行輪詢檢查這個狀態(tài)垫竞,看內(nèi)核是不是有數(shù)據(jù)到來。

image

3.3I/O復(fù)用模型

前面也已經(jīng)說了:在Linux下對文件的操作是利用文件描述符(file descriptor)來實現(xiàn)的蛀序。

在Linux下它是這樣子實現(xiàn)I/O復(fù)用模型的:

  • 調(diào)用select/poll/epoll/pselect其中一個函數(shù)欢瞪,傳入多個文件描述符,如果有一個文件描述符就緒哼拔,則返回引有,否則阻塞直到超時。

比如poll()函數(shù)是這樣子的:int poll(struct pollfd *fds,nfds_t nfds, int timeout);

其中 pollfd 結(jié)構(gòu)定義如下:


struct pollfd {
    int fd;         /* 文件描述符 */
    short events;         /* 等待的事件 */
    short revents;       /* 實際發(fā)生了的事件 */
};
image
image
  • (1)當用戶進程調(diào)用了select倦逐,那么整個進程會被block;
  • (2)而同時,kernel會“監(jiān)視”所有select負責的socket檬姥;
  • (3)當任何一個socket中的數(shù)據(jù)準備好了曾我,select就會返回;
  • (4)這個時候用戶進程再調(diào)用read操作健民,將數(shù)據(jù)從kernel拷貝到用戶進程(空間)抒巢。
  • 所以,I/O 多路復(fù)用的特點是通過一種機制一個進程能同時等待多個文件描述符秉犹,而這些文件描述符其中的任意一個進入讀就緒狀態(tài)蛉谜,select()函數(shù)就可以返回

select/epoll的優(yōu)勢并不是對于單個連接能處理得更快崇堵,而是在于能處理更多的連接型诚。

3.4I/O模型總結(jié)

正經(jīng)的描述都在上面給出了,不知道大家理解了沒有鸳劳。下面我舉幾個例子總結(jié)一下這三種模型:

阻塞I/O:

  • Java3y跟女朋友去買喜茶狰贯,排了很久的隊終于可以點飲料了。我要綠研赏廓,謝謝涵紊。可是喜茶不是點了單就能立即拿幔摸,于是我在喜茶門口等了一小時才拿到綠研摸柄。
    • 在門口干等一小時

非阻塞I/O:

  • Java3y跟女朋友去買一點點,排了很久的隊終于可以點飲料了既忆。我要波霸奶茶驱负,謝謝∧蚱叮可是一點點不是點了單就能立即拿电媳,同時服務(wù)員告訴我:你大概要等半小時哦。你們先去逛逛吧~于是Java3y跟女朋友去玩了幾把斗地主庆亡,感覺時間差不多了匾乓。于是又去一點點問:請問到我了嗎?我的單號是xxx又谋。服務(wù)員告訴Java3y:還沒到呢拼缝,現(xiàn)在的單號是XXX,你還要等一會彰亥,可以去附近耍耍咧七。問了好幾次后,終于拿到我的波霸奶茶了任斋。
    • 去逛了下街继阻、斗了下地主,時不時問問到我了沒有

I/O復(fù)用模型:

  • Java3y跟女朋友去麥當勞吃漢堡包,現(xiàn)在就厲害了可以使用微信小程序點餐了瘟檩。于是跟女朋友找了個地方坐下就用小程序點餐了抹缕。點餐了之后玩玩斗地主、聊聊天什么的墨辛。時不時聽到廣播在復(fù)述XXX請取餐卓研,反正我的單號還沒到,就繼續(xù)玩唄睹簇。~~等聽到廣播的時候再取餐就是了奏赘。時間過得挺快的,此時傳來:Java3y請過來取餐太惠。于是我就能拿到我的麥辣雞翅漢堡了磨淌。
    • 聽廣播取餐,廣播不是為我一個人服務(wù)垛叨。廣播喊到我了伦糯,我過去取就Ok了。

四嗽元、使用NIO完成網(wǎng)絡(luò)通信

4.1NIO基礎(chǔ)繼續(xù)講解

回到我們最開始的圖:

image

NIO被叫為 no-blocking io敛纲,其實是在網(wǎng)絡(luò)這個層次中理解的,對于FileChannel來說一樣是阻塞剂癌。

我們前面也僅僅講解了FileChannel淤翔,對于我們網(wǎng)絡(luò)通信是還有幾個Channel的~

3N5JPvE.png

所以說:我們通常使用NIO是在網(wǎng)絡(luò)中使用的,網(wǎng)上大部分討論NIO都是在網(wǎng)絡(luò)通信的基礎(chǔ)之上的佩谷!說NIO是非阻塞的NIO也是網(wǎng)絡(luò)中體現(xiàn)的旁壮!

從上面的圖我們可以發(fā)現(xiàn)還有一個Selector選擇器這么一個東東。從一開始我們就說過了谐檀,nio的核心要素有:

  • Buffer緩沖區(qū)
  • Channel通道
  • Selector選擇器

我們在網(wǎng)絡(luò)中使用NIO往往是I/O模型的多路復(fù)用模型抡谐!

  • Selector選擇器就可以比喻成麥當勞的廣播
  • 一個線程能夠管理多個Channel的狀態(tài)
image

4.2NIO阻塞形態(tài)

為了更好地理解桐猬,我們先來寫一下NIO在網(wǎng)絡(luò)中是阻塞的狀態(tài)代碼麦撵,隨后看看非阻塞是怎么寫的就更容易理解了。

  • 是阻塞的就沒有Selector選擇器了溃肪,就直接使用Channel和Buffer就完事了免胃。

客戶端:


public class BlockClient {

    public static void main(String[] args) throws IOException {

        // 1\. 獲取通道
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 6666));

        // 2\. 發(fā)送一張圖片給服務(wù)端吧
        FileChannel fileChannel = FileChannel.open(Paths.get("X:\\Users\\ozc\\Desktop\\新建文件夾\\1.png"), StandardOpenOption.READ);

        // 3.要使用NIO,有了Channel惫撰,就必然要有Buffer羔沙,Buffer是與數(shù)據(jù)打交道的呢
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        // 4.讀取本地文件(圖片),發(fā)送到服務(wù)器
        while (fileChannel.read(buffer) != -1) {

            // 在讀之前都要切換成讀模式
            buffer.flip();

            socketChannel.write(buffer);

            // 讀完切換成寫模式厨钻,能讓管道繼續(xù)讀取文件的數(shù)據(jù)
            buffer.clear();
        }

        // 5\. 關(guān)閉流
        fileChannel.close();
        socketChannel.close();
    }
}

服務(wù)端:


public class BlockServer {

    public static void main(String[] args) throws IOException {

        // 1.獲取通道
        ServerSocketChannel server = ServerSocketChannel.open();

        // 2.得到文件通道扼雏,將客戶端傳遞過來的圖片寫到本地項目下(寫模式坚嗜、沒有則創(chuàng)建)
        FileChannel outChannel = FileChannel.open(Paths.get("2.png"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);

        // 3\. 綁定鏈接
        server.bind(new InetSocketAddress(6666));

        // 4\. 獲取客戶端的連接(阻塞的)
        SocketChannel client = server.accept();

        // 5\. 要使用NIO,有了Channel呢蛤,就必然要有Buffer惶傻,Buffer是與數(shù)據(jù)打交道的呢
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        // 6.將客戶端傳遞過來的圖片保存在本地中
        while (client.read(buffer) != -1) {

            // 在讀之前都要切換成讀模式
            buffer.flip();

            outChannel.write(buffer);

            // 讀完切換成寫模式棍郎,能讓管道繼續(xù)讀取文件的數(shù)據(jù)
            buffer.clear();

        }

        // 7.關(guān)閉通道
        outChannel.close();
        client.close();
        server.close();
    }
}

結(jié)果就可以將客戶端傳遞過來的圖片保存在本地了:

lHcYwaD.png

此時服務(wù)端保存完圖片想要告訴客戶端已經(jīng)收到圖片啦:

DGavnUW.png

客戶端接收服務(wù)端帶過來的數(shù)據(jù):

QMBXhyv.png

如果僅僅是上面的代碼是不行的其障!這個程序會阻塞起來!

  • 因為服務(wù)端不知道客戶端還有沒有數(shù)據(jù)要發(fā)過來(與剛開始不一樣涂佃,客戶端發(fā)完數(shù)據(jù)就將流關(guān)閉了励翼,服務(wù)端可以知道客戶端沒數(shù)據(jù)發(fā)過來了),導(dǎo)致服務(wù)端一直在讀取客戶端發(fā)過來的數(shù)據(jù)辜荠。
  • 進而導(dǎo)致了阻塞汽抚!

于是客戶端在寫完數(shù)據(jù)給服務(wù)端時,顯式告訴服務(wù)端已經(jīng)發(fā)完數(shù)據(jù)了伯病!

RtQQMHo.png

4.3NIO非阻塞形態(tài)

如果使用非阻塞模式的話造烁,那么我們就可以不顯式告訴服務(wù)器已經(jīng)發(fā)完數(shù)據(jù)了。我們下面來看看怎么寫:

客戶端

public class NoBlockClient {

    public static void main(String[] args) throws IOException {

        // 1\. 獲取通道
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 6666));

        // 1.1切換成非阻塞模式
        socketChannel.configureBlocking(false);

        // 2\. 發(fā)送一張圖片給服務(wù)端吧
        FileChannel fileChannel = FileChannel.open(Paths.get("X:\\Users\\ozc\\Desktop\\新建文件夾\\1.png"), StandardOpenOption.READ);

        // 3.要使用NIO午笛,有了Channel惭蟋,就必然要有Buffer,Buffer是與數(shù)據(jù)打交道的呢
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        // 4.讀取本地文件(圖片)药磺,發(fā)送到服務(wù)器
        while (fileChannel.read(buffer) != -1) {

            // 在讀之前都要切換成讀模式
            buffer.flip();

            socketChannel.write(buffer);

            // 讀完切換成寫模式告组,能讓管道繼續(xù)讀取文件的數(shù)據(jù)
            buffer.clear();
        }

        // 5\. 關(guān)閉流
        fileChannel.close();
        socketChannel.close();
    }
}

服務(wù)端


public class NoBlockServer {

    public static void main(String[] args) throws IOException {

        // 1.獲取通道
        ServerSocketChannel server = ServerSocketChannel.open();

        // 2.切換成非阻塞模式
        server.configureBlocking(false);

        // 3\. 綁定連接
        server.bind(new InetSocketAddress(6666));

        // 4\. 獲取選擇器
        Selector selector = Selector.open();

        // 4.1將通道注冊到選擇器上,指定接收“監(jiān)聽通道”事件
        server.register(selector, SelectionKey.OP_ACCEPT);

        // 5\. 輪訓(xùn)地獲取選擇器上已“就緒”的事件--->只要select()>0癌佩,說明已就緒
        while (selector.select() > 0) {
            // 6\. 獲取當前選擇器所有注冊的“選擇鍵”(已就緒的監(jiān)聽事件)
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();

            // 7\. 獲取已“就緒”的事件木缝,(不同的事件做不同的事)
            while (iterator.hasNext()) {

                SelectionKey selectionKey = iterator.next();

                // 接收事件就緒
                if (selectionKey.isAcceptable()) {

                    // 8\. 獲取客戶端的鏈接
                    SocketChannel client = server.accept();

                    // 8.1 切換成非阻塞狀態(tài)
                    client.configureBlocking(false);

                    // 8.2 注冊到選擇器上-->拿到客戶端的連接為了讀取通道的數(shù)據(jù)(監(jiān)聽讀就緒事件)
                    client.register(selector, SelectionKey.OP_READ);

                } else if (selectionKey.isReadable()) { // 讀事件就緒

                    // 9\. 獲取當前選擇器讀就緒狀態(tài)的通道
                    SocketChannel client = (SocketChannel) selectionKey.channel();

                    // 9.1讀取數(shù)據(jù)
                    ByteBuffer buffer = ByteBuffer.allocate(1024);

                    // 9.2得到文件通道,將客戶端傳遞過來的圖片寫到本地項目下(寫模式围辙、沒有則創(chuàng)建)
                    FileChannel outChannel = FileChannel.open(Paths.get("2.png"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);

                    while (client.read(buffer) > 0) {
                        // 在讀之前都要切換成讀模式
                        buffer.flip();

                        outChannel.write(buffer);

                        // 讀完切換成寫模式我碟,能讓管道繼續(xù)讀取文件的數(shù)據(jù)
                        buffer.clear();
                    }
                }
                // 10\. 取消選擇鍵(已經(jīng)處理過的事件,就應(yīng)該取消掉了)
                iterator.remove();
            }
        }

    }
}

還是剛才的需求:服務(wù)端保存了圖片以后姚建,告訴客戶端已經(jīng)收到圖片了矫俺。

在服務(wù)端上只要在后面寫些數(shù)據(jù)給客戶端就好了:

image

在客戶端上要想獲取得到服務(wù)端的數(shù)據(jù)澈魄,也需要注冊在register上(監(jiān)聽讀事件)砍聊!


public class NoBlockClient2 {

    public static void main(String[] args) throws IOException {

        // 1\. 獲取通道
        SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1", 6666));

        // 1.1切換成非阻塞模式
        socketChannel.configureBlocking(false);

        // 1.2獲取選擇器
        Selector selector = Selector.open();

        // 1.3將通道注冊到選擇器中陵像,獲取服務(wù)端返回的數(shù)據(jù)
        socketChannel.register(selector, SelectionKey.OP_READ);

        // 2\. 發(fā)送一張圖片給服務(wù)端吧
        FileChannel fileChannel = FileChannel.open(Paths.get("X:\\Users\\ozc\\Desktop\\新建文件夾\\1.png"), StandardOpenOption.READ);

        // 3.要使用NIO芳来,有了Channel杏瞻,就必然要有Buffer平道,Buffer是與數(shù)據(jù)打交道的呢
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        // 4.讀取本地文件(圖片)辅髓,發(fā)送到服務(wù)器
        while (fileChannel.read(buffer) != -1) {

            // 在讀之前都要切換成讀模式
            buffer.flip();

            socketChannel.write(buffer);

            // 讀完切換成寫模式浪感,能讓管道繼續(xù)讀取文件的數(shù)據(jù)
            buffer.clear();
        }

        // 5\. 輪訓(xùn)地獲取選擇器上已“就緒”的事件--->只要select()>0缎罢,說明已就緒
        while (selector.select() > 0) {
            // 6\. 獲取當前選擇器所有注冊的“選擇鍵”(已就緒的監(jiān)聽事件)
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();

            // 7\. 獲取已“就緒”的事件伊群,(不同的事件做不同的事)
            while (iterator.hasNext()) {

                SelectionKey selectionKey = iterator.next();

                // 8\. 讀事件就緒
                if (selectionKey.isReadable()) {

                    // 8.1得到對應(yīng)的通道
                    SocketChannel channel = (SocketChannel) selectionKey.channel();

                    ByteBuffer responseBuffer = ByteBuffer.allocate(1024);

                    // 9\. 知道服務(wù)端要返回響應(yīng)的數(shù)據(jù)給客戶端考杉,客戶端在這里接收
                    int readBytes = channel.read(responseBuffer);

                    if (readBytes > 0) {
                        // 切換讀模式
                        responseBuffer.flip();
                        System.out.println(new String(responseBuffer.array(), 0, readBytes));
                    }
                }

                // 10\. 取消選擇鍵(已經(jīng)處理過的事件,就應(yīng)該取消掉了)
                iterator.remove();
            }
        }
    }

}

測試結(jié)果:

image

下面就簡單總結(jié)一下使用NIO時的要點:

  • 將Socket通道注冊到Selector中舰始,監(jiān)聽感興趣的事件
  • 當感興趣的時間就緒時崇棠,則會進去我們處理的方法進行處理
  • 每處理完一次就緒事件,刪除該選擇鍵(因為我們已經(jīng)處理完了)

4.4管道和DataGramChannel

這里我就不再講述了丸卷,最難的TCP都講了枕稀,UDP就很簡單了。

UDP:

image
image

管道:

sOVDdJg.png
5nWtyKt.png

五谜嫉、總結(jié)

總的來說NIO也是一個比較重要的知識點萎坷,因為它是學(xué)習(xí)netty的基礎(chǔ)~

想以一篇來完全講解NIO顯然是不可能的啦,想要更加深入了解NIO可以往下面的鏈接繼續(xù)學(xué)習(xí)~

參考資料:

如果文章有錯的地方歡迎指正,大家互相交流比原。習(xí)慣在微信看技術(shù)文章插佛,想要獲取更多的Java資源的同學(xué),可以關(guān)注微信公眾號:Java3y春寿。為了大家方便朗涩,剛新建了一下qq群:742919422,大家也可以去交流交流绑改。謝謝支持了谢床!希望能多介紹給其他有需要的朋友

文章的目錄導(dǎo)航

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市厘线,隨后出現(xiàn)的幾起案子识腿,更是在濱河造成了極大的恐慌,老刑警劉巖造壮,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件渡讼,死亡現(xiàn)場離奇詭異,居然都是意外死亡耳璧,警方通過查閱死者的電腦和手機成箫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來旨枯,“玉大人蹬昌,你說我怎么就攤上這事∨矢簦” “怎么了皂贩?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵栖榨,是天一觀的道長。 經(jīng)常有香客問我明刷,道長婴栽,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任辈末,我火速辦了婚禮愚争,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘本冲。我一直安慰自己准脂,他們只是感情好,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布檬洞。 她就那樣靜靜地躺著,像睡著了一般沟饥。 火紅的嫁衣襯著肌膚如雪添怔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天贤旷,我揣著相機與錄音广料,去河邊找鬼。 笑死幼驶,一個胖子當著我的面吹牛艾杏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播盅藻,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼购桑,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了氏淑?” 一聲冷哼從身側(cè)響起勃蜘,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎假残,沒想到半個月后缭贡,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡辉懒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年阳惹,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片眶俩。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡莹汤,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出仿便,到底是詐尸還是另有隱情体啰,我是刑警寧澤攒巍,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站荒勇,受9級特大地震影響柒莉,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜沽翔,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一兢孝、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧仅偎,春花似錦跨蟹、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至座咆,卻和暖如春痢艺,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背介陶。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工堤舒, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人哺呜。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓舌缤,卻偏偏與公主長得像,于是被迫代替她去往敵國和親某残。 傳聞我的和親對象是個殘疾皇子国撵,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345

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

  • 轉(zhuǎn)自 http://www.ibm.com/developerworks/cn/education/java/j-...
    抓兔子的貓閱讀 2,277評論 0 22
  • 原文鏈接 攻破JAVA NIO技術(shù)壁壘 現(xiàn)在使用NIO的場景越來越多,很多網(wǎng)上的技術(shù)框架或多或少的使用NIO技...
    陽光的技術(shù)小棧閱讀 499評論 2 3
  • 為你我編織著 一個又一個的謊言 掩飾自己的脆弱 抵擋你的自責憐憫 用手藝和文字編織著自己規(guī)劃的謊言 我贏了全世界卻...
    瀟筠閱讀 214評論 0 2
  • 如果說西安是一個開始驾锰,那么從我雙腳踏入這片土地的那一刻起卸留,我就瘋狂了······ 一直在思考大五的我該如何寫,因為...
    別一種痛癢閱讀 432評論 1 3