410 likes | 511 Views
New I/O Features of JDK1.4. Try NCCU Computer Center May 29,2002. 參考資料. [1]David Flanagan, “ Top Ten Cool New Features of Java 2 SE 1.4 ” ,onjava.com,Mar 2002 [2]Mark Reinhold, ” JSR51:New I/O APIs for the Java Platform ” ,http://jcp.org/jsr/detail/051.prt,May 2002
E N D
New I/O Features of JDK1.4 Try NCCU Computer Center May 29,2002
參考資料 • [1]David Flanagan,“Top Ten Cool New Features of Java 2 SE 1.4” ,onjava.com,Mar 2002 • [2]Mark Reinhold,”JSR51:New I/O APIs for the Java Platform”,http://jcp.org/jsr/detail/051.prt,May 2002 • [3]Java 2 Standard Editon Java API Document, http://java.sun.com/j2se/1.4/docs/api/index.html • [4]John Zukowski,”New I/O Functionality for Java 2 Standard Edition 1.4”,Sun,Oct 2001 • [5]Source Code of JDK 1.4.0 • [6]Tim Burns,”Non-Blocking Socket I/O in JDK1.4”,Owl Mountain Software ,LLC,Dec 2001 • [7]Elliotte Rusty Harold,”Java Network Programming”,O’reilly,Dec 1997
Presentation Conventions • 解說內文儘可能完全用中文來表達。 • 專業術語則完全用原文(from jjhou)。 • 能用圖表達的就不用文字。
Presentation Agenda • 簡介 • NIO API的新功能 • Buffer家族 • Channel家族 • File System I/O (File Channel) • Selector觀念 • Network I/O (Socket Channel)
簡介 • 規格來源為JSR51-New I/O APIs for the Java Platform。 • 初版通過:2000/1/20。 • 為何制定新的API? • 回應外界對I/O效能的批評。 • 回應外界要求的 (原來OS的native api有、Java platform卻沒有)新功能。
簡介(2) • JDK1.4十大新功能排名,I/O相關分佔第二(regular expression)、三(non-blocking)及第四名(file channel)。 • 舊I/O API目前並沒有deprecated的計畫。但JSP51中還是建議開發人員改用新的API來開發。
新的I/O 需求(JSR 51) • 新的API必須要有擴充性(Scalable),且支援polling(輪詢)及asynchronous request(如Non-blocking)。 • 可以將File System上的檔案map到程式可直接存取的記憶體位址上。這項功能必須由下層的作業系統直接支援。 • 更快速的Binary I/O及Character I/O API。 • Character I/O API要能支援regular expressions。
NIO API的新功能 • 效能提升 • 在很多API的實作過程很有技巧地使用native code(nio.dll)所以nio的實作會依平台的不同而不同,JDK的提供者負擔變多。 • 犧牲部份的簡單性換取效能大幅的進展。 • Channel-Buffer互相配合的運作機制 • 舊io:stream-oriented,新io:block-oriented。 • DirectBuffer • 直接在System Buffer上與底層os互動。
新的I/O運作機制 • 新的I/O運作機制由「Channel-Buffer」合作完成I/O動作。 • InputStream/OutputStream是單向,Channel是雙向。 • Stream每次讀寫1byte,Channel每次讀寫「1 chunk of data」 • 這「1 chunk of data」指的就是Buffer! • 由Channel進行的I/O,一定都是透過Buffer。
NIO 與 IO的相容性 • 其實部份舊I/O的類別已被重新改寫成底層使用Channel機制。如 • FileInputStream/FileOutputStream/RandomAccessFile • Socket/ServerSocket/DatagramSocket 以上這些類別都有getChannel()方法。 • 所以就算是用舊的API,也有可能不知不覺已用到了新的API功能。
Buffer家族 • Buffer類別是學習nio的基礎。 • Buffer是一個新類別,直接extend Object。 • 以Buffer為祖先,extend下來的有ByteBuffer、CharBuffer、IntBuffer、…..。 • 這些xxxBuffer之中,xxx都是「基本型別」,所以像java.lang.StringBuffer和目前我們要討論的Buffer家族一點關係都沒有。 • 每一個基本類別都有一個xxxBuffer與之對應。 • 要存取Channel,一定要透過Buffer。
Buffer是什麼 • Buffer是一組資料形成的序列,這些序列都是由基本型別所構成。 (JavaDoc) • Buffer = 定義了屬於Buffer的特徵及行為 • ByteBuffer = byte陣列 +屬於Buffer的特徵及行為 + 專屬ByteBuffer的特徵及行為 • CharBuffer = char陣列 +屬於Buffer的特徵及行為 + 專屬CharBuffer的特徵及行為 • 其它類別以此類推。
建構Buffer實體的方法 • 向系統要求5bytes的記憶體。 • ByteBuffer buf = ByteBuffer.allocate(5); • ByteBuffer.allocate(5)做了什麼事? • return new HeapByteBuffer(5, 5); • 向系統要求5bytes的DirectBuffer。 • ByteBuffer buf = ByteBuffer.allocateDirect(5); • 什麼是DirectBuffer??
DirectByteBuffer • DirectByteBuffer同時extend 自MappedByteBuffer及implement DirectBuffer這個interface。 • 呼叫ByteBuffer.allocateDirect(5)時發生了什麼事? • Return new DirectByteBuffer(5) • 呼叫unsafe.allocateMemory() • unsafe.allocateMemory()實作在JVM中,其實就是呼叫malloc()來配置記憶體。 • unsafe.setMemory(),初始化記憶體。
DirectByteBuffer(2) • 所以allocateDirect是透過JNI直接呼叫作業系統的system call來配置記憶體。 • Unsafe類別實作在JVM中,包裝了一系列低階、不安全的method,是一個final 類別。 • 由於必須透過JNI,所以呼叫次數太多反而導致效能降低。JavaDoc上的建議是那種一次allocate大一個,然後會存在很久的Buffer用DirectBuffer比較划算。
Channel觀念 (以下摘自Channel之JavaDoc) • I/O 動作的核心(nexus of I/O)。 • 一個 channel 代表一個連到特定裝置的連結(connection),例如file或 a network socket。 • Channel只有二種狀態, open 或 closed。 • Channel closed之後,去對這個Channel做任何I/O動作都會導致ClosedChannelException。
File System I/O 的流程 • 利用FileInputStream讀入檔案。 • 利用FileInputStream的getChannel()方法得到一個FileChannel。 • 利用FileChannel的read()方法將檔案由channel讀到buffer中。 • 大部份的情況下buffer比File size小,所以要讀很多次。 • 再依據一般操作Buffer的方式來操作即可。 • 最後記得呼叫channel的close()。
在螢幕上印出一個File的content String fileName = “data.txt"; FileInputStream fis = new FileInputStream(fileName); FileChannel channel = fis.getChannel(); ByteBuffer buf = ByteBuffer.allocate(10); while(channel.read(buf)>0) { buf.flip(); byte[] b = new byte[buf.limit()]; buf.get(b); System.out.print(new String(b)); buf.clear(); } channel.close();
File System I/O 的流程(map) • 利用FileInputStream讀入檔案。 • 利用FileInputStream的getChannel()方法得到一個FileChannel。 • 利用FileChannel的map()方法將檔案map到記憶體中,所得結果存入MappedByteBuffer中。 • 再依據一般操作Buffer的方式來操作即可。 • 最後記得呼叫channel的close()。
在螢幕上印出一個File的content(map) String fileName = "data.txt"; FileInputStream fis = new FileInputStream(fileName); FileChannel channel = fis.getChannel(); MappedByteBuffer buf = channel.map(FileChannel.MapMode.READ_ONLY,0,channel.size()); byte[] b = new byte[(int)channel.size()]; buf.get(b); System.out.print(new String(b)); channel.close();
File locking • 為了避免multithread運作時的檔案存取問題,可以針對File做Lock的機制。 • 可以只針對某個檔案的特定區段做Lock • 比如我們要從檔案的第10個字元lock到第30個字元,一共lock 20個字的區段,就可以寫成如下 • FileLock lock = somFileChannel.lock(10,20,true); • 其中第三個參數如果是true代表是shared lock,如果是false代表exclusive lock。
深入FileChannel的map() • MappedByteBuffer map(FileChannel.MapMode mode, long position, long size) • map方法中做了些什麼? • 檢查所傳入mode、position、size的正確性。 • 透過windows API取得SYSTEMINFO結構中的allocationGranularity • 計算pagePosition = position%allocationGranularity
深入FileChannel的map() • 根據pagePosition調整所要map的範圍 • (new postion = position - pagePosition ) • (new size = size + pagePosition ) • 調整後呼叫Native call -- map0() • Win API:CreateFileMapping()—得到Mapping Object 之Handle • Win API:MapViewOfFile()—得到Mapping Object之Address • 得到位址後,呼叫Util.newMappedByteBuffer(),其實它就是直接幫我們new了一個DirectByteBuffer。 • 接下來把這個DirectByteBuffer回傳就大功告成!!
Non-Blocking I/O的運作方式 • 送出資料後,不用一直等,可以先做別的事。有回應時系統會藉由Selector機制通知。 • 首先要利用Channel的configureBlocking()方法,告訴系統我們要用NonBlocking的方式。 • 再來向Selector註冊Channel及我們有興趣的事件。當事件發生時,Selector就會通知我們,並傳回一組SelectionKey(Set類別)。 • 這些key有一個method叫channel(),我們從這個method就可以再度取我們剛註冊的channel,並加以處理了。
while (selector.select(500) > 0) { Set readyKeys = selector.selectedKeys(); //取得SelectionKeys Iterator readyItor = readyKeys.iterator(); while (readyItor.hasNext()) { SelectionKey key = (SelectionKey)readyItor.next(); readyItor.remove();//務必記得這一行 SocketChannel keyChannel = (SocketChannel)key.channel(); if (key.isConnectable()) {//在這裏處理OP_CONNECT事件 } else if (key.isReadable()) {//在這裏處理OP_READ事件 } } }
Selector觀念 • Selector在Non-Blocking中扮演關鍵性的角色。 • (參考Channel類別圖)SelectableChannel的子類別都有一個method叫register()。透過這個method,可以向Selector註冊我們有興趣的事件。
Selector和SelectorProvider • Selector本身是一個abstract類別。 • 在實作上,為了可以支援多種不同的Selector,所以在這裏使用Factory Pattern。 • SelectorProvider = Creator。 • Selector = Product。 • PollSelectorImpl = ConcreteProduct。 • PollSelectorProvider = ConcreteCreator。
觀察Selector的建造過程 • Selector selector = Selector.open(); • open()方法內部: • SelectorProvider.provider().openSelector(); • SelectorProvider.provider()方法運作方式。 • 檢查系統參數之中是否有java.nio.channels.spi.SelectorProvider = ????這個參數。 • 如果有,就load該class來當做Selector。 • 如果沒有就load「DefaultSelectorProvider」,其實就是「PollSelectorProvider」。
Selector的selector方法探討 • 如何偵測有事件發生 while(selector.select(500)>0) { ......(do something)... } //註:selector是一個Selector的instance • selector.select(500)的傳回值大於0就是有我們註冊的事發生了。 • select()的傳回值是什麼意義? • select(500)中的500又是什麼意義呢?
Selector的selector方法探討(2) • select()的傳回值代表的是傳回所得到SelectionKey的數目。 • select()中的參數代表timeout。
追蹤PollSelector的select()方法 • pollSelector.select(500) • doSelect(500) • pollWrapper.poll() //Native call • (Win API) WSAWaitForMultipleEvent() • 追蹤後發現這個數字被原封不動地傳到底層的Win API,所以這個500的數字的意義等同於WSAWaitForMultipleEvent()的第四個參數。 • 也就是說timeout後,若wait的對象(channel)沒有回應,這個Win API就傳回WSA_WAIT_TIMEOUT。 • 在Java上來看,它會傳回0,並直接進行下一行。
如何設定no timeout • 前面說我們的timeout參數會被原封不動傳到底層,這裏有一項例外,就是當我們的timeout是傳0時。 • 它會將0改成-1,再呼叫doSelect() • 傳到pollWrapper.poll()時,就會判斷 if (timeout < 0) { timeout = WSA_INFINITE; } • 所以傳0代表的意義是永遠沒有timeout,一直等下去。 • 也可以直接呼叫select(),它其實就是等同於select(0)
Summary • Java.nio這個package最引人注目的新功能就是號稱效能上的改善及non-blocking的支援。 • 另外還有對regular expression的支援未介紹。 • 針對NIO,SUN所提供的文件不夠多,java doc上的解釋也不夠詳盡,導致developer被迫要讀source code。 • 為了避免日後程式碼所用的api被deprecate,建議日後還是用nio的方式寫I/O程式。