摘要:緩沖區(qū)一個對象是固定數(shù)量的數(shù)據(jù)的容器。緩沖區(qū)的工作與通道緊密聯(lián)系。對于操作而言,從通道讀取的數(shù)據(jù)會按順序被散布稱為到多個緩沖區(qū),將每個緩沖區(qū)填滿直至通道中的數(shù)據(jù)或者緩沖區(qū)的最大空間被消耗完。文件通道總是阻塞式的,因此不能被置于非阻塞模式。
簡介
從JDK1.4開始,java中提供一個種叫NIO(Non-Blocking IO)的IO處理機制。與以往的標準IO機制(BIO,Blocking IO)不同的是,新的機制把重點放在了如何縮短抽象與現(xiàn)實之間的距離上面。NIO中提出了一種新的抽象,NIO 彌補了原來的BIO的不足,它在標準 Java 代碼中提供了高速的、面向塊的I/O。
NIO的包括三個核心概念:緩沖區(qū)(Buffer)、通道(Channel)、選擇器(Selector)。思維導圖如下:
BIO與NIOBIO與NIO之間的共同點是他們都是同步的。而非異步的。
BIO是阻塞的(當前線程必須等待感興趣的事情發(fā)生), NIO是非柱塞的(事件選擇,感興趣的事情發(fā)生可以通知線程,而不必一直在哪等待);
BIO是面向流式的IO抽象(一次一個字節(jié)地處理數(shù)據(jù)), NIO是面向塊的IO抽象(每一個操作都在一步中產(chǎn)生或者消費一個數(shù)據(jù)塊(Buffer));
BIO的服務(wù)器實現(xiàn)模式為一個連接一個線程,NIO服務(wù)器實現(xiàn)模式為一個請求一個線程;
前提概念 緩沖區(qū)操作:緩沖區(qū),以及緩沖區(qū)如何工作,是所有 I/O 的基礎(chǔ)。所謂“輸入/輸出”講的無非就是把數(shù)據(jù)移進或移出緩沖區(qū)。進程執(zhí)行 I/O 操作,歸結(jié)起來,也就是向操作系統(tǒng)發(fā)出請求,讓它要么把緩沖區(qū)里的數(shù)據(jù)排干 (寫),要么用數(shù)據(jù)把緩沖區(qū)填滿(讀)。大致流程如圖:
注意圖中用戶空間和內(nèi)核空間的概念。用戶空間是常規(guī)進程所在區(qū)域。JVM就是常規(guī)進程,駐守于用戶空間。用戶空間是非特權(quán)區(qū)域(比如,在該區(qū)域執(zhí)行的代碼就不能直接訪問硬件設(shè)備)。內(nèi)核空間是操作系統(tǒng)所在區(qū)域。內(nèi)核代碼有特別的權(quán)力。
緩沖區(qū)操作發(fā)散/匯聚,許多操作系統(tǒng)能把組裝/分解過程進行得更加高效。
這樣用戶進程就不必多次執(zhí)行系統(tǒng)調(diào)用(那樣做可能代價不菲),內(nèi)核也可以優(yōu)化數(shù)據(jù)的處理 過程,因為它已掌握待傳輸數(shù)據(jù)的全部信息。
虛擬內(nèi)存所有現(xiàn)代操作系統(tǒng)都使用虛擬內(nèi)存。虛擬內(nèi)存意為程序中使用虛擬地址取代物理(硬件RAM)內(nèi)存地址。這樣做好處頗多:
一個以上的虛擬地址可指向同一個物理內(nèi)存地址;
虛擬內(nèi)存空間可大于實際可用的硬件內(nèi)存。
設(shè)備控制器不能通過 DMA 直接存儲到用戶空間,但通過利用上面 到的第一 項,則可以達到相同效果。把內(nèi)核空間地址與用戶空間的虛擬地址映射到同一個物理地址,這樣, DMA 硬件(只能訪問物理內(nèi)存地址)就可以填充對內(nèi)核與用戶空間進程同時可見的緩沖區(qū)。
文件I/O文件I/O屬文件系統(tǒng)范疇,文件系統(tǒng)與磁盤迥然不同。磁盤把數(shù)據(jù)存在扇區(qū)上,通常一個扇區(qū) 512 字節(jié)。磁盤屬硬件設(shè)備,對何謂文件一無所知,它只是 供了一系列數(shù)據(jù)存取窗口。文件系統(tǒng)把一連串大小一致的數(shù)據(jù)塊組織到一起。有些塊存儲元信息,如空閑塊、目錄、索引等的映射,有些包含文件數(shù)據(jù)。
內(nèi)存映射文件, 為了在內(nèi)核空間 的文件系統(tǒng)頁與用戶空間的內(nèi)存區(qū)之間移動數(shù)據(jù),一次以上的拷貝操作幾乎總是免不了的。
文件鎖定機制, 允許一個進程阻止其他進程存取某文件,或限制其存取方式。通常的用途是控制共享信息的更新方式,或用于事務(wù)隔離。文件鎖有建議使用和強制使用之分。建議型文件鎖會向 出請求的進程 供當前鎖定信息,但 操作系統(tǒng)并不要求一定這樣做,而是由相關(guān)進程進行協(xié)調(diào)并關(guān)注鎖定信息。
流I/O并非所有 I/O 都像前幾節(jié)講的是面向塊的,也有流 I/O,其原理模仿了通道。I/O 字節(jié)流必須順序存取,常見的例子有 TTY(控制臺)設(shè)備、打印機端口和網(wǎng)絡(luò)連接。
流的傳輸一般(也不必然如此)比塊設(shè)備慢,經(jīng)常用于間歇性輸入。
緩沖區(qū)一個Buffer對象是固定數(shù)量的數(shù)據(jù)的容器。其作用是一個存儲器,或者分段運輸區(qū),在 這里數(shù)據(jù)可被存儲并在之后用于檢索。緩沖區(qū)的工作與通道緊密聯(lián)系。 Buffer的類層次圖:
緩沖區(qū)屬性Capacity: 容量, 緩沖區(qū)能夠容納的數(shù)據(jù)元素的最大數(shù)量。這一容量在緩沖區(qū)創(chuàng)建時被設(shè)定,并且永遠不能被改變;
Limit: 上界, 緩沖區(qū)的第一個不能被讀或?qū)懙脑?。或者說,緩沖區(qū)中現(xiàn)存元素的計數(shù);
Position: 位置, 下一個要被讀或?qū)懙脑氐乃饕?。位置會自動由相?yīng)的get()和put()函數(shù)更新;
Mark: 標記, 一個備忘位置。調(diào)用mark()來設(shè)定mark=postion。調(diào)用reset()設(shè)定position= mark。標記在設(shè)定前是未定義的(undefined)。
這四個屬性之間總是 循以下關(guān)系:0 <= mark <= position <= limit <= capacity。
直接緩沖區(qū)操作系統(tǒng)的在內(nèi)存區(qū)域中進行I/O操作。這些內(nèi)存區(qū)域,就操作系統(tǒng)方面而言,是相連的字節(jié)序列。于是,毫無疑問,只有字節(jié)緩沖區(qū)有資格參與I/O操作。也請回想一下操作系統(tǒng)會直接存取進程——在本例中是JVM進程的內(nèi)存空間,以傳輸數(shù)據(jù)。這也意味著I/O操作的目標內(nèi)存區(qū)域必須是連續(xù)的字節(jié)序列。在JVM中,字節(jié)數(shù)組可能不會在內(nèi)存中連續(xù)存儲,或者無用存儲單元 集可能隨時對其進行移動。在Java中,數(shù)組是對象,而數(shù)據(jù)存儲在對象中的方式在不同的JVM實現(xiàn)中都各有不同。
直接緩沖區(qū)被用于與通道和固有 I/O 例程交 互。它們通過使用固有代碼來告知操作系統(tǒng)直接釋放或填充內(nèi)存區(qū)域,對用于通道直接或原始 存取的內(nèi)存區(qū)域中的字節(jié)元素的存儲盡了最大的努力。
通道通道用于在字節(jié)緩沖區(qū)和位于通道另一邊的實體(通常是一個文件或套接字)之間有效地傳輸數(shù)據(jù)。
通道可以形象地比喻為銀行出納窗口使用的動導管。您的薪水支票就是您要傳送的信息,載體(Carrier)就好比一個緩沖區(qū)。您先填充緩沖區(qū)(將您的薪水支票放到載體上),接著將緩沖“寫”到通道中(將載體進導管中),然后信息負載就被傳遞到通道另一邊的I/O服務(wù)(銀行出納員)。channel類的繼承關(guān)系如下:
Scatter/Gather通道提供了一種被稱為Scatter/Gather的重要新功能(有時也被稱為矢量 I/O)。Scatter/Gather是一個簡單卻強大的概念,它是指在多個緩沖區(qū)上實現(xiàn)一個簡單的I/O操作。對于一個write操作而言,數(shù)據(jù)是從幾個緩沖區(qū)按順序抽取(稱為gather)并沿著通道發(fā)送的。對于 read 操作而言,從通道讀取的數(shù)據(jù)會按順序被散布(稱為scatter)到多個緩沖區(qū),將每個緩沖區(qū)填滿直至通道中的數(shù)據(jù)或者緩沖區(qū)的最大空間被消耗完。
Scatter的意思是分散,Gather的意思是聚集。我們注意到在上面的類層次結(jié)構(gòu)圖中,除了ByteChannel外,各Channel類還都實現(xiàn)了兩個接口,分別是:
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根據(jù)IO服務(wù)的情況主要分為兩大類,按照《Java NIO》的描述,兩類IO分別是:file I/O 和 stream I/O。前者是針對文件讀寫操作的,而后者多是網(wǎng)絡(luò)通信相關(guān)的和Socket相關(guān)的。Channel分類也基本如此,和前者對應(yīng)的FileChannel,以及與后者對應(yīng)的SocketChannel等類對象。
文件通道總是阻塞式的,因此不能被置于非阻塞模式。
Socket通道新的socket通道類可以運行非阻塞模式并且是可選擇的。全部socket通道類包括DatagramChannel、SocketChannel和ServerSocketChannel
如上面的類圖,所有的socket通道都繼承于AbstractSelectableChannel。
請注意DatagramChannel和SocketChannel 實現(xiàn)定義讀和寫功能的接口而ServerSocketChannel不實現(xiàn)。ServerSocketChannel 負責監(jiān)聽傳入的連接和創(chuàng)建新的SocketChannel對象,它本身從不傳輸數(shù)據(jù)。
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監(jiān)聽器。它同我們所熟悉的java.net.ServerSocket執(zhí)行相同的基本任務(wù),不過它增加了通道語義,因此能夠在非阻塞模式下運行。
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 類封裝點對點、有序的網(wǎng)絡(luò)連接,類似于我們所熟知并喜愛的 TCP/IP 網(wǎng)絡(luò)連接。SocketChannel 演 戶端發(fā)起同一個監(jiān)聽服務(wù)器的連接。直到連接成功,它才能 到 數(shù)據(jù)并且只會從連接到的地址接 。
DatagramChannel
正如SocketChannel對應(yīng)Socket, ServerSocketChannel對應(yīng)ServerSocket,每一個DatagramChannel對象也有一個關(guān)聯(lián)的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; }選擇器
選擇器提供選擇執(zhí)行已經(jīng)就緒的任務(wù)的能力,這使得多元I/O成為可能。選擇器類管理著一個被注冊的通道集合的信息和它們的就緒狀態(tài)。通道是和選擇器一起被注冊的,并且使用選擇器來更新通道的就緒狀態(tài)。當這么做的時候,可以選擇將被激發(fā)的線程掛起,直
到有就緒的的通道。
將文件內(nèi)容讀取到一個字符串中
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服務(wù)端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!"); }他山之石
關(guān)于異步,同步,阻塞與非阻塞: http://blog.csdn.net/brainkick/article/details/9312407
Java NIO系列教程: http://ifeve.com/java-nio-all/
官網(wǎng)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/
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/66273.html
摘要:線程之間的切換對于操作系統(tǒng)來說是昂貴的。因此,單線程可以監(jiān)視多個通道中的數(shù)據(jù)。當方法返回后,線程可以處理這些事件。 一 NIO簡介 Java NIO 是 java 1.4 之后新出的一套IO接口,這里的的新是相對于原有標準的Java IO和Java Networking接口。NIO提供了一種完全不同的操作方式。 NIO中的N可以理解為Non-blocking,不單純是New。 它支持面...
摘要:從通道進行數(shù)據(jù)寫入創(chuàng)建一個緩沖區(qū),填充數(shù)據(jù),并要求通道寫入數(shù)據(jù)。三之通道主要內(nèi)容通道介紹通常來說中的所有都是從通道開始的。從中選擇選擇器維護注冊過的通道的集合,并且這種注冊關(guān)系都被封裝在當中停止選擇的方法方法和方法。 由于內(nèi)容比較多,我下面放的一部分是我更新在我的微信公眾號上的鏈接,微信排版比較好看,更加利于閱讀。每一篇文章下面我都把文章的主要內(nèi)容給列出來了,便于大家學習與回顧。 Ja...
摘要:上篇說了最基礎(chǔ)的五種模型,相信大家對相關(guān)的概念應(yīng)該有了一定的了解,這篇文章主要講講基于多路復用的。 上篇說了最基礎(chǔ)的五種IO模型,相信大家對IO相關(guān)的概念應(yīng)該有了一定的了解,這篇文章主要講講基于多路復用IO的Java NIO。 背景 Java誕生至今,有好多種IO模型,從最早的Java IO到后來的Java NIO以及最新的Java AIO,每種IO模型都有它自己的特點,詳情請看我的上...
摘要:的選擇器允許單個線程監(jiān)視多個輸入通道。一旦執(zhí)行的線程已經(jīng)超過讀取代碼中的某個數(shù)據(jù)片段,該線程就不會在數(shù)據(jù)中向后移動通常不會。 1、引言 很多初涉網(wǎng)絡(luò)編程的程序員,在研究Java NIO(即異步IO)和經(jīng)典IO(也就是常說的阻塞式IO)的API時,很快就會發(fā)現(xiàn)一個問題:我什么時候應(yīng)該使用經(jīng)典IO,什么時候應(yīng)該使用NIO? 在本文中,將嘗試用簡明扼要的文字,闡明Java NIO和經(jīng)典IO之...
摘要:面向流面向緩沖阻塞非阻塞無選擇器面向流與面向緩沖和之間第一個最大的區(qū)別是,是面向流的,是面向緩沖區(qū)的。換句話說,如果緩沖區(qū)準備好被處理,那么表示緩沖區(qū)滿了。方法掃描緩沖區(qū),但必須保持在方法被調(diào)用之前狀態(tài)相同。 當學習了Java NIO和IO的API后,一個問題馬上涌入腦海: 我應(yīng)該何時使用IO,何時使用NIO呢?在本文中,我會盡量清晰地解析Java NIO和IO的差異、它們的使用場景,...
摘要:而我們現(xiàn)在都已經(jīng)發(fā)布了,的都不知道,這有點說不過去了。而對一個的讀寫也會有響應(yīng)的描述符,稱為文件描述符,描述符就是一個數(shù)字,指向內(nèi)核中的一個結(jié)構(gòu)體文件路徑,數(shù)據(jù)區(qū)等一些屬性。 前言 只有光頭才能變強 回顧前面: 給女朋友講解什么是代理模式 包裝模式就是這么簡單啦 本來我預(yù)想是先來回顧一下傳統(tǒng)的IO模式的,將傳統(tǒng)的IO模式的相關(guān)類理清楚(因為IO的類很多)。 但是,發(fā)現(xiàn)在整理的過程已...
閱讀 862·2021-11-25 09:43
閱讀 3692·2021-11-19 09:40
閱讀 898·2021-09-29 09:34
閱讀 1814·2021-09-26 10:21
閱讀 887·2021-09-22 15:24
閱讀 4208·2021-09-22 15:08
閱讀 3285·2021-09-07 09:58
閱讀 2709·2019-08-30 15:55