Java NIO 特性學習
Java NIO 包含幾個核心的組件:
- Channels
- Buffer
- Selectors
Channels
可以理解爲資源的一個流,通過這個流資源可以從Channel讀取Data到一個Buffer中或者從一個Buffer中寫入Data到Channel;
Channel Implementations
集中Jdk7常用的Channel上線
- FileChannel : 操作文件讀取或者寫入數據
- DatagramChannel : 從一個網絡UDP連接中讀取或寫入數據
- SocketChannel : 從一個TCP網絡連接中讀取或寫入數據
- ServerSocketChannel: 可以使用這個Channel來監聽TCP連接,如同Web Server 當連接來時可以創建出一個SocketChannel
Base Channel Example
Read Data from Channel
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
FileChannel inChannel = aFile.getChannel();
ByteBuffer buf = ByteBuffer.allocate(48);
//write data to buffer
int bytesRead = inChannel.read(buf);
while (bytesRead != -1) {
System.out.println("Read " + bytesRead);
//swither buffer type to read data from buffer
buf.flip();
while(buf.hasRemaining()){
System.out.print((char) buf.get());
}
buf.clear();
bytesRead = inChannel.read(buf);
}
aFile.close();
Buffer
Java NIO Buffers 和Channels配合使用,Read from channels into buffer ,writtern from buffers into channels
Buffer Usage
- 向buffer中寫入數據
- 調用buffer.flip()方法
- 使用buffer.get()方法獲取出數據
- 調用buffer.clear() 或者 buffer.compact()清理已經讀取的數據
RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt" , "rw");
FileChannel inChannel = aFile.getChannel();
//Create Buffer with capacity of 48 byte
ByteBuffer buf = ByteBuffer.allocate(48);
int bytesRead = inChannel.read(buf);
while(bytesRead!=-1){
buf.flip(); //make buffer ready for read
while(buf.hasRemaining()){
System.out.print((char) buf.get()); // read 1 byte at a time
}
buf.clear(); //make buffer ready for writing
bytesRead = inChannel.read(buf);
}
Buffer Capacity , Position and Limit
Capacity
Buffer在創建的時候會分配規定大小的內存塊,並且在buffer寫入數據時只能寫入這個固定大小的數據。如果Buffer已經放滿數據,就需要先read 或者 clear 只能才能繼續寫入。
Position
Position 在讀模式和模式下面含義不一樣。Bufer init之後Position值爲0,當數據寫入one byte ,long etc Buffer Position相應的移動到下一個位置。並且Position<=Capacity-1
當調用filp()
從Buffer讀取數據時會set position = 0
,position會隨着讀取過程向下移動;
Limit
在Write模式下,Limit等於Capacity的值,表示能寫多少數據到Buffer中。
調用filp()
進入Reade模式會set limit = position
,表示最多能讀的數據量。
常用的Buffer 實現
- ByteBuffer
- MappedByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
SomeMethods of Buffer
Allocating a Buffer
通過使用allocate()
來分配Buffer大小
ByteBuffer buf = ByteBuffer.allocate(48);
CharBuffer buf = CharBuffer.allocate(1024);
Write Data to a Buffer
向Buffer中寫入數據有兩種方法:
- 從Channel中讀取數據寫入Buffer
- 調用Buffer put()
方法寫入數據
int bytesRead = inChannel.read(buf); //read into buffer.
buf.put(127);
flip()
轉換寫狀態到讀狀態,發生動作:set limit = position ; set position=0 ;
Reading Data from a Buffer
兩種方法:
- 從Buffer中讀取數據到Channel
- 讀取Buffer中數據給自己,可以使用buffer自帶方法 get()
etc
//read from buffer into channel.
int bytesWritten = inChannel.write(buf);
byte aByte = buf.get();
rewind()
set position=0
mark() 和 reset()
mark用於打點記錄position爲止,之後使用reset方法可以將position重置到mark記錄的位置。
Scatter / Gather /Transfers
Channel 可以執行讀取操作將數據讀取到多個Buffer中。
Channel 可以執行寫操作將多個Buffer的數據寫入Channle中。
Channel 可以在Channle之間做轉換包括transferFrom() & transferTo
Scatter Reads :
例如我將html的header部分讀取到第一個buffer中,將body部分讀取到第二個Buffer中
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
ByteBuffer[] bufferArray = { header, body };
channel.read(buffers);
Gather Writes
ByteBuffer header = ByteBuffer.allocate(128);
ByteBuffer body = ByteBuffer.allocate(1024);
//write data into buffers
ByteBuffer[] bufferArray = { header, body };
channel.write(buffers);
Transfers
TransferFrom()
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(fromChannel, position, count);
TransferTo()
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();
fromChannel.transferTo(position, count, toChannel);
Selector
Selector組件可以運行判斷多個Channel,動態決定使用哪個Channel來執行Read 或者 Write操作。通過這個組件可以上線一個Thread 管理多個Channels 或者 多個網絡連接Channel。
Create a Selector
通過使用Selector.open()
方法來創建一個Selector
Selector selector = Selector.open();
註冊Channels到Selector上
爲了能讓Selector管理Channels需要調用Selector.register()
註冊到Selector
channel.configureBlocking(false); //The Channel must be in non-blocking mode to be used with a Selector
SelectionKey key = channel.register(selector, SelectionKey.OP_READ);
註冊Channel的時候需要制定Channel需要關心的事件,事件包括:
- Connect -> SelectionKey.OP_CONNECT
- Accept -> SelectionKey.OP_ACCEPT
- Read -> SelectionKey.OP_READ
- Write ->SelectionKey.OP_WRITE
如果註冊多個事件可以使用int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;
通過int interestSet = selectionKey.interestOps();
可以得到所有Selector’s的事件。
Simple Demo:
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<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> 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();
}
}