天天看點

java1.4的nio相關知識

基本概念

io是主存和外部裝置(硬碟、終端和網絡等)拷貝資料的過程。io是作業系統的底層功能實作,底層通過i/o指令進行完成。

所有語言運作時系統提供執行i/o較進階别的工具。(c的printf scanf,java的面向對象封裝)

java 标準io回顧

java标準io類庫是io面向對象的一種抽象。基于本地方法的底層實作,我們無須關注底層實作。 inputstreamoutputstream(位元組流):一次傳送一個位元組。 readerwriter(字元流):一次一個字元。

nio簡介

nio是java new io的簡稱,在jdk1.4裡提供的新api。sun官方标榜的特性如下:

– 為所有的原始類型提供(buffer)緩存支援。

– 字元集編碼解碼解決方案。

– channel:一個新的原始i/o抽象。

– 支援鎖和記憶體映射檔案的檔案通路接口。

– 提供多路(non-bloking)非阻塞式的高伸縮性網絡i/o。

本文将圍繞這幾個特性進行學習和介紹。

buffer&chanel

channel和buffer是nio是兩個最基本的資料類型抽象。

buffer:

– 是一塊連續的記憶體塊。

– 是nio資料讀或寫的中轉地。

channel:

– 資料的源頭或者資料的目的地

– 用于向buffer提供資料或者讀取buffer資料,buffer對象的唯一接口。

– 異步i/o支援

java1.4的nio相關知識

圖1:channel和buffer關系

例子1:copyfile.java:

其中buffer内部結構如下(下圖拷貝自資料):

java1.4的nio相關知識

圖2:buffer内部結構

一個buffer主要由position,limit,capacity三個變量來控制讀寫的過程。此三個變量的含義見如下表格:

java1.4的nio相關知識

buffer常見方法:

flip():寫模式轉換成讀模式

rewind():将position重置為0,一般用于重複讀。

clear():清空buffer,準備再次被寫入(position變成0,limit變成capacity)。

compact():将未讀取的資料拷貝到buffer的頭部位。

mark()、reset():mark可以标記一個位置,reset可以重置到該位置。

buffer常見類型:bytebuffer 、mappedbytebuffer 、charbuffer 、doublebuffer 、 floatbuffer 、 intbuffer 、 longbuffer 、 shortbuffer。

channel常見類型:filechannel、datagramchannel(udp)、socketchannel(tcp)、serversocketchannel(tcp)

在本機上面做了個簡單的性能測試。我的筆記本性能一般。(具體代碼可以見附件。見nio.sample.filecopy包下面的例子)以下是參考資料:

– 場景1: copy一個370m的檔案

– 場景2: 三個線程同時拷貝,每個線程拷貝一個370m檔案

java1.4的nio相關知識

nio.charset

字元編碼解碼:位元組碼本身隻是一些數字,放到正确的上下文中被正确被解析。向bytebuffer中存放資料時需要考慮字元集的編碼方式,讀取展示bytebuffer資料時涉及對字元集解碼。

java.nio.charset提供了編碼解碼一套解決方案。

以我們最常見的http請求為例,在請求的時候必須對請求進行正确的編碼。在得到響應時必須對響應進行正确的解碼。

以下代碼向baidu發一次請求,并擷取結果進行顯示。例子示範到了charset的使用。

例子2baidureader.java

非阻塞 io

關于非阻塞io将從何為阻塞、何為非阻塞、非阻塞原理和異步核心api幾個方面來了解。

何為阻塞?

一個常見的網絡io通訊流程如下:

java1.4的nio相關知識

圖3:網絡通訊基本過程

從該網絡通訊過程來了解一下何為阻塞:

在以上過程中若連接配接還沒到來,那麼accept會阻塞,程式運作到這裡不得不挂起,cpu轉而執行其他線程。

在以上過程中若資料還沒準備好,read會一樣也會阻塞。

阻塞式網絡io的特點:多線程處理多個連接配接。每個線程擁有自己的棧空間并且占用一些cpu時間。每個線程遇到外部為準備好的時候,都會阻塞掉。阻塞的結果就是會帶來大量的程序上下文切換。且大部分程序上下文切換可能是無意義的。比如假設一個線程監聽一個端口,一天隻會有幾次請求進來,但是該cpu不得不為該線程不斷做上下文切換嘗試,大部分的切換以阻塞告終。

何為非阻塞?

下面有個隐喻:

一輛從a開往b的公共汽車上,路上有很多點可能會有人下車。司機不知道哪些點會有哪些人會下車,對于需要下車的人,如何處理更好?

1.司機過程中定時詢問每個乘客是否到達目的地,若有人說到了,那麼司機停車,乘客下車。(類似阻塞式)

2.每個人告訴售票員自己的目的地,然後睡覺,司機隻和售票員互動,到了某個點由售票員通知乘客下車。 (類似非阻塞)

很顯然,每個人要到達某個目的地可以認為是一個線程,司機可以認為是cpu。在阻塞式裡面,每個線程需要不斷的輪詢,上下文切換,以達到找到目的地的結果。而在非阻塞方式裡,每個乘客(線程)都在睡覺(休眠),隻在真正外部環境準備好了才喚醒,這樣的喚醒肯定不會阻塞。

非阻塞的原理

把整個過程切換成小的任務,通過任務間協作完成。

由一個專門的線程來處理所有的io事件,并負責分發。

事件驅動機制:事件到的時候觸發,而不是同步的去監視事件。

線程通訊:線程之間通過wait,notify等方式通訊。保證每次上下文切換都是有意義的。減少無謂的程序切換。

以下是異步io的結構:

java1.4的nio相關知識

圖4:非阻塞基本原理

reactor就是上面隐喻的售票員角色。每個線程的處理流程大概都是讀取資料、解碼、計算處理、編碼、發送響應。

異步io核心api

selector

異步io的核心類,它能檢測一個或多個通道(channel)上的事件,并将事件分發出去。

使用一個select線程就能監聽多個通道上的事件,并基于事件驅動觸發相應的響應。而不需要為每個channel去配置設定一個線程。

selectionkey

包含了事件的狀态資訊和時間對應的通道的綁定。