| IO | NIO |
面向流 | 面向緩沖 |
阻塞IO | 非阻塞IO |
無 | 選擇器 |
在整個Java的心I/O中,所以操作都是以緩沖區進行的,使操作的性能大大提高。
操作在Buffer中存在一系列的狀態變量,這狀態變量隨著寫入或讀取都可能會被概念,在緩沖區開元使用是三個值表示緩沖區的狀態。
創建緩沖區:
import java.nio.IntBuffer ;public class IntBufferDemo{ public static void main(String args[]){ IntBuffer buf = IntBuffer.allocate(10) ; // 準備出10個大小的緩沖區 System.out.ip() ; // 重設緩沖區 // postion = 0 ,limit = 原本position System.out.print("3、準備輸出數據時的position、limit和capacity:") ; System.out.println("position = " + buf.position() + ",limit = " + buf.limit() + ",capacty = " + buf.capacity()) ; System.out.print("緩沖區中的內容:") ; while(buf.hasRemaining()){ int x = buf.get() ; System.out.print(x + "、") ; } }}如果創建了緩沖區,則JVM可直接對其執行本機的IO操作
import java.nio.ByteBuffer ;public class ByteBufferDemo{ public static void main(String args[]){ ByteBuffer buf = ByteBuffer.allocateDirect(10) ; // 準備出10個大小的緩沖區 byte temp[] = {1,3,5,7,9} ; // 設置內容 buf.put(temp) ; // 設置一組內容 buf.flip() ; System.out.print("主緩沖區中的內容:") ; while(buf.hasRemaining()){ int x = buf.get() ; System.out.print(x + "、") ; } }}通道(Channel)Java NIO的非阻塞模式,使一個線程從某通道發送請求讀取數據,但是它僅能得到目前可用的數據,如果目前沒有數據可用時,就什么都不會獲取。而不是保持線程阻塞,所以直至數據變的可以讀取之前,該線程可以繼續做其他的事情。 非阻塞寫也是如此。一個線程請求寫入一些數據到某通道,但不需要等待它完全寫入,這個線程同時可以去做別的事情。 線程通常將非阻塞IO的空閑時間用于在其它通道上執行IO操作,所以一個單獨的線程現在可以管理多個輸入和輸出通道(channel)。
Java NIO的通道類似流,但又有些不同:
正如上面所說,從通道讀取數據到緩沖區,從緩沖區寫入數據到通道。

這些是Java NIO中最重要的通道的實現:
FileChannel 從文件中讀寫數據。
DatagramChannel 能通過UDP讀寫網絡中的數據。
SocketChannel 能通過TCP讀寫網絡中的數據。
ServerSocketChannel可以監聽新進來的TCP連接,像Web服務器那樣。對每一個新進來的連接都會創建一個SocketChannel。
通過通道可以完成雙向的輸入和輸出操作。在通道還有一種方式稱為內存映射
幾種讀入的方式的比較
RandomaccessFile 較慢FileInputStream 較慢緩沖讀取 速度較快內存映射 速度最快FileChannel內存映射實例
import java.nio.ByteBuffer ;import java.nio.MappedByteBuffer ;import java.nio.channels.FileChannel ;import java.io.File ;import java.io.FileOutputStream ;import java.io.FileInputStream ;public class FileChannelDemo03{ public static void main(String args[]) throws Exception{ File file = new File("d:" + File.separator + "oumyye.txt") ; FileInputStream input = null ; input = new FileInputStream(file) ; FileChannel fin = null ; // 定義輸入的通道 fin = input.getChannel() ; // 得到輸入的通道 MappedByteBuffer mbb = null ; mbb = fin.map(FileChannel.MapMode.READ_ONLY,0,file.length()) ; byte data[] = new byte[(int)file.length()] ; // 開辟空間接收內容 int foot = 0 ; while(mbb.hasRemaining()){ data[foot++] = mbb.get() ; // 讀取數據 } System.out.println(new String(data)) ; // 輸出內容 fin.close() ; input.close() ; }}操作以上代碼的時候,執行的是寫入操作則可能是非常危險的,因為僅僅只是改變數組中的單個元素這種簡單的操作,就可能直接修改磁盤上的文件,因為修改數據與數據保存在磁盤上是一樣的。
選擇器(Selectors)Selector(選擇器)是Java NIO中能夠檢測一到多個NIO通道,并能夠知曉通道是否為諸如讀寫事件做好準備的組件。這樣,一個單獨的線程可以管理多個channel,從而管理多個網絡連接。
為什么使用Selector?僅用單個線程來處理多個Channels的好處是,只需要更少的線程來處理通道。事實上,可以只用一個線程處理所有的通道。對于操作系統來說,線程之間上下文切換的開銷很大,而且每個線程都要占用系統的一些資源(如內存)。因此,使用的線程越少越好。
但是,需要記住,現代的操作系統和CPU在多任務方面表現的越來越好,所以多線程的開銷隨著時間的推移,變得越來越小了。實際上,如果一個CPU有多個內核,不使用多任務可能是在浪費CPU能力。不管怎么說,關于那種設計的討論應該放在另一篇不同的文章中。在這里,只要知道使用Selector能夠處理多個通道就足夠了。
要點使用Selector可以構建一個非阻塞的網絡服務。
在新IO實現網絡程序需要依靠ServerSocketChannel類與SocketChannel
Selector實例下面使用Selector完成一個簡單的服務器的操作,服務器可以同時在多個端口進行監聽,此服務器的主要功能是返回當前時間。
import java.net.InetSocketAddress ;import java.net.ServerSocket ;import java.util.Set ;import java.util.Iterator ;import java.util.Date ;import java.nio.channels.ServerSocketChannel ;import java.nio.ByteBuffer ;import java.nio.channels.SocketChannel ;import java.nio.channels.Selector ;import java.nio.channels.SelectionKey ;public class DateServer{ public static void main(String args[]) throws Exception { int ports[] = {8000,8001,8002,8003,8005,8006} ; // 表示五個監聽端口 Selector selector = Selector.open() ; // 通過open()方法找到Selector for(int i=0;i<ports.length;i++){ ServerSocketChannel initSer = null ; initSer = ServerSocketChannel.open() ; // 打開服務器的通道 initSer.configureBlocking(false) ; // 服務器配置為非阻塞 ServerSocket initSock = initSer.socket() ; InetSocketAddress address = null ; address = new InetSocketAddress(ports[i]) ; // 實例化綁定地址 initSock.bind(address) ; // 進行服務的綁定 initSer.register(selector,SelectionKey.OP_ACCEPT) ; // 等待連接 System.out.println("服務器運行,在" + ports[i] + "端口監聽。") ; } // 要接收全部生成的key,并通過連接進行判斷是否獲取客戶端的輸出 int keysAdd = 0 ; while((keysAdd=selector.select())>0){ // 選擇一組鍵,并且相應的通道已經準備就緒 Set<SelectionKey> selectedKeys = selector.selectedKeys() ;// 取出全部生成的key Iterator<SelectionKey> iter = selectedKeys.iterator() ; while(iter.hasNext()){ SelectionKey key = iter.next() ; // 取出每一個key if(key.isAcceptable()){ ServerSocketChannel server = (ServerSocketChannel)key.channel() ; SocketChannel client = server.accept() ; // 接收新連接 client.configureBlocking(false) ;// 配置為非阻塞 ByteBuffer outBuf = ByteBuffer.allocateDirect(1024) ; // outBuf.put(("當前的時間為:" + new Date()).getBytes()) ; // 向緩沖區中設置內容 outBuf.flip() ; client.write(outBuf) ; // 輸出內容 client.close() ; // 關閉 } } selectedKeys.clear() ; // 清楚全部的key } }}服務器完成之后可以使用Telnet命令完成,這樣就完成了一個一部的操作服務器。
新聞熱點
疑難解答