Java–NIO
Java傳統的IO是阻塞式的,而NIO則提供了非阻塞式的IO.
NIO即New IO, java從jdk1.4開始引入,用於解決阻塞式的處理,所以也可以說是No block IO.
NIO主要是用來處理Socket中阻塞的問題。
1、NIO組件
Java NIO 由以下幾個核心部分組成:
- Channels
- Buffers
- Selectors
Channel
所有的 IO 在NIO 中都從一個Channel 開始。Channel 有點象流。 數據可以從Channel讀到Buffer中,也可以從Buffer 寫到Channel中。
Channel和Buffer有好幾種類型。下面是JAVA NIO中的一些主要Channel的實現:
- FileChannel
- DatagramChannel
- SocketChannel
- ServerSocketChannel
正如你所看到的,這些通道涵蓋了UDP 和 TCP 網絡IO,以及文件IO。
Buffer
以下是Java NIO裏關鍵的Buffer實現:
- ByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
這些Buffer覆蓋了你能通過IO發送的基本數據類型:byte, short, int, long, float, double 和 char。
selector
Selector允許單線程處理多個 Channel。如果你的應用打開了多個連接(通道),但每個連接的流量都很低,使用Selector就會很方便。例如,在一個聊天服務器中。
這是在一個單線程中使用一個Selector處理3個Channel的圖示:
要使用Selector,得向Selector註冊Channel,然後調用它的select()方法。這個方法會一直阻塞到某個註冊的通道有事件就緒。一旦這個方法返回,線程就可以處理這些事件,事件的例子有如新連接進來,數據接收等。
2、Channel、Buffer
Channel和Buffer在一起使用,channel連接一個目的地,從Buffer中進行讀寫。
1、FileChannel、ByteBuffer
Java NIO中的FileChannel是一個連接到文件的通道。可以通過文件通道讀寫文件。
FileChannel無法設置爲非阻塞模式,它總是運行在阻塞模式下。
Buffer可以創建對應大小的緩存區。
打開FileChannel、創建ByteBuffer
通過使用一個InputStream、OutputStream或RandomAccessFile來獲取一個FileChannel實例.
RandomAccessFile file=new RandomAccessFile("./a.txt","rw");
FileChannel fChannel= file.getChannel();
Buffer沒有公開的構造方法,使用allocate方法創建實例,傳入一個數字作爲容量。
ByteBuffer buffer=ByteBuffer.allocate(48);
Buffer中關於數據的讀寫起始位置,可以看做指針:
Buffer有三個屬性:
- capacity:容量,即創建時指定的數值
- position:定位,即當前緩衝中指向的位置
- limit:極限:即當前Buffer中讀寫所處的極限位置。
操作Buffer和Channel
Channel和Buffer之間進行數據讀寫,和普通IO基本一樣,通過write和read方法。
在對Buffer進行讀寫,可以使用get,put方法。
Buffer進行讀寫之前需要對Buffer中的三個屬性進行調整,以便數據讀寫正確。
向Buffer中寫數據,需要使用clear()與compact()方法,如果調用的是clear()方法,position將被設回0,limit被設置成 capacity的值。
如果Buffer中仍有未讀的數據,且後續還需要這些數據,但是此時想要先先寫些數據,那麼使用compact()方法。
從Buffer中讀數據,需要使用flip方法。
關閉Channel
Buffer不需要關閉,jvm會自動處理。
Channel關閉使用close方法即可。
從文件中讀寫數據
RandomAccessFile file=new RandomAccessFile("./a.txt","rw");
FileChannel fChannel= file.getChannel();//獲取通道
ByteBuffer buffer=ByteBuffer.allocate(48);//get Buffer
int len=fChannel.read(buffer);//返回長度
buffer.flip();//進入讀模式
byte[] b=new byte[64];
buffer.get(b,0,len);//讀到b中len個字節
System.out.println(new String(b,0,len));//輸出
buffer.clear();//全部讀完,進入寫模式
String data="\nnow is: "+System.currentTimeMillis();
buffer.put(data.getBytes());//向通道中寫入
buffer.flip();//進入讀模式
fChannel.write(buffer);//從Buffer中讀出
file.close();
2、SocketChannel、ServerSocketChannel
Socket纔是NIO真正使用的地方。
ServerSocketChannel可以設置爲非阻塞式,這種情況下的網絡Io即不會阻塞。
configureBlocking(false);
打開/關閉Channel
Socket通過open打開連接,需要一個Socket地址。關閉使用close,非阻塞情況下連接可以自動關閉,無需顯示調用。
SocketChannel sChannel=SocketChannel.open();
sChannel.connect(new InetSocketAddress("127.0.0.1", 8001));
操作
操作基本一樣,如下:
使用客戶端向服務端發送數據,服務端返回數據。
客戶端
SocketChannel sChannel=SocketChannel.open();
sChannel.connect(new InetSocketAddress("127.0.0.1", 8001));
ByteBuffer buffer=ByteBuffer.allocate(1024);
buffer.put("hello world".getBytes());
buffer.flip();
sChannel.write(buffer);
buffer.clear();
int len=sChannel.read(buffer);
buffer.flip();
byte[] b=new byte[1024];
buffer.get(b, 0, len);
System.out.println(new String(b,0,len));
服務端
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress("127.0.0.1",8001));
serverSocketChannel.configureBlocking(false);
ByteBuffer buffer=ByteBuffer.allocate(1024);
while(true){
//這裏不再阻塞,直接向下執行
SocketChannel socketChannel =
serverSocketChannel.accept();
if(socketChannel!=null){
int len=socketChannel.read(buffer);
buffer.flip();
byte[] b=new byte[1024];
buffer.get(b,0,len);
System.out.println(new String(b,0,len));
buffer.clear();
buffer.put("yes".getBytes());
buffer.flip();
socketChannel.write(buffer);
break;
}
3、Scatter|Gather
分散與聚集,通道可以與多個Buffer建立關聯。
寫入多個Buffer時,如果某個Buffer滿了,則轉入下個Buffer。
同樣,讀取時,某個Buffer被讀完,則從其他Buffer讀。
file=“b.txt”
abcdefghijkhello world
RandomAccessFile rFile=new RandomAccessFile(file, "rw");
ByteBuffer buffer1=ByteBuffer.allocate(10);
ByteBuffer buffer2=ByteBuffer.allocate(10);
ByteBuffer buffer3=ByteBuffer.allocate(10);
ByteBuffer[] buffers={buffer1,buffer2,buffer3};
FileChannel fc= rFile.getChannel();
long l=fc.read(buffers);//讀到buffers中,buffer1滿則讀到buffer2
System.out.println("buffers "+l);
buffer3.flip();
buffer2.flip();
buffer1.flip();
System.out.println((char)buffer1.get());
System.out.println((char)buffer2.get());
System.out.println((char)buffer3.get());
buffer1.clear();
buffer2.clear();
buffer3.clear();
buffer1.put("scatter".getBytes());
buffer2.put("gatter".getBytes());
buffer3.flip();
buffer2.flip();
buffer1.flip();
fc.write(buffers);//從buffers中寫出,buffer1寫完,則從buffer2中寫。