------NIO簡介(1)--------
NIO組件
channel,buffer,selector,pip,filelock。
Channel分為:
FileChannel
DatagramChannel
SocketChannel
ServerSocketChannel
UDP 和 TCP 網絡IO,以及文件IO
Buffer分為:
ByteBuffer
CharBuffer
DoubleBuffer
FloatBuffer
IntBuffer
LongBuffer
ShortBuffer
byte, short, int, long, float, double 和 char。
結構圖
? ? ? ? ? ? ? ? ? thread
? ? ? ? ? ? ? ? ? ? |
? ? ? ? ? ? ? ? selector
? ? |? ? ? ? ? ? ? |? ? ? ? ? ? |
channel? ? ? ? ? channel? ? ? channel
使用Selector,得向Selector注冊Channel,然后調用它的select()方法。
這個方法會一直阻塞到某個注冊的通道有事件就緒。一旦這個方法返回
線程就可以處理這些事件,事件的例子有如新連接進來,數據接收等
--------channel(2)---------
channel 可以理解為pip
channel <=======> buffer
FileChannel 從文件中讀寫數據。
DatagramChannel 能通過UDP讀寫網絡中的數據。
SocketChannel 能通過TCP讀寫網絡中的數據。
ServerSocketChannel可以監聽新進來的TCP連接,像Web服務器那樣。
對每一個新進來的連接都會創建一個SocketChannel。?
例子:
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? NIO
File? -? channel -? buffer =============== application
buf.flip() 的調用,首先讀取數據到Buffer,然后反轉Buffer,接著再從Buffer中讀取數據
--------NIO buffer(3)------------
四個步驟:
寫入數據到Buffer
調用flip()方法
從Buffer中讀取數據
調用clear()方法或者compact()方法
flip()方法
反轉IO? 寫=========》讀
capacity
position
capacity /limit? ? ? -
要想獲得一個Buffer對象首先要進行分配。
allocate方法。
ByteBuffer buf = ByteBuffer.allocate(48);
--------向Buffer中寫數據-----
從Channel寫到Buffer。
通過Buffer的put()方法寫到Buffer里。
int bytesRead = inChannel.read(buf); //read into buffer.
buf.put(127);
調用flip()方法會將position設回0,并將limit設置成之前position的值。
----從Buffer中讀取數據----
從Buffer讀取數據到Channel。
使用get()方法從Buffer中讀取數據。
int bytesWritten = inChannel.write(buf);
byte aByte = buf.get();
rewind()方法
Buffer.rewind()將position設回0,所以你可以重讀Buffer中的所有數據。limit保持不變
如果調用的是clear()方法.
position將被設回0,limit被設置成 capacity的值,Buffer 被清空了
compact()方法
如果Buffer中仍有未讀的數據,且后續還需要這些數據,但是此時想要先先寫些數據,
compact()方法將所有未讀的數據拷貝到Buffer起始處。然后將position設到最后一個未讀元素正后面
mark()與reset()方法
通過調用Buffer.mark()方法,可以標記Buffer中的一個特定position。
之后可以通過調用Buffer.reset()方法恢復到這個position。
equals()方法
有相同的類型(byte、char、int等)。
Buffer中剩余的byte、char等的個數相等。
Buffer中所有剩余的byte、char等都相同。
compareTo()方法
ompareTo()方法比較兩個Buffer的剩余元素
第一個不相等的元素小于另一個Buffer中對應的元素 。
第一個Buffer的元素個數比另一個少)
--------------Scatter/Gather(4)-----------------
scatter/gather用于描述從Channel
scatter? 讀操作時將讀取的數據寫入多個buffer中
gather? 寫操作時將多個buffer的數據寫入同一個Channel
scatter
經常用于需要將傳輸的數據分開處理的場合,例如傳輸一個由消息頭和消息體組成的消息,
你可能會將消息體和消息頭分散到不同的buffer中,這樣你可以方便的處理消息頭和消息體。
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body? = ByteBuffer.allocate(1024);
ByteBuffer[] bufferArray = { header, body };
channel.read(bufferArray);
read()方法按照buffer在數組中的順序將從channel中讀取的數據寫入到buffer,
當一個buffer被寫滿后,channel緊接著向另一個buffer中寫。
這也意味著它不適用于動態消息(譯者注:消息大小不固定)
----Gathering---------
Gathering Writes是指數據從多個buffer寫入到同一個channel。
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body? = ByteBuffer.allocate(1024);
ByteBuffer[] bufferArray = { header, body };
channel.write(bufferArray);
注意只有position和limit之間的數據才會被寫入。
因此,如果一個buffer的容量為128byte,但是僅僅包含58byte的數據
------------(五) 通道之間的數據傳輸---------------------
直接將數據從一個channel傳輸到另外一個channel。
transferFrom()
FileChannel? ===========>? FileChannel
RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
FileChannel? ? ? fromChannel = fromFile.getChannel();
RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
FileChannel? ? ? toChannel = toFile.getChannel();
long position = 0;
long count = fromChannel.size();
toChannel.transferFrom(position, count, fromChannel);
transferTo()
FileChannel ===========>? Channel
-----------------------(六) Selector--------------
Selector(選擇器)是Java NIO中能夠檢測一到多個NIO通道
一個單獨的線程可以管理多個channel
Selector的創建?
Selector selector = Selector.open();
向Selector注冊通道
SelectableChannel.register()
channel.configureBlocking(false);
SelectionKey key = channel.register(selector,
? ? Selectionkey.OP_READ);
Connect
Accept
Read
Write
int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
SelectionKey
interest集合
ready集合
Channel
Selector
附加的對象(可選)
Channel? channel? = selectionKey.channel();
Selector selector = selectionKey.selector();
通過Selector選擇通道
下面是select()方法:
int select()
int select(long timeout)
int selectNow()
完整的示例
Selector selector = Selector.open();
channel.configureBlocking(false);
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
while(true) {
? int readyChannels = selector.select();
? if(readyChannels == 0) continue;
? Set selectedKeys = selector.selectedKeys();
? Iterator keyIterator = selectedKeys.iterator();
? while(keyIterator.hasNext()) {
? ? SelectionKey key = keyIterator.next();
? ? if(key.isAcceptable()) {
? ? ? ? // a connection was accepted by a ServerSocketChannel.
? ? } 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
? ? }
? ? keyIterator.remove();
? }
}
-------(七) FileChannel--------------
連接到文件的通道
FileChannel無法設置為非阻塞模式,它總是運行在阻塞模式下。
打開FileChannel
InputStream、OutputStream或RandomAccessFile來獲取一個FileChannel實例
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
從FileChannel讀取數據
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);
向FileChannel寫數據
String newData = "New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining()) {
? ? channel.write(buf);
}
關閉FileChannel
channel.close();
FileChannel的某個特定位置進行數據的讀/寫操作
position方法
long pos = channel.position();
channel.position(pos +123);
FileChannel的size方法
long fileSize = channel.size();
FileChannel的truncate方法
截取文件,后面的部分將被刪除
channel.truncate(1024);
FileChannel的force方法
FileChannel.force()方法將通道里尚未寫入磁盤的數據強制寫到磁盤上。
channel.force(true);
---------(八) SocketChannel------------
連接到TCP網絡套接字的通道
創建SocketChannel
打開一個SocketChannel并連接到互聯網上的某臺服務器。
一個新連接到達ServerSocketChannel時,會創建一個SocketChannel
打開 SocketChannel
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));
關閉 SocketChannel
socketChannel.close();
從 SocketChannel 讀取數據
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = socketChannel.read(buf);
分配一個Buffer。從SocketChannel讀取到的數據將會放到這個Buffer中
返回的int值表示讀了多少字節進Buffer里
寫入 SocketChannel
new string - new buf - buf clear - buf put - buf flip - buf remain - channel write
非阻塞模式
設置 SocketChannel 為非阻塞模式(non-blocking mode)
異步模式下調用connect(), read() 和write()了。
connect()
socketChannel.configureBlocking(false);
socketChannel.connect(new InetSocketAddress("http://jenkov.com", 80));
while(! socketChannel.finishConnect() ){
? ? //wait, or do something else...
}
非阻塞模式與選擇器
非阻塞模式與選擇器搭配會工作的更好,通過將一或多個SocketChannel注冊到Selector,
可以詢問選擇器哪個通道已經準備好了讀取,寫入等
--------(九) ServerSocketChannel---------
ServerSocketChannel 是一個可以監聽新進來的TCP連接的通道
例子:
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9999));
while(true){
? ? SocketChannel socketChannel =
? ? ? ? ? ? serverSocketChannel.accept();
? ? //do something with socketChannel...
}
打開 ServerSocketChannel
關閉 ServerSocketChannel
監聽新進來的連接
ServerSocketChannel.accept() 方法監聽新進來的連接
返回一個包含新進來的連接的 SocketChannel
非阻塞模式
非阻塞模式下,accept() 方法會立刻返回
如果還沒有新進來的連接,返回的將是null。
需要檢查返回的SocketChannel是否是null
if(socketChannel != null){
? ? ? ? //do something with socketChannel...
? ? }
--------(十) Java NIO DatagramChannel-------
DatagramChannel是一個能收發UDP包的通道
UDP是無連接的網絡協議,所以不能像其它通道那樣讀取和寫入。它發送和接收的是數據包。
打開 DatagramChannel
DatagramChannel channel = DatagramChannel.open();
channel.socket().bind(new InetSocketAddress(9999));
接收數據
receive()
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
channel.receive(buf);
發送數據
send()方法
String newData = "New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
int bytesSent = channel.send(buf, new InetSocketAddress("jenkov.com", 80));
連接到特定的地址
channel.connect(new InetSocketAddress("jenkov.com", 80));
當連接后,也可以使用read()和write()方法,就像在用傳統的通道一樣。
只是在數據傳送方面沒有任何保證。這里有幾個例子:
----------(十一) Pipe--------------
管道是2個線程之間的單向數據連接
Pipe有一個source通道和一個sink通道
數據會被寫到sink通道,從source通道讀取。
ThreadA - sink - source - ThreadB
創建管道
Pipe pipe = Pipe.open();
向管道寫數據
Pipe.SinkChannel sinkChannel = pipe.sink();
String newData = "New String to write to file..." + System.currentTimeMillis();
ByteBuffer buf = ByteBuffer.allocate(48);
buf.clear();
buf.put(newData.getBytes());
buf.flip();
while(buf.hasRemaining()) {
? ? sinkChannel.write(buf);
}
從管道讀取數據
Pipe.SourceChannel sourceChannel = pipe.source();
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = sourceChannel.read(buf);
------------(十二) Java NIO與IO-----------
IO? ? ? ? ? ? ? ? NIO
面向流? ? ? ? ? ? 面向緩沖
阻塞IO? ? ? ? ? ? 非阻塞IO
無? ? ? ? ? ? ? ? 選擇器
面向流與面向緩沖