摘要:緩沖區一個對象是固定數量的數據的容器。緩沖區的工作與通道緊密聯系。對于操作而言,從通道讀取的數據會按順序被散布稱為到多個緩沖區,將每個緩沖區填滿直至通道中的數據或者緩沖區的最大空間被消耗完。文件通道總是阻塞式的,因此不能被置于非阻塞模式。
簡介
從JDK1.4開始,java中提供一個種叫NIO(Non-Blocking IO)的IO處理機制。與以往的標準IO機制(BIO,Blocking IO)不同的是,新的機制把重點放在了如何縮短抽象與現實之間的距離上面。NIO中提出了一種新的抽象,NIO 彌補了原來的BIO的不足,它在標準 Java 代碼中提供了高速的、面向塊的I/O。
NIO的包括三個核心概念:緩沖區(Buffer)、通道(Channel)、選擇器(Selector)。思維導圖如下:
BIO與NIOBIO與NIO之間的共同點是他們都是同步的。而非異步的。
BIO是阻塞的(當前線程必須等待感興趣的事情發生), NIO是非柱塞的(事件選擇,感興趣的事情發生可以通知線程,而不必一直在哪等待);
BIO是面向流式的IO抽象(一次一個字節地處理數據), NIO是面向塊的IO抽象(每一個操作都在一步中產生或者消費一個數據塊(Buffer));
BIO的服務器實現模式為一個連接一個線程,NIO服務器實現模式為一個請求一個線程;
前提概念 緩沖區操作:緩沖區,以及緩沖區如何工作,是所有 I/O 的基礎。所謂“輸入/輸出”講的無非就是把數據移進或移出緩沖區。進程執行 I/O 操作,歸結起來,也就是向操作系統發出請求,讓它要么把緩沖區里的數據排干 (寫),要么用數據把緩沖區填滿(讀)。大致流程如圖:
注意圖中用戶空間和內核空間的概念。用戶空間是常規進程所在區域。JVM就是常規進程,駐守于用戶空間。用戶空間是非特權區域(比如,在該區域執行的代碼就不能直接訪問硬件設備)。內核空間是操作系統所在區域。內核代碼有特別的權力。
緩沖區操作發散/匯聚,許多操作系統能把組裝/分解過程進行得更加高效。
這樣用戶進程就不必多次執行系統調用(那樣做可能代價不菲),內核也可以優化數據的處理 過程,因為它已掌握待傳輸數據的全部信息。
虛擬內存所有現代操作系統都使用虛擬內存。虛擬內存意為程序中使用虛擬地址取代物理(硬件RAM)內存地址。這樣做好處頗多:
一個以上的虛擬地址可指向同一個物理內存地址;
虛擬內存空間可大于實際可用的硬件內存。
設備控制器不能通過 DMA 直接存儲到用戶空間,但通過利用上面 到的第一 項,則可以達到相同效果。把內核空間地址與用戶空間的虛擬地址映射到同一個物理地址,這樣, DMA 硬件(只能訪問物理內存地址)就可以填充對內核與用戶空間進程同時可見的緩沖區。
文件I/O文件I/O屬文件系統范疇,文件系統與磁盤迥然不同。磁盤把數據存在扇區上,通常一個扇區 512 字節。磁盤屬硬件設備,對何謂文件一無所知,它只是 供了一系列數據存取窗口。文件系統把一連串大小一致的數據塊組織到一起。有些塊存儲元信息,如空閑塊、目錄、索引等的映射,有些包含文件數據。
內存映射文件, 為了在內核空間 的文件系統頁與用戶空間的內存區之間移動數據,一次以上的拷貝操作幾乎總是免不了的。
文件鎖定機制, 允許一個進程阻止其他進程存取某文件,或限制其存取方式。通常的用途是控制共享信息的更新方式,或用于事務隔離。文件鎖有建議使用和強制使用之分。建議型文件鎖會向 出請求的進程 供當前鎖定信息,但 操作系統并不要求一定這樣做,而是由相關進程進行協調并關注鎖定信息。
流I/O并非所有 I/O 都像前幾節講的是面向塊的,也有流 I/O,其原理模仿了通道。I/O 字節流必須順序存取,常見的例子有 TTY(控制臺)設備、打印機端口和網絡連接。
流的傳輸一般(也不必然如此)比塊設備慢,經常用于間歇性輸入。
緩沖區一個Buffer對象是固定數量的數據的容器。其作用是一個存儲器,或者分段運輸區,在 這里數據可被存儲并在之后用于檢索。緩沖區的工作與通道緊密聯系。 Buffer的類層次圖:
緩沖區屬性Capacity: 容量, 緩沖區能夠容納的數據元素的最大數量。這一容量在緩沖區創建時被設定,并且永遠不能被改變;
Limit: 上界, 緩沖區的第一個不能被讀或寫的元素。或者說,緩沖區中現存元素的計數;
Position: 位置, 下一個要被讀或寫的元素的索引。位置會自動由相應的get()和put()函數更新;
Mark: 標記, 一個備忘位置。調用mark()來設定mark=postion。調用reset()設定position= mark。標記在設定前是未定義的(undefined)。
這四個屬性之間總是 循以下關系:0 <= mark <= position <= limit <= capacity。
直接緩沖區操作系統的在內存區域中進行I/O操作。這些內存區域,就操作系統方面而言,是相連的字節序列。于是,毫無疑問,只有字節緩沖區有資格參與I/O操作。也請回想一下操作系統會直接存取進程——在本例中是JVM進程的內存空間,以傳輸數據。這也意味著I/O操作的目標內存區域必須是連續的字節序列。在JVM中,字節數組可能不會在內存中連續存儲,或者無用存儲單元 集可能隨時對其進行移動。在Java中,數組是對象,而數據存儲在對象中的方式在不同的JVM實現中都各有不同。
直接緩沖區被用于與通道和固有 I/O 例程交 互。它們通過使用固有代碼來告知操作系統直接釋放或填充內存區域,對用于通道直接或原始 存取的內存區域中的字節元素的存儲盡了最大的努力。
通道通道用于在字節緩沖區和位于通道另一邊的實體(通常是一個文件或套接字)之間有效地傳輸數據。
通道可以形象地比喻為銀行出納窗口使用的動導管。您的薪水支票就是您要傳送的信息,載體(Carrier)就好比一個緩沖區。您先填充緩沖區(將您的薪水支票放到載體上),接著將緩沖“寫”到通道中(將載體進導管中),然后信息負載就被傳遞到通道另一邊的I/O服務(銀行出納員)。channel類的繼承關系如下:
Scatter/Gather通道提供了一種被稱為Scatter/Gather的重要新功能(有時也被稱為矢量 I/O)。Scatter/Gather是一個簡單卻強大的概念,它是指在多個緩沖區上實現一個簡單的I/O操作。對于一個write操作而言,數據是從幾個緩沖區按順序抽取(稱為gather)并沿著通道發送的。對于 read 操作而言,從通道讀取的數據會按順序被散布(稱為scatter)到多個緩沖區,將每個緩沖區填滿直至通道中的數據或者緩沖區的最大空間被消耗完。
Scatter的意思是分散,Gather的意思是聚集。我們注意到在上面的類層次結構圖中,除了ByteChannel外,各Channel類還都實現了兩個接口,分別是:
ScatteringByteChannel
GatheringByteChannel
public interface ScatteringByteChannel extends ReadableByteChannel { public long read (ByteBuffer [] dsts) throws IOException; public long read (ByteBuffer [] dsts, int offset, int length) throws IOException; } public interface GatheringByteChannel extends WritableByteChannel { public long write(ByteBuffer[] srcs) throws IOException; public long write(ByteBuffer[] srcs, int offset, int length) throws IOException; }文件通道
Channel根據IO服務的情況主要分為兩大類,按照《Java NIO》的描述,兩類IO分別是:file I/O 和 stream I/O。前者是針對文件讀寫操作的,而后者多是網絡通信相關的和Socket相關的。Channel分類也基本如此,和前者對應的FileChannel,以及與后者對應的SocketChannel等類對象。
文件通道總是阻塞式的,因此不能被置于非阻塞模式。
Socket通道新的socket通道類可以運行非阻塞模式并且是可選擇的。全部socket通道類包括DatagramChannel、SocketChannel和ServerSocketChannel
如上面的類圖,所有的socket通道都繼承于AbstractSelectableChannel。
請注意DatagramChannel和SocketChannel 實現定義讀和寫功能的接口而ServerSocketChannel不實現。ServerSocketChannel 負責監聽傳入的連接和創建新的SocketChannel對象,它本身從不傳輸數據。
ServerSocketChannel
讓我們從最簡單的ServerSocketChannel來開始對socket通道類的討論。以下是ServerSocketChannel的完整API:
public abstract class ServerSocketChannel extends AbstractSelectableChannel{ public static ServerSocketChannel open() throws IOException public abstract ServerSocket socket(); public abstract ServerSocket accept() throws IOException; public final int validOps() }
ServerSocketChannel是一個基于通道的socket監聽器。它同我們所熟悉的java.net.ServerSocket執行相同的基本任務,不過它增加了通道語義,因此能夠在非阻塞模式下運行。
SocketChannel
SocketChannel,它是使用最多的socket通道類,接口如下:
public abstract class SocketChannel extends AbstractSelectableChannel implements ByteChannel, ScatteringByteChannel,GatheringByteChannel{ public static SocketChannel open() throws IOException public static SocketChannel open (InetSocketAddress remote) throws IOException public abstract Socket socket(); public abstract boolean connect (SocketAddress remote) throws IOException; public abstract boolean isConnectionPending(); public abstract boolean finishConnect() throws IOException; public abstract boolean isConnected(); public final int validOps() }
socket 和 SocketChannel 類封裝點對點、有序的網絡連接,類似于我們所熟知并喜愛的 TCP/IP 網絡連接。SocketChannel 演 戶端發起同一個監聽服務器的連接。直到連接成功,它才能 到 數據并且只會從連接到的地址接 。
DatagramChannel
正如SocketChannel對應Socket, ServerSocketChannel對應ServerSocket,每一個DatagramChannel對象也有一個關聯的DatagramSocket對象。不過原命名模式在此并未適用: DatagramSocketChannel顯得有點笨拙,因此采用了簡潔的DatagramChannel名稱。
public abstract class DatagramChannel extends AbstractSelectableChannel implements ByteChannel, ScatteringByteChannel, GatheringByteChannel{ public static DatagramChannel open( ) throws IOException public abstract DatagramSocket socket( ); public abstract DatagramChannel connect (SocketAddress remote) throws IOException; public abstract boolean isConnected( ); public abstract DatagramChannel disconnect( ) throws IOException; public abstract SocketAddress receive (ByteBuffer dst) throws IOException; public abstract int send (ByteBuffer src, SocketAddress target) public abstract int read (ByteBuffer dst) throws IOException; public abstract long read (ByteBuffer [] dsts) throws IOException; public abstract long read (ByteBuffer [] dsts, int offset,int length) throws IOException; public abstract int write (ByteBuffer src) throws IOException; public abstract long write(ByteBuffer[] srcs) throws IOException; public abstract long write(ByteBuffer[] srcs, int offset,int length) throws IOException; }選擇器
選擇器提供選擇執行已經就緒的任務的能力,這使得多元I/O成為可能。選擇器類管理著一個被注冊的通道集合的信息和它們的就緒狀態。通道是和選擇器一起被注冊的,并且使用選擇器來更新通道的就緒狀態。當這么做的時候,可以選擇將被激發的線程掛起,直
到有就緒的的通道。
將文件內容讀取到一個字符串中
public static String readFileToString(String filePath, Charset charset) throws IOException { try(FileInputStream in = new FileInputStream(filePath); FileChannel channel = in.getChannel() ){ long fileSize = channel.size(); int bufferSize = 1024; if (fileSize < 1024){ bufferSize = (int)fileSize; } StringBuilder builder = new StringBuilder((int)(fileSize/2)); ByteBuffer byteBuffer = ByteBuffer.allocate(bufferSize); CharBuffer charBuffer = CharBuffer.allocate(bufferSize/2); CharsetDecoder decoder = charset.newDecoder(); while (channel.read(byteBuffer) != -1) { byteBuffer.flip(); CoderResult rel; do{ rel = decoder.decode(byteBuffer,charBuffer,false); charBuffer.flip(); builder.append(charBuffer.array(),0,charBuffer.limit()); charBuffer.clear(); }while (rel.isOverflow()); byteBuffer.compact(); } byteBuffer.flip(); decoder.decode(byteBuffer,charBuffer,true); charBuffer.flip(); builder.append(charBuffer.array(),0,charBuffer.limit()); charBuffer.clear(); return builder.toString(); } }文件寫入
將一串字符串寫入文件中
public static long writeStringToFile(String filePath, String content, Charset charset) throws IOException { long writeSize = 0; try(FileOutputStream out = new FileOutputStream(filePath); FileChannel channel = out.getChannel() ){ ByteBuffer buffer = ByteBuffer.wrap(content.getBytes(charset)); while (buffer.hasRemaining()){ writeSize += channel.write(buffer); } channel.force(false); } return writeSize; }簡單的ServerSocketChannel使用
只是一個簡單的ServerSocketChannel
public static void main(String[] args) throws IOException { ServerSocketChannel ssc = ServerSocketChannel.open(); ssc.socket().bind(new InetSocketAddress(8888)); // ssc.configureBlocking(false); String hello_string = "hello rudy!"; ByteBuffer buffer = ByteBuffer.wrap(hello_string.getBytes()); while (true){ // System.out.println("wait for connections"); SocketChannel clientSocket = ssc.accept(); if (null == clientSocket){ try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } }else{ System.out.println(String.format("incomimg connection from: %s",clientSocket.getRemoteAddress())); buffer.rewind(); clientSocket.write(buffer); clientSocket.close(); } } }簡單的SocketChannel使用
public static void main(String[] args) throws IOException { SocketChannel channel = SocketChannel.open(); channel.connect(new InetSocketAddress("localhost",8888)); ByteBuffer buffer = ByteBuffer.allocate(100); CharBuffer charBuffer = CharBuffer.allocate(100); CharsetDecoder decoder = Charset.defaultCharset().newDecoder(); channel.read(buffer); buffer.flip(); decoder.decode(buffer,charBuffer,false); charBuffer.flip(); while (charBuffer.hasRemaining()){ System.out.println(charBuffer.get()); } channel.close(); }Selector使用,I/O多路復用
較為綜合的例子
public static void main(String[] args) throws IOException { Selector selector = Selector.open(); ServerSocketChannel channel = ServerSocketChannel.open(); channel.bind(new InetSocketAddress("localhost",8888)); channel.configureBlocking(false); SelectionKey selectionKey = channel.register(selector,SelectionKey.OP_ACCEPT); ByteBuffer buffer = ByteBuffer.allocate(1024); CharBuffer charBuffer = CharBuffer.allocate(1024); CharsetDecoder decoder = Charset.defaultCharset().newDecoder(); while (true){ int readyNum = selector.select(); if (readyNum <= 0){ continue; } SetUDP服務端readyKey = selector.selectedKeys(); for (SelectionKey tempKey : readyKey){ if (tempKey.isAcceptable()){ ServerSocketChannel tempChannel = (ServerSocketChannel) tempKey.channel(); SocketChannel clientChannel = tempChannel.accept(); if (null != clientChannel){ System.out.println("one connection:" + clientChannel.getRemoteAddress()); clientChannel.configureBlocking(false); clientChannel.register(selector,SelectionKey.OP_READ); } } if(tempKey.isReadable()){ SocketChannel tempChannel = (SocketChannel) tempKey.channel(); tempChannel.read(buffer); buffer.flip(); decoder.decode(buffer,charBuffer,false); charBuffer.flip(); String getData = new String(charBuffer.array(),0,charBuffer.limit()); System.out.println(tempChannel.getRemoteAddress() + ":" + getData); buffer.clear(); charBuffer.clear(); tempChannel.write(ByteBuffer.allocate(0)); if (getData.equalsIgnoreCase("exit")){ tempChannel.close(); } } if (tempKey.isWritable()){ SocketChannel tempChannel = (SocketChannel) tempKey.channel(); // System.out.println(tempChannel.getRemoteAddress() + ": read"); } readyKey.remove(tempKey); } } }
public static void main(String[] args) throws IOException { DatagramChannel channel = DatagramChannel.open(); channel.bind(new InetSocketAddress("localhost",8888)); ByteBuffer buffer = ByteBuffer.allocate(100); CharBuffer charBuffer = CharBuffer.allocate(100); CharsetDecoder decoder = Charset.defaultCharset().newDecoder(); while (true){ buffer.clear(); charBuffer.clear(); SocketAddress remoteAddress = channel.receive(buffer); buffer.flip(); decoder.decode(buffer,charBuffer,false); charBuffer.flip(); System.out.println( remoteAddress +":" + new String(charBuffer.array(),0, charBuffer.limit())); } }UDP客戶端
public static void main(String[] args) throws IOException { DatagramChannel channel = DatagramChannel.open(); String sendData = "哈哈哈 hello rudy!"; ByteBuffer buffer = ByteBuffer.wrap(sendData.getBytes()); channel.send(buffer, new InetSocketAddress("localhost",8888)); System.out.println("send end!"); }他山之石
關于異步,同步,阻塞與非阻塞: http://blog.csdn.net/brainkick/article/details/9312407
Java NIO系列教程: http://ifeve.com/java-nio-all/
官網java7 api文檔: http://docs.oracle.com/javase/7/docs/api/
java NIO詳解: http://zalezone.cn/2014/09/17/NIO%E7%B2%BE%E7%B2%B9/
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/66273.html
摘要:線程之間的切換對于操作系統來說是昂貴的。因此,單線程可以監視多個通道中的數據。當方法返回后,線程可以處理這些事件。 一 NIO簡介 Java NIO 是 java 1.4 之后新出的一套IO接口,這里的的新是相對于原有標準的Java IO和Java Networking接口。NIO提供了一種完全不同的操作方式。 NIO中的N可以理解為Non-blocking,不單純是New。 它支持面...
摘要:從通道進行數據寫入創建一個緩沖區,填充數據,并要求通道寫入數據。三之通道主要內容通道介紹通常來說中的所有都是從通道開始的。從中選擇選擇器維護注冊過的通道的集合,并且這種注冊關系都被封裝在當中停止選擇的方法方法和方法。 由于內容比較多,我下面放的一部分是我更新在我的微信公眾號上的鏈接,微信排版比較好看,更加利于閱讀。每一篇文章下面我都把文章的主要內容給列出來了,便于大家學習與回顧。 Ja...
摘要:上篇說了最基礎的五種模型,相信大家對相關的概念應該有了一定的了解,這篇文章主要講講基于多路復用的。 上篇說了最基礎的五種IO模型,相信大家對IO相關的概念應該有了一定的了解,這篇文章主要講講基于多路復用IO的Java NIO。 背景 Java誕生至今,有好多種IO模型,從最早的Java IO到后來的Java NIO以及最新的Java AIO,每種IO模型都有它自己的特點,詳情請看我的上...
摘要:的選擇器允許單個線程監視多個輸入通道。一旦執行的線程已經超過讀取代碼中的某個數據片段,該線程就不會在數據中向后移動通常不會。 1、引言 很多初涉網絡編程的程序員,在研究Java NIO(即異步IO)和經典IO(也就是常說的阻塞式IO)的API時,很快就會發現一個問題:我什么時候應該使用經典IO,什么時候應該使用NIO? 在本文中,將嘗試用簡明扼要的文字,闡明Java NIO和經典IO之...
摘要:面向流面向緩沖阻塞非阻塞無選擇器面向流與面向緩沖和之間第一個最大的區別是,是面向流的,是面向緩沖區的。換句話說,如果緩沖區準備好被處理,那么表示緩沖區滿了。方法掃描緩沖區,但必須保持在方法被調用之前狀態相同。 當學習了Java NIO和IO的API后,一個問題馬上涌入腦海: 我應該何時使用IO,何時使用NIO呢?在本文中,我會盡量清晰地解析Java NIO和IO的差異、它們的使用場景,...
摘要:而我們現在都已經發布了,的都不知道,這有點說不過去了。而對一個的讀寫也會有響應的描述符,稱為文件描述符,描述符就是一個數字,指向內核中的一個結構體文件路徑,數據區等一些屬性。 前言 只有光頭才能變強 回顧前面: 給女朋友講解什么是代理模式 包裝模式就是這么簡單啦 本來我預想是先來回顧一下傳統的IO模式的,將傳統的IO模式的相關類理清楚(因為IO的類很多)。 但是,發現在整理的過程已...
閱讀 848·2021-11-25 09:43
閱讀 3681·2021-11-19 09:40
閱讀 882·2021-09-29 09:34
閱讀 1784·2021-09-26 10:21
閱讀 870·2021-09-22 15:24
閱讀 4188·2021-09-22 15:08
閱讀 3266·2021-09-07 09:58
閱讀 2657·2019-08-30 15:55