java_nio

原文

先來回顧一下傳統(tǒng)的IO模式的湖员,將傳統(tǒng)的IO模式的相關(guān)類理清楚(因為IO的類很多)。

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

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

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

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

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

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

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

https://zhuanlan.zhihu.com/p/28286559

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

https://blog.csdn.net/panweiwei1994/article/details/78046000

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

而NIO對于我來說可以說是挺陌生的,在當(dāng)初學(xué)的時候是接觸過的食拜。但是一直沒有用它鸵熟,所以停留認(rèn)知: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é)時認(rèn)為nio遠(yuǎn)不可及

我在找資料的過程中也收藏了好多講解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ù)制文件的性能:

importjava.io.*;

importjava.nio.ByteBuffer;

importjava.nio.channels.FileChannel;

publicclassSimpleFileTransferTest{

privatelongtransferFile(File source, File des)throwsIOException{

longstartTime = System.currentTimeMillis();

if(!des.exists())

des.createNewFile();

BufferedInputStream bis =newBufferedInputStream(newFileInputStream(source));

BufferedOutputStream bos =newBufferedOutputStream(newFileOutputStream(des));

//將數(shù)據(jù)源讀到的內(nèi)容寫入目的地--使用數(shù)組

byte[] bytes =newbyte[1024*1024];

intlen;

while((len = bis.read(bytes)) != -1) {

bos.write(bytes,0, len);

}

longendTime = System.currentTimeMillis();

returnendTime - startTime;

}

privatelongtransferFileWithNIO(File source, File des)throwsIOException{

longstartTime = System.currentTimeMillis();

if(!des.exists())

des.createNewFile();

RandomAccessFile read =newRandomAccessFile(source,"rw");

RandomAccessFile write =newRandomAccessFile(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();

longendTime = System.currentTimeMillis();

returnendTime - startTime;

}

publicstaticvoidmain(String[] args)throwsIOException{

SimpleFileTransferTest simpleFileTransferTest =newSimpleFileTransferTest();

File sourse =newFile("F:\\電影\\[電影天堂www.dygod.cn]猜火車-cd1.rmvb");

File des =newFile("X:\\Users\\ozc\\Desktop\\io.avi");

File nio =newFile("X:\\Users\\ozc\\Desktop\\nio.avi");

longtime = simpleFileTransferTest.transferFile(sourse, des);

System.out.println(time +":普通字節(jié)流時間");

longtimeNio = simpleFileTransferTest.transferFileWithNIO(sourse, nio);

System.out.println(timeNio +":NIO時間");

}

}

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

1.1為什么要使用NIO

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

而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ū)別

可簡單認(rèn)為: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ù)打交道直晨,它只負(fù)責(zé)運輸數(shù)據(jù)搀军。與數(shù)據(jù)打交道的是Buffer緩沖區(qū)

Channel-->運輸

Buffer-->數(shù)據(jù)

相對于傳統(tǒng)IO而言,流是單向的勇皇。對于NIO而言罩句,有了Channel管道這個概念,我們的讀寫都是雙向的(鐵路上的火車能從廣州去北京敛摘、自然就能從北京返還到廣州)门烂!

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

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

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

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

拿到一個緩沖區(qū)我們往往會做什么诅福?很簡單,就是讀取緩沖區(qū)的數(shù)據(jù)/寫數(shù)據(jù)到緩沖區(qū)中拖叙。所以氓润,緩沖區(qū)的核心方法就是:

put()

get()

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

容量Capacity

緩沖區(qū)能夠容納的數(shù)據(jù)元素的最大數(shù)量薯鳍。容量在緩沖區(qū)創(chuàng)建時被設(shè)定咖气,并且永遠(yuǎn)不能被改變。(不能被改變的原因也很簡單挖滤,底層是數(shù)組嘛)

上界Limit

緩沖區(qū)里的數(shù)據(jù)的總數(shù)崩溪,代表了當(dāng)前緩沖區(qū)中一共有多少數(shù)據(jù)。

位置Position

下一個要被讀或?qū)懙脑氐奈恢?/b>斩松。Position會自動由相應(yīng)的?get( )和?put( )函數(shù)更新伶唯。

標(biāo)記Mark

一個備忘位置。用于記錄上一次讀寫的位置惧盹。

2.1.2buffer代碼演示

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

publicstaticvoidmain(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é)果:

現(xiàn)在我想要從緩存區(qū)拿數(shù)據(jù)钧椰,怎么拿呀粹断??NIO給了我們一個flip()方法嫡霞。這個方法可以改動position和limit的位置瓶埋!

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

很明顯的是:

limit變成了position的位置了

而position變成了0

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

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

每當(dāng)要從緩存區(qū)的時候讀取數(shù)據(jù)時,就調(diào)用filp()“切換成讀模式”端姚。

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

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

byte[] bytes =newbyte[byteBuffer.limit()];

// 將讀取的數(shù)據(jù)裝進(jìn)我們的字節(jié)數(shù)組中

byteBuffer.get(bytes);

// 輸出數(shù)據(jù)

System.out.println(newString(bytes,0, bytes.length));

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

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

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

2.1.3FileChannel通道核心要點

Channel通道只負(fù)責(zé)傳輸數(shù)據(jù)、不直接操作數(shù)據(jù)的橄仆。操作數(shù)據(jù)都是通過Buffer緩沖區(qū)來進(jìn)行操作剩膘!

// 1. 通過本地IO的方式來獲取通道

FileInputStream fileInputStream =newFileInputStream("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ù)制的功能:

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

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

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

非直接緩沖區(qū)是需要經(jīng)過一個:copy的階段的(從內(nèi)核空間copy到用戶空間)

直接緩沖區(qū)不需要經(jīng)過copy階段,也可以理解成--->內(nèi)存映射文件盆顾,(上面的圖片也有過例子)怠褐。

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

緩沖區(qū)創(chuàng)建的時候分配的是直接緩沖區(qū)

在FileChannel上調(diào)用map()方法,將文件直接映射到內(nèi)存中創(chuàng)建

2.1.5scatter和gather您宪、字符集

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

分散讀取(scatter):將一個通道中的數(shù)據(jù)分散讀取到多個緩沖區(qū)中

聚集寫入(gather):將多個緩沖區(qū)中的數(shù)據(jù)集中寫入到一個通道中

分散讀取

聚集寫入

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

三磷杏、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)核空間

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

一部分為內(nèi)核空間餐茵。

一部分為用戶空間

3.0.3I/O運行過程

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

可以發(fā)現(xiàn)的是:當(dāng)應(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模型

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

3.2非阻塞I/O模型

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

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)定義如下:

structpollfd{

intfd;/* 文件描述符 */

shortevents;/* 等待的事件 */

shortrevents;/* 實際發(fā)生了的事件 */

};

(1)當(dāng)用戶進(jìn)程調(diào)用了select,那么整個進(jìn)程會被block;

(2)而同時墙贱,kernel會“監(jiān)視”所有select負(fù)責(zé)的socket热芹;

(3)當(dāng)任何一個socket中的數(shù)據(jù)準(zhǔn)備好了,select就會返回惨撇;

(4)這個時候用戶進(jìn)程再調(diào)用read操作伊脓,將數(shù)據(jù)從kernel拷貝到用戶進(jìn)程(空間)。

所以串纺,I/O 多路復(fù)用的特點是通過一種機制一個進(jìn)程能同時等待多個文件描述符丽旅,而這些文件描述符其中的任意一個進(jìn)入讀就緒狀態(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跟女朋友去麥當(dāng)勞吃漢堡包矢腻,現(xiàn)在就厲害了可以使用微信小程序點餐了。于是跟女朋友找了個地方坐下就用小程序點餐了射赛。點餐了之后玩玩斗地主多柑、聊聊天什么的。時不時聽到廣播在復(fù)述XXX請取餐楣责,反正我的單號還沒到竣灌,就繼續(xù)玩唄。~~等聽到廣播的時候再取餐就是了秆麸。時間過得挺快的初嘹,此時傳來:Java3y請過來取餐。于是我就能拿到我的麥辣雞翅漢堡了沮趣。

聽廣播取餐屯烦,廣播不是為我一個人服務(wù)。廣播喊到我了房铭,我過去取就Ok了驻龟。

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

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

回到我們最開始的圖:

NIO被叫為?no-blocking io缸匪,其實是在網(wǎng)絡(luò)這個層次中理解的翁狐,對于FileChannel來說一樣是阻塞

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

所以說:我們通常使用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選擇器就可以比喻成麥當(dāng)勞的廣播钦睡。

一個線程能夠管理多個Channel的狀態(tài)

4.2NIO阻塞形態(tài)

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

是阻塞的就沒有Selector選擇器了,就直接使用Channel和Buffer就完事了秧秉。

客戶端:

publicclassBlockClient{

publicstaticvoidmain(String[] args)throwsIOException{

// 1. 獲取通道

SocketChannel socketChannel = SocketChannel.open(newInetSocketAddress("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ù)端:

publicclassBlockServer{

publicstaticvoidmain(String[] args)throwsIOException{

// 1.獲取通道

ServerSocketChannel server = ServerSocketChannel.open();

// 2.得到文件通道,將客戶端傳遞過來的圖片寫到本地項目下(寫模式啦撮、沒有則創(chuàng)建)

FileChannel outChannel = FileChannel.open(Paths.get("2.png"), StandardOpenOption.WRITE, StandardOpenOption.CREATE);

// 3. 綁定鏈接

server.bind(newInetSocketAddress(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é)果就可以將客戶端傳遞過來的圖片保存在本地了:

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

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

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

因為服務(wù)端不知道客戶端還有沒有數(shù)據(jù)要發(fā)過來(與剛開始不一樣锥涕,客戶端發(fā)完數(shù)據(jù)就將流關(guān)閉了,服務(wù)端可以知道客戶端沒數(shù)據(jù)發(fā)過來了)狭吼,導(dǎo)致服務(wù)端一直在讀取客戶端發(fā)過來的數(shù)據(jù)层坠。

進(jìn)而導(dǎo)致了阻塞!

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

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

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

客戶端

publicclassNoBlockClient{

publicstaticvoidmain(String[] args)throwsIOException{

// 1. 獲取通道

SocketChannel socketChannel = SocketChannel.open(newInetSocketAddress("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ù)端

publicclassNoBlockServer{

publicstaticvoidmain(String[] args)throwsIOException{

// 1.獲取通道

ServerSocketChannel server = ServerSocketChannel.open();

// 2.切換成非阻塞模式

server.configureBlocking(false);

// 3. 綁定連接

server.bind(newInetSocketAddress(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. 獲取當(dāng)前選擇器所有注冊的“選擇鍵”(已就緒的監(jiān)聽事件)

Iterator 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);

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

// 9. 獲取當(dāng)前選擇器讀就緒狀態(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ù)給客戶端就好了:

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

publicclassNoBlockClient2{

publicstaticvoidmain(String[] args)throwsIOException{

// 1. 獲取通道

SocketChannel socketChannel = SocketChannel.open(newInetSocketAddress("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. 獲取當(dāng)前選擇器所有注冊的“選擇鍵”(已就緒的監(jiān)聽事件)

Iterator 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ù)給客戶端袭灯,客戶端在這里接收

intreadBytes = channel.read(responseBuffer);

if(readBytes >0) {

// 切換讀模式

responseBuffer.flip();

System.out.println(newString(responseBuffer.array(),0, readBytes));

}

}

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

iterator.remove();

}

}

}

}

測試結(jié)果:

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

將Socket通道注冊到Selector中绑嘹,監(jiān)聽感興趣的事件

當(dāng)感興趣的時間就緒時稽荧,則會進(jìn)去我們處理的方法進(jìn)行處理

每處理完一次就緒事件,刪除該選擇鍵(因為我們已經(jīng)處理完了)

4.4管道和DataGramChannel

這里我就不再講述了工腋,最難的TCP都講了姨丈,UDP就很簡單了。

UDP:

管道:

五擅腰、總結(jié)

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

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

參考資料:

https://www.zhihu.com/question/29005375---如何學(xué)習(xí)Java的NIO趁冈?

http://ifeve.com/java-nio-all/---Java NIO 系列教程

https://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html-----NIO 入門

https://blog.csdn.net/anxpp/article/details/51503329-----Linux 網(wǎng)絡(luò) I/O 模型簡介(圖文)

https://wangjingxin.top/2016/10/21/decoration/-----談?wù)刯ava的NIO和AIO

https://www.yiibai.com/java_nio/-----Java NIO教程

https://blog.csdn.net/cowthan/article/details/53563206------Java 8:Java 的新IO (nio)

https://blog.csdn.net/youyou1543724847/article/details/52748785-------JAVA NIO(1.基本概念歼争,基本類)

https://www.cnblogs.com/zingp/p/6863170.html-----IO模式和IO多路復(fù)用

https://www.cnblogs.com/Evsward/p/nio.html----掌握NIO,程序人生

https://blog.csdn.net/anxpp/article/details/51512200----Java 網(wǎng)絡(luò)IO編程總結(jié)(BIO渗勘、NIO沐绒、AIO均含完整實例代碼)

https://zhuanlan.zhihu.com/p/24393775?refer=hinus---進(jìn)擊的Java新人

《Java編程思想》

《瘋狂Java 講義》

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市旺坠,隨后出現(xiàn)的幾起案子乔遮,更是在濱河造成了極大的恐慌,老刑警劉巖取刃,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蹋肮,死亡現(xiàn)場離奇詭異,居然都是意外死亡璧疗,警方通過查閱死者的電腦和手機坯辩,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來崩侠,“玉大人漆魔,你說我怎么就攤上這事±材ぃ” “怎么了有送?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長僧家。 經(jīng)常有香客問我雀摘,道長,這世上最難降的妖魔是什么八拱? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任阵赠,我火速辦了婚禮涯塔,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘清蚀。我一直安慰自己匕荸,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布枷邪。 她就那樣靜靜地躺著榛搔,像睡著了一般。 火紅的嫁衣襯著肌膚如雪东揣。 梳的紋絲不亂的頭發(fā)上践惑,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音嘶卧,去河邊找鬼尔觉。 笑死,一個胖子當(dāng)著我的面吹牛芥吟,可吹牛的內(nèi)容都是我干的侦铜。 我是一名探鬼主播,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼钟鸵,長吁一口氣:“原來是場噩夢啊……” “哼钉稍!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起携添,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤嫁盲,失蹤者是張志新(化名)和其女友劉穎篓叶,沒想到半個月后烈掠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡缸托,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年左敌,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片俐镐。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡矫限,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出佩抹,到底是詐尸還是另有隱情叼风,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布棍苹,位于F島的核電站无宿,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏枢里。R本人自食惡果不足惜孽鸡,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一蹂午、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧彬碱,春花似錦豆胸、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至嚼沿,卻和暖如春搬泥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背伏尼。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工忿檩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人爆阶。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓燥透,卻偏偏與公主長得像,于是被迫代替她去往敵國和親辨图。 傳聞我的和親對象是個殘疾皇子班套,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

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

  • Java NIO(New IO)是從Java 1.4版本開始引入的一個新的IO API,可以替代標(biāo)準(zhǔn)的Java I...
    編碼前線閱讀 2,258評論 0 5
  • Java NIO(New IO)是從Java 1.4版本開始引入的一個新的IO API故河,可以替代標(biāo)準(zhǔn)的Java I...
    JackChen1024閱讀 7,536評論 1 143
  • Java NIO(New IO)是從Java 1.4版本開始引入的一個新的IO API吱韭,可以替代標(biāo)準(zhǔn)的Java I...
    zhisheng_blog閱讀 1,112評論 0 7
  • JAVA NIO基礎(chǔ) ...
    文思li閱讀 610評論 0 3
  • 百日行動派 進(jìn)入復(fù)習(xí)沖刺階段,每天面對各式各樣的題目鱼的,難免審美疲勞理盆,我們今天就來總結(jié)一下在各路高考題和模擬題中出現(xiàn)...
    立為閱讀 226評論 0 0