快学Java NIO
Java NIO Tutorial 地址:http://tutorials.jenkov.com/java-nio/index.html
Java NIO系列教程譯文地址:http://ifeve.com/java-nio-all/
以下是我拜讀過程中摘抄的部分內(nèi)容,并且加了一些內(nèi)容、筆記,姑且叫《快學(xué)Java NIO》,方便以后再翻閱學(xué)習(xí)
附上一個(gè)Java NIO實(shí)現(xiàn)的demo,多人網(wǎng)絡(luò)聊天室
?
Java NIO 由以下幾個(gè)核心部分組成:
- Channels
- Buffers
- Selectors
基本上,所有的 IO 在NIO 中都從一個(gè)Channel 開始。Channel 有點(diǎn)像流。 數(shù)據(jù)可以從Channel讀到Buffer中,也可以從Buffer 寫到Channel中。
?
Channel
FileChannel 從文件中讀寫數(shù)據(jù)。
DatagramChannel 能通過UDP讀寫網(wǎng)絡(luò)中的數(shù)據(jù)。
SocketChannel 能通過TCP讀寫網(wǎng)絡(luò)中的數(shù)據(jù)。
ServerSocketChannel可以監(jiān)聽新進(jìn)來的TCP連接,像Web服務(wù)器那樣。對(duì)每一個(gè)新進(jìn)來的連接都會(huì)創(chuàng)建一個(gè)SocketChannel。
下面是一個(gè)FileChannel的示例
public class FileChannelTest {public static void main(String[] args) throws IOException {RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");FileChannel inChannel = aFile.getChannel();//涉及到的buffer的方法稍后解釋ByteBuffer buf = ByteBuffer.allocate(48);int bytesRead = inChannel.read(buf);while (bytesRead != -1) {//make buffer ready for read buf.flip();while (buf.hasRemaining()) {System.out.print((char) buf.get());// read 1 byte at a time}
buf.clear();//buf.compact();也可以bytesRead = inChannel.read(buf);}aFile.close();} }
?
Buffer
為了理解Buffer的工作原理,需要熟悉它的三個(gè)屬性:
- capacity
- position
- limit
在寫模式下,Buffer的limit表示你最多能往Buffer里寫多少數(shù)據(jù)。 寫模式下,limit等于Buffer的capacity。
當(dāng)切換Buffer到讀模式時(shí), limit表示你最多能讀到多少數(shù)據(jù)。因此,當(dāng)切換Buffer到讀模式時(shí),limit會(huì)被設(shè)置成寫模式下的position值。換句話說,你能讀到之前寫入的所有數(shù)據(jù)(limit被設(shè)置成已寫數(shù)據(jù)的數(shù)量,這個(gè)值在寫模式下就是position)
?
clear方法就是讓position設(shè)回0,limit與capacity相等。
public final Buffer clear() {position = 0;limit = capacity;mark = -1;return this;}?
flip方法將Buffer從寫模式切換到讀模式。調(diào)用flip()方法會(huì)將position設(shè)回0,并將limit設(shè)置成之前position的值。
public final Buffer flip() {limit = position;position = 0;mark = -1;return this;}?
compact()方法將所有未讀的數(shù)據(jù)拷貝到Buffer起始處。然后將position設(shè)到最后一個(gè)未讀元素正后面。limit屬性依然像clear()方法一樣,設(shè)置成capacity。現(xiàn)在Buffer準(zhǔn)備好寫數(shù)據(jù)了,但是不會(huì)覆蓋未讀的數(shù)據(jù)。
public ByteBuffer compact() {System.arraycopy(hb, ix(position()), hb, ix(0), remaining());position(remaining());limit(capacity());discardMark();return this;}?
?Scatter/Gather
scatter/gather用于描述從Channel中讀取或者寫入到Channel的操作
分散(scatter)從Channel中讀取是指在讀操作時(shí)將讀取的數(shù)據(jù)寫入多個(gè)buffer中。因此,Channel將從Channel中讀取的數(shù)據(jù)“分散(scatter)”到多個(gè)Buffer中。
聚集(gather)寫入Channel是指在寫操作時(shí)將多個(gè)buffer的數(shù)據(jù)寫入同一個(gè)Channel,因此,Channel 將多個(gè)Buffer中的數(shù)據(jù)“聚集(gather)”后發(fā)送到Channel。
?
應(yīng)用場景:例如傳輸一個(gè)由消息頭和消息體組成的消息,你可能會(huì)將消息體和消息頭分散到不同的buffer中,這樣你可以方便的處理消息頭和消息體
?
Scattering Reads在移動(dòng)下一個(gè)buffer前,必須填滿當(dāng)前的buffer,這也意味著它不適用于動(dòng)態(tài)消息(譯者注:消息大小不固定)。
ByteBuffer header = ByteBuffer.allocate(128); ByteBuffer body = ByteBuffer.allocate(1024);ByteBuffer[] bufferArray = { header, body };channel.read(bufferArray);?
ByteBuffer header = ByteBuffer.allocate(128); ByteBuffer body = ByteBuffer.allocate(1024);//write data into buffers ByteBuffer[] bufferArray = { header, body };channel.write(bufferArray);?
FileChannel的transferFrom()方法可以將數(shù)據(jù)從源通道傳輸?shù)紽ileChannel中,下面是一個(gè)簡單的例子:
RandomAccessFile fromFile = new RandomAccessFile("data/fromFile.txt", "rw"); FileChannel fromChannel = fromFile.getChannel();RandomAccessFile toFile = new RandomAccessFile("data/toFile.txt", "rw"); FileChannel toChannel = toFile.getChannel();long position = 0; long count = fromChannel.size();//toChannel.transferFrom(fromChannel, position, count);也可以 fromChannel.transferTo(position, count, toChannel);?
Selector
Selector(選擇器)是Java NIO中能夠檢測一到多個(gè)NIO通道,并能夠知曉通道是否為諸如讀寫事件做好準(zhǔn)備的組件。這樣,一個(gè)單獨(dú)的線程可以管理多個(gè)channel,從而管理多個(gè)網(wǎng)絡(luò)連接,Selector能夠處理多個(gè)通道。
?
Selector selector = Selector.open();//FileChannel不能切換到非阻塞模式,所以這邊不能使FileChannel channel.configureBlocking(false);//與Selector一起使用時(shí),Channel必須處于非阻塞模式下 SelectionKey key = channel.register(selector, SelectionKey.OP_READ); //除了注冊讀,還可以注冊connect,accept,read,write事件 while(true) {int readyChannels = selector.select(); //阻塞到至少有一個(gè)通道就緒,還有select(long timeout)超時(shí)就不阻塞,selectNow()不阻塞,沒有就返回0,當(dāng)然打斷阻塞還有wakeUp()方法,可以用另外一個(gè)線程調(diào)用這個(gè)方法,操作同一個(gè)selector對(duì)象即可if(readyChannels == 0) continue; Set selectedKeys = selector.selectedKeys(); //可以通過這個(gè)方法,知道可用通道的集合Iterator keyIterator = selectedKeys.iterator();while(keyIterator.hasNext()) {SelectionKey key = keyIterator.next();if(key.isAcceptable()) {// a connection was accepted by a ServerSocketChannel.
//SelectionKey.channel()方法返回的通道需要轉(zhuǎn)型成你要處理的類型,如ServerSocketChannel或SocketChannel等} else if (key.isConnectable()) {// a connection was established with a remote server.} else if (key.isReadable()) {// a channel is ready for reading} else if (key.isWritable()) {// a channel is ready for writing }
//Selector不會(huì)自己從已選擇鍵集中移除SelectionKey實(shí)例。必須在處理完通道時(shí)自己移除。下次該通道變成就緒時(shí),Selector會(huì)再次將其放入已選擇鍵集中。keyIterator.remove();} }
?
現(xiàn)在能看到的情況是,一個(gè)請(qǐng)求過來,到Selector這邊,selector從注冊的通道中選擇就緒的通道,然后找到具體的通道處理這個(gè)請(qǐng)求。
用一個(gè)selector線程來安排所有的channel!
當(dāng)然為了并發(fā),可以用多個(gè)selector,然后不同的channel來注冊。這樣就有了反向代理的感覺,selector就是反向代理服務(wù)器上的線程!
(以上是我個(gè)人對(duì)selector的理解,若理解有誤,請(qǐng)指正)
?
Java NIO與IO
我應(yīng)該何時(shí)使用IO,何時(shí)使用NIO呢?在本文中,我會(huì)盡量清晰地解析Java NIO和IO的差異、它們的使用場景,以及它們?nèi)绾斡绊懩拇a設(shè)計(jì)。
?
Java NIO與IO之間主要差別
IO NIO 面向流 面向緩沖 阻塞IO 非阻塞IO 無 選擇器Java NIO的緩沖導(dǎo)向方法是數(shù)據(jù)讀取到一個(gè)它稍后處理的緩沖區(qū),需要時(shí)可在緩沖區(qū)中前后移動(dòng),這就增加了處理過程中的靈活性。NIO設(shè)計(jì)中多了buffer,傳統(tǒng)IO如果要這個(gè)效果,需要自行定義操作buffer。
Java NIO的非阻塞模式,使一個(gè)線程從某通道發(fā)送請(qǐng)求讀取數(shù)據(jù),但是它僅能得到目前可用的數(shù)據(jù),如果目前沒有數(shù)據(jù)可用時(shí),就什么都不會(huì)獲取。
Java NIO的選擇器允許一個(gè)單獨(dú)的線程來監(jiān)視多個(gè)輸入通道,你可以注冊多個(gè)通道使用一個(gè)選擇器。
?
在IO設(shè)計(jì)中,我們從InputStream或 Reader逐字節(jié)讀取數(shù)據(jù)。?readline()阻塞直到整行讀完
NIO可讓您只使用一個(gè)(或幾個(gè))單線程管理多個(gè)通道(網(wǎng)絡(luò)連接或文件),但付出的代價(jià)是解析數(shù)據(jù)可能會(huì)比從一個(gè)阻塞流中讀取數(shù)據(jù)更復(fù)雜。
?
如果需要管理同時(shí)打開的成千上萬個(gè)連接,這些連接每次只是發(fā)送少量的數(shù)據(jù),例如聊天服務(wù)器,實(shí)現(xiàn)NIO的服務(wù)器可能是一個(gè)優(yōu)勢。
如果你需要維持許多打開的連接到其他計(jì)算機(jī)上,如P2P網(wǎng)絡(luò)中,使用一個(gè)單獨(dú)的線程來管理你所有出站連接,可能是一個(gè)優(yōu)勢。
Java NIO: 單線程管理多個(gè)連接,如下圖
如果你有少量的連接使用非常高的帶寬,一次發(fā)送大量的數(shù)據(jù),也許典型的IO服務(wù)器實(shí)現(xiàn)可能非常契合。
Java IO: 一個(gè)典型的IO服務(wù)器設(shè)計(jì)- 一個(gè)連接通過一個(gè)線程處理,如下圖
至此,基本上Java NIO的大體輪廓已經(jīng)明白了,鑒于篇幅不要太長,各個(gè)具體Channel的介紹移步:快學(xué)Java NIO續(xù)篇
- FileChannel
- SocketChannel
- ServerSocketChannel
- Java NIO DatagramChannel
- Pipe
?
?
?
?
轉(zhuǎn)載于:https://www.cnblogs.com/yanghuahui/p/3683600.html
《新程序員》:云原生和全面數(shù)字化實(shí)踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的快学Java NIO的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Console.WriteLine()与
- 下一篇: [Java] HashMap遍历的两种方