什麼是NIO
Java NIO(NEW IO)是從Java1.4開始引入的新版IO,用來替代標準的Java IO API
NIO於原來的IO有相同的功能,但是他們之間的使用方式是完全不同的,NIO是面向緩衝區,面向通道的的IO操作,NIO擁有更加高效的進行文件讀寫。
另外NIO在網絡編程可以是一個無阻塞的IO交互,可以大大提升Socket
交互的效率。
NIO於IO的區別
IO | NIO |
---|---|
面向流(Stream Oriented) | 面向緩衝區(Buffer Oriented) |
阻塞IO(Block IO) | 無阻塞IO(Non Block IO) |
無 | Selectors(選擇器) |
單向通道 | 雙向通道 |
NIO 三大核心內容
通道channel
在NIO中文件通道是不具備文件傳輸功能的,如果需要文件傳輸,必須依賴於緩衝區的存在。
通道常見實現類有:
創建通道的方式FileChannel
爲例
- Java 針對支持通道的類提供了getChannel()方法
-
本地文件IO
-
FileInputStream/FileOutInputStream
-
RandomAccessFile
-
網絡文件IO
-
Socket
-
ServerSocket
-
DatagramSocket
-
在JDK 1.7中NIO.2針對各個通道提供了靜態方法open()
-
在JDK 1.7中NIO.2的Files工具類的newByteChannel()
/**
* 採用支持通道的類,創建對應的文件通道
*
**/
public void test6() throws FileNotFoundException {
FileInputStream fileInputStream = new FileInputStream(new File("D:\\1.txt"));
FileChannel channel = fileInputStream.getChannel();
}
/**
* 使用靜態方法獲得
*/
public void test3()throws IOException{
FileChannel open = FileChannel.open(Paths.get("src/psb (2).jpg"), StandardOpenOption.READ);
FileChannel channel = FileChannel.open(Paths.get("psb.jpg"), StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE_NEW);
}
Buffer
緩衝區
緩衝區(buffer):在Java NIO 中負責數據存儲。緩衝區就是數組。用於存儲不同數據類型的數據
根據數據類型不同(Boolean除外),提供了相應類型的緩衝區:
- ByteBuffer
- CharBuffer
- ShortBuffer
- IntegerBuffer
- LongBuffer
- FloatBuffer
- DoubleBuffer
在buffer緩衝區中有四個核心參數:
capacity
:緩衝區中最大存儲數據的容量。一旦聲明不能改變limit
:界限,表示緩衝區中可以操作數據的大小。(limit後數據不能進行讀寫)position
:位置,表示緩衝區中正在操作數據的位置mark
:記錄,表示記錄當前position的位置。可以通過reset()恢復到mark的位置mark <= position <= limit <= capacity
他們之間存在這樣的關係
buffer
緩衝區分類
- 非直接緩衝區
通過allocate()方法分配非直接緩衝區,將緩衝區建立在jvm內存中
public class TestBuffer {
public static void main(String[] args) {
// 打開1024字節大小的緩衝區
ByteBuffer allocate = ByteBuffer.allocate(1024);
System.out.println("-------------allocate-----------");
System.out.println(allocate.position());
System.out.println(allocate.limit());
System.out.println(allocate.capacity());
// 利用put方法存入數據到緩衝區中
System.out.println("--------------put---------------");
allocate.put(new String("abc").getBytes());
System.out.println(allocate.position());
System.out.println(allocate.limit());
System.out.println(allocate.capacity());
// filp()方法進行讀寫模式轉換
allocate.flip();
System.out.println("--------------get---------------");
// 將數據讀取到字節數組中
byte[] bytes = new byte[allocate.limit()];
allocate.get(bytes);
System.out.println(new String(bytes,0,bytes.length));
// rewind():可重複讀
allocate.rewind();
// clear:清空緩衝區,但是緩衝區中的數據依然存在,但是處於被遺忘狀態
// 判斷緩衝區中是否還有可以操作的數據
if (allocate.hasRemaining()) {
// 打印還有多少數據可以操作
System.out.println(allocate.remaining());
}
}
}
- 直接緩衝區
直接字節緩衝區可以通過調用此類的allocateDirect()
工廠方法創建,此方法返回的緩衝區進行分配和取消分配所需成本通常高於非直接緩衝區。直接緩衝區的內容可以主流在常規的垃圾回收堆之外,因此,他們對應用程序內存需求量造成的影響可能並不明顯。所以,建議講直接緩衝區主要分配給那些易受基礎系統的本機IO操作影響、持久的緩衝區。一般情況下,最好盡在直接緩衝區能在程序性能方面帶來明顯好處時分配他們。
直接緩衝區還可以通過FilChannel
的map()
方法將文件區域直接銀蛇到內存中來創建。該方法返回MappedByteBuffer
。Java平臺的實現有助於。
執行方法後各個參數值變化示意圖
ByteBuffer allocate = ByteBuffer.allocate(1024);
分配了一個1024空間大小的緩衝區
allocate.put(new String("abc").getBytes());
向緩衝區放入三個字節數的字符串
allocate.flip();
利用filp()
方法進行讀寫反轉
Selector
選擇器
NIO之所以能在網絡編程上面有很大的提升,於這個關鍵元素有很大的關係
Selector
選擇器,網絡編程使用NIO的大哥!!!
服務器可以執行一個線程,運行Selector程序,進行監聽操作。
新連接, 已經連接, 讀取數據,寫入數據
Selector常用方法:
public static Selector Open();
得到一個選擇器對象
public int select(long timeout);
監聽所有註冊通道,存在IO流操作是,會將對應的信息SelectionKey存入到內部的集
閤中,參數是一個超時時間
public Set<SelectionKey> selectionKeys();
返回當前Selector內部集合中保存的所有SelectionKey
SelectionKey
表示Selector和網絡通道直接的關係
int OP_ACCEPT; 16 需要連接
int OP_CONNECT; 8 已經連接
int OP_READ; 1 讀取操作
int OP_WRITE; 4 寫入操作
SelectionKey
public abstract Selector selector();
得到與之關聯的 Selector 對象
public abstract SelectableChannel channel();
得到與之關聯的通道
public final Object attachment();
得到與之關聯的共享數據
public abstract SelectionKey interestOps(int ops);
設置或改變監聽事件
public final boolean isAcceptable();
是否可以 accept
public final boolean isReadable();
是否可以讀
public final boolean isWritable();
是否可以寫
採用無阻塞IO實現網絡聊天室
import org.junit.Test;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
/**
* <p>Title:TestBlockIO</p>
* <p>Description:TestBlockIO ,測試無阻塞NIO</p>
*
* @author justLym
* @version 1.0.0
* @date 2019/11/23
**/
public class TestBlockIO {
@Test
public void client() throws IOException {
SocketChannel socketChannel = SocketChannel.open(new InetSocketAddress("127.0.0.1",6888));
socketChannel.configureBlocking(false);
ByteBuffer buffer = ByteBuffer.allocate(1024);
buffer.put("黎亞明".getBytes());
buffer.flip();
socketChannel.write(buffer);
buffer.clear();
socketChannel.close();
}
@Test
public void server() throws IOException{
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.bind(new InetSocketAddress("127.0.0.1",6888));
ByteBuffer buffer = ByteBuffer.allocate(1024);
Selector selector = Selector.open();
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while(selector.select()>0){
Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
while(iterator.hasNext()){
SelectionKey selectionKey = iterator.next();
if(selectionKey.isAcceptable()){
SocketChannel accept = serverSocketChannel.accept();
System.out.println(accept);
accept.configureBlocking(false);
accept.register(selector,SelectionKey.OP_READ);
}else if(selectionKey.isReadable()){
SocketChannel channel = (SocketChannel)selectionKey.channel();
channel.configureBlocking(false);
int len = 0;
while((len=channel.read(buffer))!=-1){
buffer.flip();
System.out.println(new String(buffer.array(),0,len));
buffer.clear();
}
}
iterator.remove();
}
}
serverSocketChannel.close();
selector.close();
}
}