




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)
文檔簡介
1、Java 的 I/O 類庫的基本架構(gòu)I/O 問題是任何編程語言都無法回避的問題,可以說 I/O 問題是整個(gè)人機(jī)交互的問題,因?yàn)?I/O 是獲取和交換信息的主要。在這個(gè)數(shù)據(jù)大,I/O 問題尤其突出,很容易成為一個(gè)性能瓶頸。正因如此,所以 Java在 I/O 上也一直在做持續(xù)的優(yōu)化,如從 1.4 開始引入了 NIO,提升了 I/O 的性能。關(guān)于 NIO在后面詳細(xì)介紹。Java 的 I/O 操作類在包 java.io 下,大概有將近 80 個(gè)類,但是這些類大概可以分成四組,分別是:.基于字節(jié)操作的基于字符操作的基于磁盤操作的基于網(wǎng)絡(luò)操作的I/O I/O I/O I/O接口:InputS
2、tream 和 OutputStream接口:Writer 和 Reader接口:File 接口:Socket前兩組主要是根據(jù)傳輸數(shù)據(jù)的數(shù)據(jù)格式,后兩組主要是根據(jù)傳輸數(shù)據(jù)的方式,雖然 Socket 類并不在 java.io 包下,但是我仍然把它們劃分在一起,因?yàn)槲覀€(gè)人認(rèn)為 I/O 的問題要么是數(shù)據(jù)格式影響 I/O 操作,要么是傳輸方式影響I/O 操作,也就是將什么樣的數(shù)據(jù)寫到什么地方的問題,I/O 只是人與或者與交互的,除了在它們能夠完成這個(gè)交互功能外,我們關(guān)注的就是如何提高它的運(yùn)行效率了,而數(shù)據(jù)格式和傳輸方式是影響效率最關(guān) 鍵的因素了。我們后面的分析也是基于這兩個(gè)因素來展開的。基于字節(jié)的 I
3、/O 操作接口基于字節(jié)的 I/O 操作接口輸入和輸出分別是:InputStream 和 OutputStream, InputStream 輸入流的類繼承層次如下圖所示:圖 1. InputStream 相關(guān)類層次結(jié)構(gòu)(查看大圖)輸入流根據(jù)數(shù)據(jù)類型和操作方式又被劃分成若干個(gè)子類,每個(gè)子類分別處理不同操作類型,OutputStream 輸出流的類層次結(jié)構(gòu)也是類似,如下圖所示:圖 2. OutputStream 相關(guān)類層次結(jié)構(gòu)(查看大圖)這里就不詳細(xì)解釋每個(gè)子類如何使用了,如果不清楚的話可以參考一下 JDK 的API 說明文檔,這里只想說明兩點(diǎn),一個(gè)是操作數(shù)據(jù)的方式是可以組合使用的, 如這樣組合使
4、用OutputStream out = new BufferedOutputStream(new ObjectOutputStream(new FileOutputStream("fileName");還 有一點(diǎn)是流最終寫到什么地方必須要指定,要么是寫到磁盤要么是寫到網(wǎng)絡(luò)中,其實(shí)從上面的類圖中我們發(fā)現(xiàn),寫網(wǎng)絡(luò)實(shí)際上也是寫文件,只不過寫網(wǎng)絡(luò)還需 要處理就是底層操作系統(tǒng)再將數(shù)據(jù)傳送到其它地方而不是本地磁盤。關(guān)于網(wǎng)絡(luò) I/O 和磁盤 I/O在后面詳細(xì)介紹?;谧址?I/O 操作接口不磁盤還是網(wǎng)絡(luò)傳輸,最小的單元都是字節(jié),而不是字符,所以 I/O 操作的都是字節(jié)而不是字符,但是為
5、啥有操作字符的 I/O 接口呢?這是因?yàn)槲覀兊耐ǔ2僮鞯臄?shù)據(jù)都是以字符形式,為了操作方然要提供一個(gè)直接寫字符的 I/O 接口,如此而已。我們知道字符到字節(jié)必須要經(jīng)過編碼轉(zhuǎn)換,而這個(gè)編碼又非常耗時(shí),而且還會經(jīng)常出現(xiàn)亂碼問題,所以 I/O 的編碼問題經(jīng)常是讓人頭疼的問題。關(guān)于 I/O 編碼問題請參考另一篇文章 深入分析 Java 中的中文編碼問題。下圖是寫字符的 I/O 操作接口涉及到的類,Writer 類提供了一個(gè)抽象方法write(char cbuf, int off, int len) 由子類去實(shí)現(xiàn)。圖 3. Writer 相關(guān)類層次結(jié)構(gòu)(查看大圖)讀字符的操作接口也有類似的類結(jié)構(gòu),如下圖所
6、示:圖 4.Reader 類層次結(jié)構(gòu)(查看大圖)讀 字符的操作接口中也是 int read(char cbuf, int off, int len),返回讀到的 n 個(gè)字節(jié)數(shù),不Writer 還是 Reader 類它們都只定義了或?qū)懭氲臄?shù)據(jù)字符的方式,也就是怎么寫或讀,但是并沒有規(guī)定數(shù)據(jù)要寫到哪去,寫到哪去就是我們后面要討論的基于磁盤和網(wǎng)絡(luò)的工作機(jī) 制。字節(jié)與字符的轉(zhuǎn)化接口另外數(shù)據(jù)持久化或網(wǎng)絡(luò)傳輸都是以字節(jié)進(jìn)行的,所以必須要有字符到字節(jié)或字節(jié)到字符的轉(zhuǎn)化。字符到字節(jié)需要轉(zhuǎn)化,其中讀的轉(zhuǎn)化過程如下圖所示:圖 5. 字符相關(guān)類結(jié)構(gòu)InputStreamReader 類是字節(jié)到字符的轉(zhuǎn)化橋梁,Inp
7、utStream 到 Reader 的過程要指定編碼字符集,否則將采用操作系統(tǒng)默認(rèn)字符集,很可能會出現(xiàn)亂碼問題。StreamDecoder 正是完成字節(jié)到字符的的實(shí)現(xiàn)類。也就是當(dāng)你用如下方式讀取一個(gè)文件時(shí):1.文件try StringBuffer char buf = FileReader fstr = new StringBuffer(); new char1024;= new FileReader("file");while(f.read(buf)>0) str.append(buf);str.toString(); catch (IOException e) F
8、ileReader 類就是按照上面的工作方式文件的,F(xiàn)ileReader 是繼承了InputStreamReader 類,實(shí)際上是文件流,然后通過 StreamDecoder成 char,只不過這里的字符集是默認(rèn)字符集。寫入也是類似的過程如下圖所示:圖 6. 字符編碼相關(guān)類結(jié)構(gòu)通過 OutputStreamWriter 類完成,字符到字節(jié)的編碼過程,由完成編碼過程。StreamEncoder回頁首磁盤 I/O 工作機(jī)制前面介紹了基本的 Java I/O 的操作接口,這些接口主要定義了如何操作數(shù)據(jù), 以及介紹了操作兩種數(shù)據(jù)結(jié)構(gòu):字節(jié)和字符的方式。還有一個(gè)關(guān)鍵問題就是數(shù)據(jù)寫到何處,其中一個(gè)主要方式
9、就是將數(shù)據(jù)持久化到物理磁盤,下面將介紹如何將數(shù)據(jù)持久化到物理磁盤的過程。我 們知道數(shù)據(jù)在磁盤的唯一最小描述就是文件,也就是說上層應(yīng)用程序只能通過文件來操作磁盤上的數(shù)據(jù),文件也是操作系統(tǒng)和磁盤驅(qū)動器交互的一個(gè)最小單元。值得 注意的是 Java 中通常的 File 并不代表一個(gè)真實(shí)存在的文件對象, 當(dāng)你通過指定一個(gè)路徑描述符時(shí),它就會返回一個(gè)代表這個(gè)路徑相關(guān)聯(lián)的一個(gè)虛擬對象,這個(gè)可能是一個(gè)真實(shí)存在的文件或者是 一個(gè)包含多個(gè)文件的目錄。為何要這樣設(shè)計(jì)?因?yàn)榇蟛糠智闆r下,我們并不關(guān)心這個(gè)文件是否真的存在,而是關(guān)心這個(gè)文件到底如何操作。例如我們里通常存了 幾百個(gè)朋友的號碼,但是我們通常關(guān)心的是我有沒有這
10、個(gè)朋友的號碼,或者這個(gè)號碼是什么,但是這個(gè)號碼到底能不能打通,我們并不是時(shí)時(shí)刻刻 都去檢查,而只有在真正要給他打看這個(gè)能不能用。也就是使用這個(gè)要比打這個(gè)的次數(shù)多很多。何時(shí)真正會要檢查一個(gè) 文件存不存?就是在真正要這個(gè)文件時(shí),例如FileInputStream 類都是操作一個(gè)文件的接口,注意到在創(chuàng)建一個(gè)FileInputStream 對象時(shí),會創(chuàng)建一個(gè) FileDescriptor 對象,其實(shí)這個(gè)對象就是真正代表一個(gè)存在的文件對象的描述,當(dāng)我們在操作一個(gè)文件對象時(shí)可以通過getFD() 方法獲取真正操作的與底層操作系統(tǒng)關(guān)聯(lián)的文件描述。例如可以調(diào)用FileDescriptor.sync() 方法將
11、操作系統(tǒng)緩存中的數(shù)據(jù)強(qiáng)制刷新到物理磁盤中。下面以1的例,介紹下如何從磁盤一段文本字符。如下圖所示:圖 7.從磁盤文件當(dāng) 傳入一個(gè)文件路徑,將會根據(jù)這個(gè)路徑創(chuàng)建一個(gè)File 對象來標(biāo)識這個(gè)文件,然后將會根據(jù)這個(gè) File 對象創(chuàng)建真正文件的操作對象,這時(shí)將會真正創(chuàng)建一個(gè)關(guān)聯(lián)真實(shí)存在的磁盤文件的文件描述符 FileDescriptor,通過這個(gè)對象可以直接這個(gè)磁盤文件。由于我們需要的是字符格式,所以需要StreamDecoder 類將 byte為 char 格式,至于如何從磁盤驅(qū)動器上一段數(shù)據(jù),由操作系統(tǒng)幫我們完成。至于操作系統(tǒng)是如何將數(shù)據(jù)持久化到磁盤以及如何建立數(shù)據(jù)結(jié)構(gòu)需要根據(jù)當(dāng)前操作系統(tǒng)使用
12、何種文件系統(tǒng)來回答,至于文件系統(tǒng)的相關(guān)以參考另外的文章?;仨撌譐ava Socket 的工作機(jī)制Socket 這個(gè)概念沒有對應(yīng)到一個(gè)具體的實(shí)體,它是描述計(jì)算機(jī)之間完成相互通信一種抽象功能。打個(gè)比方,可以把 Socket 比作為兩個(gè)城市之間的交通工具, 有了它,就可以在城市之間來回穿梭了。交通工具有多種,每種交通工具也有相應(yīng)的交通規(guī)則。Socket 也一樣,也有多種。大部分情況下我們使用的都是基于TCP/IP 的流套接字,它是一種穩(wěn)定的通信協(xié)議。下圖是典型的基于 Socket 的通信的場景:圖 8.Socket 通信示例主 機(jī) A 的應(yīng)用程序要能和主機(jī) B 的應(yīng)用程序通信,必須通過接,而建立 S
13、ocket 連接必須需要底層 TCP/IP 協(xié)議來建立 TCPSocket 建立連連接。建立 TCP連接需要底層 IP 協(xié)議來尋址網(wǎng)絡(luò)中的主機(jī)。我們知道網(wǎng)絡(luò)層使用的 IP 協(xié)議可以幫助我們根據(jù) IP 地址來找到目標(biāo)主機(jī),但是一臺主機(jī)上可能運(yùn)行著多個(gè)應(yīng)用程序,如何才能與指定的應(yīng)用程序通信就要通過 TCP 或 UPD 的地址也就是端口號來指定。這樣就可以通過一個(gè) Socket 實(shí)例唯一代表一個(gè)主機(jī)上的一個(gè)應(yīng)用程序的通信鏈路了。建立通信鏈路當(dāng) 客戶端要與服務(wù)端通信,客戶端首先要創(chuàng)建一個(gè) Socket 實(shí)例,操作系統(tǒng)將為這個(gè) Socket 實(shí)例分配一個(gè)沒有被使用的本地端,并創(chuàng)建一個(gè)包含本地和地址和端的
14、套接字?jǐn)?shù)據(jù)結(jié)構(gòu),這個(gè)數(shù)據(jù)結(jié)構(gòu)將一直保存在系統(tǒng)中直到這個(gè)連接關(guān)閉。在創(chuàng)建 Socket 實(shí)例的構(gòu)造函數(shù)正確返回之前,將要進(jìn)行 TCP 的三次握手協(xié)議,TCP 握手協(xié)議完成后,Socket 實(shí)例對象將創(chuàng)建完成,否則將拋出 IOException 錯(cuò)誤。與之對應(yīng)的服務(wù)端將創(chuàng)建一個(gè) ServerSocket 實(shí)例,ServerSocket 創(chuàng)建比較簡單只要指定的端沒有被占用,一般實(shí)例創(chuàng)建都會,同時(shí)操作系統(tǒng)也會為ServerSocket 實(shí)例創(chuàng)建一個(gè)底層數(shù)據(jù)結(jié)構(gòu),這個(gè)數(shù)據(jù)結(jié)構(gòu)中包含指定的端和包含地址的通配符,通常情況下都是“*”即所有地址。之后當(dāng)調(diào)用 accept() 方法時(shí),將進(jìn)入阻塞狀態(tài),等待客戶端
15、的請求。當(dāng)一個(gè)新的請求到來時(shí),將為這個(gè)連接創(chuàng)建一個(gè)新的套接字?jǐn)?shù)據(jù)結(jié)構(gòu),該套接字?jǐn)?shù)據(jù)的信息包含的地址和端口信息正 是請求源地址和端口。這個(gè)新創(chuàng)建的數(shù)據(jù)結(jié)構(gòu)將會關(guān)聯(lián)到ServerSocket 實(shí)例的一個(gè)未完成的連接數(shù)據(jù)結(jié)構(gòu)列表中,注意這時(shí)服務(wù)端與之對應(yīng)的 Socket 實(shí)例并沒有完成創(chuàng)建,而要等到與客戶端的三次握手完成后,這個(gè)服務(wù)端的 Socket 實(shí)例才會返回,并將這個(gè) Socket 實(shí)例對應(yīng)的數(shù)據(jù)結(jié)構(gòu)從未完成列表中移到已完成列表中。所以 ServerSocket 所關(guān)聯(lián)的列表中每個(gè)數(shù)據(jù)結(jié)構(gòu),都代表與一個(gè)客戶端的建立的 TCP 連接。數(shù)據(jù)傳輸傳輸數(shù)據(jù)是我們建立連接的主要目的,如何通過 Socke
16、t 傳輸數(shù)據(jù),下面將詳細(xì)介紹。當(dāng) 連接已經(jīng)建立,服務(wù)端和客戶端都會擁有一個(gè) Socket 實(shí)例,每個(gè)Socket 實(shí)例一個(gè) InputStream 和 OutputStream,正是通過這兩個(gè)對象來交換數(shù)據(jù)。同時(shí)我們也知道網(wǎng)絡(luò) I/O 都是以字節(jié)流傳輸?shù)?。?dāng) Socket 對象創(chuàng)建時(shí),操作系統(tǒng)將會為 InputStream 和 OutputStream 分別分配一定大小的緩沖區(qū),數(shù)據(jù)的寫入和都是通過這個(gè)緩存區(qū)完成的。寫入端將數(shù)據(jù)寫到OutputStream 對應(yīng)的 SendQ 隊(duì)列中,當(dāng)隊(duì)列填滿時(shí),數(shù)據(jù)將被到另一端InputStream 的 RecvQ 隊(duì)列中,如果這時(shí) RecvQ 已經(jīng)滿了,
17、那么 OutputStream的 write 方法將會阻塞直到 RecvQ 隊(duì)列有足夠的空間容納 SendQ的數(shù)據(jù)。值得特別注意的是,這個(gè)緩存區(qū)的大小以及寫入端的速度和 端的速度非常影響這個(gè)連接的數(shù)據(jù)傳輸效率,由于可能會發(fā)生阻塞,所以網(wǎng)絡(luò) I/O 與磁盤 I/O 在數(shù)據(jù)的寫入和 還要有一個(gè)協(xié)調(diào)的過程,如果兩邊同時(shí)傳送數(shù)據(jù)時(shí)可能會產(chǎn)生死鎖,在后面 NIO 部分將介紹避免這種情況。回頁首NIO 的工作方式BIO 帶來的BIO 即阻塞 I/O,不者從 InputStream磁盤 I/O 還是網(wǎng)絡(luò) I/O,數(shù)據(jù)在寫入 OutputStream 或時(shí)可能會阻塞。一旦有線程阻塞將會失去 CPU 的使量和有
18、性能要求情況下是不能接受的。雖然當(dāng)前的用權(quán),這在當(dāng)前的大規(guī)模網(wǎng)絡(luò) I/O 有一些解決辦法,如一個(gè)客戶端一個(gè)處理線程,出現(xiàn)阻塞時(shí)只是一個(gè)線程阻塞而影響其它線程工作,還有為了減少系統(tǒng)線程的開銷,采用線程池的辦法來減少線 程創(chuàng)建和回收的成本,但是有一些使用場景仍然是無法解決的。如當(dāng)前一些需要大量 HTTP 長連接的情況,像淘寶現(xiàn)在使用的 Web項(xiàng)目,服務(wù)端需要同時(shí)保持幾百萬的 HTTP 連接,但是并不是每時(shí)每刻這些連接都在傳輸數(shù)據(jù),這種情況下不可能同時(shí)創(chuàng)建這么多線程來保持連接。即使線程的數(shù)量不是問題,仍然有一些問題還是無法避免 的。如這種情況,我們想給某些客戶端更高的服務(wù)優(yōu)先級,很難通過設(shè)計(jì)線程的優(yōu)
19、先級來完成,另外一種情況是,我們需要讓每個(gè)客戶端的請求在服務(wù)端可能需要一些競爭,由于這些客戶端是在不同線程中,因此需要同步,而往往要實(shí)現(xiàn)這些同步操作要遠(yuǎn)遠(yuǎn)比用單線程復(fù)雜很多。以上這些情況都說明,我們需要另外一種新的I/O操作方式。NIO 的工作機(jī)制我們先看一下 NIO涉及到的關(guān)聯(lián)類圖,如下:圖 9.NIO相關(guān)類圖上 圖中有兩個(gè):Channel 和 Selector,它們是NIO 中兩個(gè)概念。我們還用前面的城市交通工具來繼續(xù)比喻 NIO 的工作方式,這里的 Channel 要比 Socket 更加具體,它可以比作為某種具體的交通工具,如汽車或是高鐵等, 而 Selector 可以比作為一個(gè)車站的
20、車輛運(yùn)行調(diào)度系統(tǒng),它將負(fù)責(zé)每輛車的當(dāng)前運(yùn)行狀態(tài):是已經(jīng)出戰(zhàn)還是在路,也就是它可以輪詢每個(gè) Channel 的狀態(tài)。這里還有一個(gè) Buffer 類,它也比 Stream 更加具體化,我們可以將它比作為車上的座位,Channel 是汽車的話就是汽車上的座位,就是的座位,它始終是一個(gè)具體的概念,與 Stream 不同。Stream 只能代表是一個(gè)座位,至于是什么座位由你去想象,也就是你在去上車之前并不知道,這個(gè)車上是否還有沒有座位了,也不知道上的是什么車,因?yàn)槟悴⒉荒苓x 擇,這些信息都已經(jīng)被封裝在了工具(Socket)里面了,對你是透明的。NIO 引入了Channel、Buffer 和 Select
21、or 就是想把這些信息具體化,讓程序員有機(jī)會它們,如:當(dāng)我們調(diào)用 write() 往 SendQ 寫數(shù)據(jù)時(shí),當(dāng)一次寫的數(shù)據(jù)超過 SendQ長度是需要按照 SendQ 的長度進(jìn)行分割,這個(gè)過程中需要有將用戶空間數(shù)據(jù)和內(nèi)核地址空間進(jìn)行切換,而這個(gè)切換不是你可以的。而在 Buffer 中我們可以Buffer 的 capacity,并且是否擴(kuò)容以及如何擴(kuò)容都可以。理解了這些概念后我們看一下,實(shí)際上它們是如何工作的,下面是典型的一段NIO 代碼:2. NIO 工作代碼示例public void selector() throws IOException ByteBuffer buffer = Selec
22、tor selector = ServerSocketChannelByteBuffer.allocate(1024); Selector.open();ssc = ServerSocketChannel.open();ssc.configureBlocking(false);/設(shè)置為非阻塞方式ssc.socket().bind(new InetSocketAddress(8080); ssc.register(selector, SelectionKey.OP_ACCEPT);/的事件while (true) Set selectedKeys = selector.selectedKeys(
23、);/取得所有 key集合Iterator it = selectedKeys.iterator(); while (it.hasNext() SelectionKey key = (SelectionKey) it.next();if (key.readyOps() & SelectionKey.OP_ACCEPT) = SelectionKey.OP_ACCEPT) ServerSocketChannel ssChannel = (ServerSocketChannel) key.channel();SocketChannel sc = ssChannel.accept();/接受
24、到服務(wù)端的請求sc.configureBlocking(false); sc.register(selector, SelectionKey.OP_READ); it.remove(); else if(key.readyOps() & SelectionKey.OP_READ) = SelectionKey.OP_READ) SocketChannel sc = (SocketChannel) key.channel(); while (true) buffer.clear();int n = sc.read(buffer);/ if (n <= 0) 數(shù)據(jù)break;buff
25、er.flip();it.remove();調(diào)用 Selector 的靜態(tài)工廠創(chuàng)建一個(gè)選擇器,創(chuàng)建一個(gè)服務(wù)端的 Channel 綁定到一個(gè) Socket 對象,并把這個(gè)通信信道 到選擇器上,把這個(gè)通信信道設(shè)置為非阻塞模式。然后就可以調(diào)用 Selector 的 selectedKeys 方法來檢查已經(jīng)注冊在這個(gè)選擇器上的所有通信信道是否有需要的 時(shí),將會返回所有的 SelectionKey,通過這個(gè)對象發(fā)生,如果有某個(gè)發(fā)生Channel 方法就可以取得這的數(shù)據(jù)是 Buffer,這個(gè)個(gè)通信信道對象從而可以通信的數(shù)據(jù),而這里Buffer 是我們可以的緩沖器。在上面的這段,是將 Server 端的連接
26、請求的和處理請求的放在一個(gè)線程中,但是在實(shí)際應(yīng)用中,我們通常會把它們放在兩個(gè)線程中,一個(gè)線程專門負(fù)責(zé)客戶端的連接請求,而 且是阻塞方式執(zhí)行的;另外一個(gè)線程專門來處理請求,這個(gè)專門處理請求的線程才會真正采用 NIO 的方式,像 Web 服務(wù)器 Tomcat 和 Jetty 都是這個(gè)處理方式,關(guān)于 Tomcat 和 Jetty 的 NIO 處理方式可以參考文章 Jetty 的工作原理和與 Tomcat 的比較。下圖是描述了基于 NIO 工作方式的 Socket 請求的處理過程:圖 10. 基于 NIO 的 Socket 請求的處理過程上 圖中的 Selector 可以同時(shí)前提是這個(gè) Selecto
27、r 要已經(jīng)用 select() 方法檢查已經(jīng)一組通信信道(Channel)上的 I/O 狀態(tài), 到這些通信信道中。選擇器 Selector 可以調(diào)的通信信道上的是否有 I/O 已經(jīng)準(zhǔn)備好,如果沒有至少一個(gè)信道 I/O 狀態(tài)有變化,那么 select 方阻塞等待或在超時(shí)時(shí)間后會返回 0。上圖中如果有多個(gè)信道有數(shù)據(jù),那么將會將這些數(shù)據(jù)分配到對應(yīng)的數(shù)據(jù) Buffer 中。所以關(guān)鍵的地方是有一個(gè)線程來處理所有連接的數(shù)據(jù)交互, 每個(gè)連接的數(shù)據(jù)交互都不是阻塞方式,所以可以同時(shí)處理大量的連接請求。Buffer 的工作方式上面介紹了 Selector 將檢測到有通信信道 I/O 有數(shù)據(jù)傳輸時(shí),通過 selel
28、ct()取得 SocketChannel,將數(shù)據(jù)如何接受和寫出數(shù)據(jù)?或?qū)懭?Buffer 緩沖區(qū)。下面討論一下 BufferBuffer 可以簡單的理解為一組基本數(shù)據(jù)類型的元素列表,它通過幾個(gè)變量來保存這個(gè)數(shù)據(jù)的當(dāng)前位置狀態(tài),也就是有四個(gè)索引。如下表所示:表 1.Buffer 中的參數(shù)項(xiàng)索引capacity緩沖區(qū)數(shù)組的總長度說明position下一個(gè)要操作的數(shù)據(jù)元素的位置limit mark緩沖區(qū)數(shù)組中不可操作的下一個(gè)元素的位置,limit<=capacity用于當(dāng)前 position 的前一個(gè)位置或者默認(rèn)是 0在實(shí)際操作數(shù)據(jù)時(shí)它們有如下關(guān)系圖:我們通過 ByteBuffer.alloc
29、ate(11) 方法創(chuàng)建一個(gè) 11 個(gè) byte 的數(shù)組緩沖區(qū),初始狀態(tài)如上圖所示,position 的位置為 0,capacity 和 limit 默認(rèn)都是數(shù)組長度。當(dāng)我們寫入 5 個(gè)字節(jié)時(shí)位置變化如下圖所示:這時(shí)我們需要將緩沖區(qū)的 5 個(gè)字節(jié)數(shù)據(jù)寫入 Channel 通信信道,所以我們需要調(diào)用byteBuffer.flip() 方法,數(shù)組的狀態(tài)又發(fā)生如下變化:這時(shí)底層操作系統(tǒng)就可以從緩沖區(qū)中正確這 5 個(gè)字節(jié)數(shù)據(jù)出去了。在下一次寫數(shù)據(jù)之前我們在調(diào)一下 clear() 方法。緩沖區(qū)的索引狀態(tài)又回到初始位置。這里還要說明一下 mark,當(dāng)我們調(diào)用 mark() 時(shí),它將當(dāng)前 position
30、的前一個(gè)位置,當(dāng)我們調(diào)用 reset 時(shí),position 將恢復(fù) mark下來的值。還 有一點(diǎn)需要說明,通過 Channel 獲取的 I/O 數(shù)據(jù)首先要經(jīng)過操作系統(tǒng)的 Socket 緩沖區(qū)再將數(shù)據(jù)到 Buffer 中,這個(gè)的操作系統(tǒng)緩沖區(qū)就是底層的 TCP 協(xié)議關(guān)聯(lián)的 RecvQ 或者SendQ 隊(duì)列,從操作系統(tǒng)緩沖區(qū)到用戶緩沖區(qū)數(shù)據(jù)比較耗性能,Buffer 提供了另外一種直接操作操作系統(tǒng)緩沖區(qū)的的方式即 ByteBuffer.allocateDirector(size),這個(gè)方法返回的byteBuffer 就是與底層操作方式類似??臻g關(guān)聯(lián)的緩沖區(qū),它的操作方式與 linux2.4 內(nèi)核的
31、sendfile回頁首I/O 調(diào)優(yōu)下面就磁盤 I/O 和網(wǎng)絡(luò) I/O 的一些常用的優(yōu)化技巧進(jìn)行總結(jié)如下:磁盤 I/O 優(yōu)化性能檢測我們的應(yīng)用程序通常都需要磁盤數(shù)據(jù),而磁盤 I/O 通常都很耗時(shí),我們要I/O 是否是一個(gè)瓶頸,我們有一些參數(shù)指標(biāo)可以參考:如 我們可以測試應(yīng)用程序看系統(tǒng)的 I/O wait 指標(biāo)是否正常,例如測試機(jī)器有 4 個(gè) CPU,那么理想的 I/O wait 參數(shù)不應(yīng)該超過 25%,如果超過 25% 的話,I/O 很可能成為應(yīng)用程序的性能瓶頸。Linux 操作系統(tǒng)下可以通過 iostat 命令查看。通常 我們在I/O 性能時(shí)還會看另外一個(gè)參數(shù)就是 IOPS,我們應(yīng)用程序需要最
32、低的 IOPS 是多少,而我們的磁盤的 IOPS 能不能達(dá)到我們的要求。每個(gè)磁盤的 IOPS 通常是在一個(gè)范圍內(nèi),這和在磁盤的數(shù)據(jù)塊的大小和方式也有關(guān)。但是主要是由磁盤的轉(zhuǎn)速決定的,磁盤的轉(zhuǎn)速越高磁盤的 IOPS 也越高。現(xiàn) 在為了提高磁盤 I/O 的性能,通常采用一種叫 RAID 的技術(shù),就是將不同的磁盤組合起來來提高 I/O 性能,目前有多種 RAID 技術(shù),每種 RAID 技術(shù)對 I/O 性能提升會有不同,可以用一個(gè) RAID 因子來代表,磁盤的讀寫吞吐量可以通過iostat 命令來獲取,于是我們可以計(jì)算出一個(gè)理論的 IOPS 值,計(jì)算公式如下所以:( 磁盤數(shù) * 每塊磁盤的 IOPS)
33、/( 磁盤讀的吞吐量 +RAID 因子 * 磁盤寫的吞吐量 )=IOPS這個(gè)公式的詳細(xì)信息請查閱參考資料 Understanding Disk I/O。提升 I/O 性能提升磁盤 I/O 性能通常的方法有:1.2.增加緩存,減少磁盤次數(shù)優(yōu)化磁盤的管理系統(tǒng),設(shè)計(jì)最優(yōu)的磁盤這里是在底層操作系統(tǒng)層面考慮的。策略,以及磁盤的尋址策略,3.設(shè)計(jì)合理的磁盤數(shù)據(jù)塊,以及這些數(shù)據(jù)塊的策略,這里是在應(yīng)用層面考慮的。如我們可以給存放的數(shù)據(jù)設(shè)計(jì)索引,通過尋址索引來加快和減少磁盤的應(yīng)用合理的所示:,還有可以采用異步和非阻塞的方式加快磁盤的效率。4.RAID 策略提升磁盤 IO,每種 RAID 的區(qū)別我們可以用下表表
34、2.RAID 策略磁盤陣列說明RAID 數(shù)據(jù)被平均寫到多個(gè)磁盤陣列中,寫數(shù)據(jù)和讀數(shù)據(jù)都是并行的,所以磁盤0的 IOPS 可以提高一倍。磁盤陣列說明RAID 1 的主要作用是能夠提高數(shù)據(jù)的安全性,它將一份數(shù)據(jù)分別到多RAID 1個(gè)磁盤陣列中。并不能提升 IOPS 但是相同的數(shù)據(jù)有多個(gè)備份。通常用于對數(shù)據(jù)安全性較高的場合中。這中設(shè)計(jì)方式是前兩種的折中方式,它將數(shù)據(jù)平均寫到所有磁盤陣列總數(shù)減一的磁盤中,往另外一個(gè)磁盤中寫入這份數(shù)據(jù)的奇偶校驗(yàn)信息。如果其中一個(gè)磁盤損壞,可以通過其它磁盤的數(shù)據(jù)和這個(gè)數(shù)據(jù)的奇偶校驗(yàn)信息來恢復(fù)這份數(shù)據(jù)。如名字一樣,就是根據(jù)數(shù)據(jù)的備份情況進(jìn)行分組,一份數(shù)據(jù)同時(shí)寫到多個(gè)備份磁盤
35、分組中,同時(shí)多個(gè)分組也會并行讀寫。RAID 5RAID 0+1網(wǎng)絡(luò) I/O 優(yōu)化網(wǎng)絡(luò) I/O 優(yōu)化通常有一些基本處理原則:1.一個(gè)是減少網(wǎng)絡(luò)交互的次數(shù):要減少網(wǎng)絡(luò)交互的次數(shù)通常我們在需要網(wǎng)絡(luò)交互的兩端會設(shè)置緩存,比如 Oracle 的 JDBC 驅(qū)動程序,就提供了對查詢的 SQL 結(jié)果的緩存,在客戶端和數(shù)據(jù)庫端 ,可以有效的減少對數(shù)據(jù)庫的。關(guān)于 Oracle JDBC 的內(nèi)存以參考 Oracle JDBC 內(nèi)存管理。除了設(shè)置緩存還有一個(gè)辦法是,合并請求:如在數(shù)據(jù)庫時(shí),我們要查 10 個(gè) id,我可以每次查一個(gè) id,也可以一次查 10 個(gè)id。再比如在一個(gè)頁面時(shí)通過會有多個(gè) js 或 css
36、的文件,我們可以將多個(gè) js 文件合并在一個(gè) HTTP到后端 Web 服務(wù)器根據(jù)這個(gè) URL中,每個(gè)文件用逗號隔開,然后,再拆分出各個(gè)文件,然后打包再一并發(fā)回給前端瀏覽器。這些都是常用的減少網(wǎng)絡(luò) I/O 的辦法。減少網(wǎng)絡(luò)傳輸數(shù)據(jù)量的大?。簻p少網(wǎng)絡(luò)數(shù)據(jù)量的辦法通常是將數(shù)據(jù)壓縮后2.再傳輸,如 HTTP 請求中,通常 Web 服務(wù)器將請求的 Web 頁面縮后在傳輸給瀏覽器。還有就是通過設(shè)計(jì)簡單的協(xié)議,盡量通過gzip 壓協(xié)議和 7 層頭來獲取有用的價(jià)值信息。比如在程序設(shè)計(jì)時(shí),有 4 層盡量避免要整個(gè)通信數(shù)據(jù)來取得需要的信息。3.盡量減少編碼:通常在網(wǎng)絡(luò) I/O 中數(shù)據(jù)傳輸都是以字節(jié)形式的,也就是通
37、常要序列化。但是我們要傳輸?shù)臄?shù)據(jù)都是字符形式的,從字符到字節(jié)必須編碼。但是這個(gè)編碼過程是比較耗時(shí)的,所以 在要經(jīng)過網(wǎng)絡(luò) I/O傳輸時(shí),盡量直接以字節(jié)形式或者減少字符到字節(jié)的轉(zhuǎn)化過程。也就是盡量提前將字符轉(zhuǎn)化為字節(jié),4.根據(jù)應(yīng)用場景設(shè)計(jì)合適的交互方式:所謂的交互場景主要包括同步與異步阻塞與非阻塞方式,下面將詳細(xì)介紹。同步與異步所 謂同步就是一個(gè)任務(wù)的完成需要依賴另外一個(gè)任務(wù)時(shí),只有等待被依賴的任務(wù)完成后,依賴的任務(wù)才能算完成,這是一種可靠的任務(wù)序列。要么都,失敗都 失敗,兩個(gè)任務(wù)的狀態(tài)可以保持一致。而異步是不需要等待被依賴的任務(wù)完成,只是通知被依賴的任務(wù)要完成什么工作,依賴的任務(wù)也立即執(zhí)行,只要
38、完成了整 個(gè)任務(wù)就算完成了。至于被依賴的任務(wù)最終是否真正完成,依賴它的任務(wù)無法確定,所以它是不可靠的任務(wù)序列。我們可以用打很好的比喻同步與異 步操作。和發(fā)來在設(shè)計(jì)到 IO 處理時(shí)通常都會遇到一個(gè)是同步還是異步的處理方式的選擇問題。因?yàn)橥脚c異步的 I/O 處理方式對調(diào)用者的影響很大,在數(shù)據(jù)庫中都會遇到這個(gè)問題。因?yàn)?I/O 操作通常是一個(gè)非常耗時(shí)的操作,在一個(gè)任務(wù)序列中 I/O 通常都是性能瓶頸。但是同步與異步的處理方式對程序的可靠性影響非常大,同步能夠保證程序的可靠性,而異步可以提升程序的性能,必須在可靠性和性能之間做 個(gè)平衡,沒有完美的解決辦法。阻塞與非阻塞阻塞與非阻塞主要是從 CPU 的
39、消耗上來說的,阻塞就是 CPU 停下來等待一個(gè)慢的操作完成 CPU 才接著完成其它的事。非阻塞就是在這個(gè)慢的操作在執(zhí)行時(shí)CPU 去干其它別的事,等這個(gè)慢的操作完成時(shí),CPU 再接著完成后續(xù)的操作。雖然表面上看非阻塞的方式可以明顯的提高 CPU 的利用率,但是也帶了另外一種后果就是系統(tǒng)的線程切換增加。增加的 CPU 使用時(shí)間能不能補(bǔ)償系統(tǒng)的切換成本需要好好評估。兩種的方式的組合組合的方式可以由四種,分別是:同步阻塞、同步非阻塞、異步阻塞、異步非阻塞,這四種方式都對 I/O 性能有影響。下面給出分析,并有一些常用的設(shè)計(jì)用例參考。表 3. 四種組合方式組合方式性能分析同步最常用的一種用法,使用也是最
40、簡單的,但是 I/O 性能一般很差,CPU 大阻塞 部分在提升 I/O同步網(wǎng)絡(luò) I/O態(tài)。性能的常用,就是將 I/O 的阻塞改成非阻塞方式,尤其在是長連接,同時(shí)傳輸數(shù)據(jù)也不是很多的情況下,提升性能非常有非阻效。這種方式通常能提升 I/O 性能,但是會增加 CPU 消耗,要考慮增加的 I/O 性能能不能補(bǔ)償 CPU 的消耗,也就是系統(tǒng)的瓶頸是在 I/O 還是在 CPU 上。這種方式在分布式數(shù)據(jù)庫中經(jīng)常用到,例如在網(wǎng)一個(gè)分布式數(shù)據(jù)庫中寫一塞異步條,通常會有一份是同步阻塞的,而還有兩至三份是備份會阻塞 寫到其它上,這些備份通常都是采用異步阻塞的方式寫 I/O。異步阻塞對網(wǎng)絡(luò) I/O 能夠提升效率,尤
41、其像上面這種同時(shí)寫多份相同數(shù)據(jù)的情況。異步這種組合方式用起來比較復(fù)雜,只有在一些非常復(fù)雜的分布式情況下使用,組合方式性能分析非阻像集群之間的消息同步機(jī)制一般用這種 I/O 組合方式。如 Cassandra 的塞Gossip 通信機(jī)制就是采用異步非阻塞的方式。它適合同時(shí)要傳多份相同的數(shù)據(jù)到集群中不同的,同時(shí)數(shù)據(jù)的傳輸量雖然不大,但是卻非常頻繁。這種網(wǎng)絡(luò) I/O 用這個(gè)方式性能能達(dá)到最高。雖然異步和非阻塞能夠提升 I/O 的性能,但是也會帶來一些額外的性能成本, 例如會增加線程數(shù)量從而增加 CPU 的消耗,同時(shí)也會導(dǎo)致程序設(shè)計(jì)的復(fù)雜度上升。如果設(shè)計(jì)的不合理的話反而會導(dǎo)致性能下降。在實(shí)際設(shè)計(jì)時(shí)要根據(jù)
42、應(yīng)用場景綜合評估一下。下面舉一些異步和阻塞的操作實(shí)例:在 Cassandra 中要數(shù)據(jù)通常會往多個(gè)數(shù)據(jù)節(jié)點(diǎn)命令,但是要檢查同步結(jié)果的應(yīng)用場景,部分每個(gè)節(jié)點(diǎn)返回?cái)?shù)據(jù)的完整性,所以需要一個(gè)異步代碼如下:3.異步同步結(jié)果class AsyncResult implements IAsyncResultprivate private private private privatebyte result_;AtomicBoolean done_ = new AtomicBoolean(false); Lock lock_ = new ReentrantLock();Condition condition_; long startTime_;public AsyncResult()condition_ = lock_.newCondition();/ 創(chuàng)建一個(gè)鎖startTime_ = System.currentTimeMillis();/* 檢查需要的數(shù)據(jù)是否已經(jīng)返回,如果沒有返回阻塞 */ public byte get()lock_.lock(); tryif (!done_.get()condition_.await();catch (InterruptedException ex) throw new AssertionError(ex);finallylock_.unloc
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 公司運(yùn)營部管理制度
- 加工廠環(huán)境管理制度
- 南京家紡店管理制度
- 大學(xué)生社會化成長視角下的高校思政教育理論
- 女職工宿舍管理制度
- 學(xué)院110管理制度
- 小公司5人管理制度
- 小食品銷售管理制度
- 投標(biāo)部獎懲管理制度
- 柴油發(fā)電房管理制度
- 2025年國家英語四級考試試題及答案
- 2025屆河南省洛陽市等兩地高三下學(xué)期三模歷史試題(含答案)
- 智能口罩設(shè)計(jì)優(yōu)化-洞察闡釋
- 2024年湖北省南漳縣事業(yè)單位公開招聘教師崗考試題帶答案分析
- 2025浙江寧波市余姚市市屬企業(yè)面向社會招聘企業(yè)員工68人筆試參考題庫附帶答案詳解
- 限高架維修合同8篇
- 2025年4月八大員-勞務(wù)員練習(xí)題庫與參考答案解析
- 2025-2030肺癌手術(shù)行業(yè)市場現(xiàn)狀供需分析及投資評估規(guī)劃分析研究報(bào)告
- 農(nóng)村飲水安全工程可行性研究報(bào)告
- 一級注冊建筑師真題含答案2025年
- 全麻期間氣道梗阻的預(yù)防與處理
評論
0/150
提交評論