在學習NIO之前,我們需要先了解幾個概念
- 什麼是同步、異步IO?
同步IO是指某一線程在發出一個調用時,在調用結果沒有結束之前,一直等待,調用不返回。
異步IO是指當某一線程發出一個調用時,因爲不能立刻得到結果,所以該線程可以去做其它的事情,等原來的調用有了結果,以狀態、通知或者回調的方式通知調用者。
- 什麼是阻塞、非阻塞?
阻塞是指該線程無法做其它任務,只有條件就緒時才能繼續
非阻塞是指不管IO操作是否結束,直接返回,相應操作在後臺繼續處理。
有的同學會混淆同步和阻塞、異步和非阻塞的概念,認爲它們是等同的,其實它們還是有區別的,可以這樣理解:
同步/異步的區別在在於進程是否阻塞,可以理解爲實際爲進程操作。
阻塞和非阻塞的區別在於應用程序的調用是否立即返回,可以理解爲IO請求。
有了這些概念,我們學習下NIO與IO有哪些不同。
1、NIO與IO
翻譯了jenkov的文檔,總結NIO和IO如下表
IO | NIO |
---|---|
面向流 | 面向緩衝 |
阻塞IO | 非阻塞IO |
無 | 選擇器 |
通過表可以看出,NIO的在原有IO的做了很大的改變,我們具體分析下
- 面向流VS面向緩衝
java IO 面向流意味着可以從流中讀取一個或多個字節,這些字節不會存放在任何在任何緩存中,流中數據的流向是單向的,如果需要移動來回移動流中的數據,需要將這些數據存存放在緩衝區中。
Java NIO的面向緩衝是將數據讀入到緩衝區,從緩衝區中拿數據,存放在緩衝區中的數據可以來回移動,從緩衝區拿數據時,需要先檢查緩衝區是否存在自己想要的數據,當向緩衝區寫數據時,要確保不要覆蓋緩衝區中未處理的數據。
- 阻塞IO VS 非阻塞IO
Java IO當線程調用read()和write()時,該線程將會被阻塞,直到有一些數據讀取結束或者被完全寫入爲止,在此期間,線程將無法執行其它任何操作。
Java NIO非阻塞模式可以從通道讀取數據,並且可以獲取當前可用的內容,如果當前沒有可用的數據,線程可以繼續處理其它的事情,而不是在數據可供讀取之前保持阻塞狀態。
- 選擇器
Java NIO選擇器允許單個線程監視多個輸入通道,可以使用選擇器註冊多個通道,然後使用單個線程選擇具有可用於處理輸入的通道,或者選擇已經準準備好進入寫入的通道,這種選擇器機制可以使用單個線程管理多個通道。
**NOTE :**IO和NIO都是同步的,因爲NIO中的accept/read/write方法的內核操作都會阻塞當前線程。
2、NIO的功能
NIO主要有三個組成部分,分別爲Channel(通道)、Buffer(緩衝區)、Selector(選擇器)
2.1通道
通道表示打開到IO設備(如文件)的連接,類似於傳統IO中的流的傳輸,在NIO中,Channel本身不能直接訪問數據,Channel只能與Buffer進行交互,Java爲Channel接口提供了四個主要的實現類
繼承鏈爲:
FileChannel | 讀取、寫入、映射和操作文件的通道 |
---|---|
DatagramChannel | 通過UDP讀寫網絡中的數據通道 |
SocketChannel | 通過TCP讀寫網絡中的數據 |
ServerSocketChannel | 可以監聽新進來的TCP連接,對每一個新進來的連接都會創建一個ServerSocketChannel |
本文主要講解FileChannel,其餘三個放在網絡編程的專題中講解。
獲取通道需要使用支持通道的對象調用getChannel()方法。
支持通道的類有
- FileInputStream、FileOutputStream、RandomAccessFile、DatagramSocket、Socket、ServerSocket
2.2緩衝區
緩衝區(Buffer)是一個包含特定類型的容器,在java NIO中,Buffer用於和通道進行交互,數據從通道讀入緩衝區,從緩衝區寫入通道。Buffer類似於Java IO中定義的緩存數組,它可以保存多個相同類型的數據,
繼承鏈爲:
繼承鏈中的Buffer的子類僅僅是管理的數據類型不同,它們對數據處理的方法是相似的。
Buffer的子類中提供了兩個用於數據操作的方法:get()和put()
- 獲取buffer中的數據
get():讀取單個字節
get(byte[] dst):讀取多個字節到dst中
get(int index):讀取指定位置的字節
//get():讀取單個字節
public abstract byte get();
//get(byte[] dst):讀取多個字節到dst中
public ByteBuffer get(byte[] dst) {
return get(dst, 0, dst.length);
}
//get(int index):讀取指定位置的字節
public abstract byte get(int index);
- 將數據放入到buffer中
put(byte b):將給定單個字節寫入緩衝區的當前位置
put(byte() src):將 src 中的字節寫入緩衝區的當前位置
put(int index,byte b):將指定字節寫入緩衝區的索引位置
//put(byte b):將給定單個字節寫入緩衝區的當前位置
public abstract ByteBuffer put(byte b);
//put(byte() src):將 src 中的字節寫入緩衝區的當前位置
public ByteBuffer put(ByteBuffer src) {
if (src == this)
throw new IllegalArgumentException();
if (isReadOnly())
throw new ReadOnlyBufferException();
int n = src.remaining();
if (n > remaining())
throw new BufferOverflowException();
for (int i = 0; i < n; i++)
put(src.get());
return this;
}
//put(int index,byte b):將指定字節寫入緩衝區的索引位置
public abstract ByteBuffer put(int index, byte b);
打開Buffer類,可以發現主要有以下幾個重要的屬性
capacity:表示buffer的最大數據容量,不能爲負,創建後不能修改。
limit:不能被寫入或讀取的第一個位置的索引,limit後的數據不能被讀寫
position:下一個要讀取或寫入數據的索引
mark:標記一個索引,通過buffer中的mark()方法指定Buffer中一個特定的position,之後可以通過reset方法恢復到該位置。
private int mark = -1;
private int position = 0;
private int limit;
private int capacity;
緩衝區常用的方法,參考jdk8開發手冊
方法 | 描述 |
---|---|
Buffer clean() | 清空緩存並返回對緩衝區的引用 |
Buffer flip() | 將緩衝區的界限設置爲當前位置,並將當前位置設置爲0 |
int capacity() | 返回Buffer的capacity大小 |
boolean hasRemaining() | 判斷緩衝區是否還有元素 |
int limit() | 返回 Buffer 的界限(limit) 的位置 |
Buffer limit(int n) | 將設置緩衝區界限爲 n, 並返回一個具有新 limit 的緩衝區對象 |
Buffer mark() | 對緩衝區設置標記 |
int position() | 返回緩衝區的當前位置 position |
Buffer position(int n) | 將設置緩衝區的當前位置爲 n , 並返回修改後的 Buffer 對象 |
int remaining() | 返回 position 和 limit 之間的元素個數 |
Buffer reset() | 將位置 position 轉到以前設置的 mark 所在的位置 |
Buffer rewind() | 將位置設爲 0, 取消設置的 mark |
//Buffer clear()方法,清空緩存並返回對緩衝區的引用
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
//Buffer flip()方法,將緩衝區的界限設置爲當前位置,並將當前位置設置爲0
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
//Buffer rewind() 將位置設爲 0, 取消設置的 mark
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}
//remaining()返回 position 和 limit 之間的元素個數
public final int remaining() {
return limit - position;
}
//hasRemaining,判斷緩衝區是否還有元素
public final boolean hasRemaining() {
return position < limit;
}
//limit(),返回 Buffer 的界限(limit) 的位置
public final int limit() {
return limit;
}
//Buffer position(),將設置緩衝區的當前位置爲 n , 並返回修改後的 Buffer 對象
public final Buffer position(int newPosition) {
if ((newPosition > limit) || (newPosition < 0))
throw new IllegalArgumentException();
position = newPosition;
if (mark > position) mark = -1;
return this;
}
//Buffer limit(),將設置緩衝區界限爲 n, 並返回一個具有新 limit 的緩衝區對象
public final Buffer limit(int newLimit) {
if ((newLimit > capacity) || (newLimit < 0))
throw new IllegalArgumentException();
limit = newLimit;
if (position > limit) position = limit;
if (mark > limit) mark = -1;
return this;
}
//Buffer mark(),對緩衝區設置標記
public final Buffer mark() {
mark = position;
return this;
}
//Buffer reset(),將位置 position 轉到以前設置的 mark 所在的位置
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}
緩衝區還支持分散讀取和聚集寫入
- 分散讀取
將channel中的分散到多個buffer中,按照緩衝區的順序,依次將Buffer填滿
- 聚集寫入
將多個buffer中的數據聚集到Channel,按照緩衝區的順序,寫入channel
2.3選擇器
選擇器部分將放在網絡編程專題中講解。
3、NIO的使用
3.1Channel的使用
-
利用通道完成文件的複製
核心代碼如下
public void test1() {
FileInputStream fis = null;
FileOutputStream fos = null;
//獲取通道
FileChannel inChannel = null;
FileChannel outChannel = null;
try {
fis = new FileInputStream("F:\\學習筆記\\Simon學習筆記\\java\\wife.jpg");
fos = new FileOutputStream("F:\\學習筆記\\Simon學習筆記\\java\\wife2.jpg");
inChannel=fis.getChannel();
outChannel=fos.getChannel();
//分配指定大小的緩衝器
ByteBuffer buf = ByteBuffer.allocate(1024);
//將通道中的數據存入緩衝區
while (inChannel.read(buf) != -1) {
//切換讀數據模式
buf.flip();
//將緩衝區的數據寫入通道
outChannel.write(buf);
//清空緩衝區
buf.clear();
}
catch(Exception e){}
finally(){}
}
-
分散讀取與聚集寫入
public void test2() throws IOException{ RandomAccessFile raf1=new RandomAccessFile("F:\\學習筆記\\Simon學習筆記\\java\\test.txt","rw"); //1、獲取通道 FileChannel channel1=raf1.getChannel(); //分配指定大小的緩衝區 ByteBuffer buf1=ByteBuffer.allocate(512); ByteBuffer buf2=ByteBuffer.allocate(1024); //3、分散讀取 ByteBuffer[] bufs={buf1,buf2}; channel1.read(bufs); for (ByteBuffer byteBuffer:bufs){ byteBuffer.flip(); } System.out.println(new String(bufs[0].array(), 0, bufs[0].limit())); System.out.println("-----------------"); System.out.println(new String(bufs[1].array(), 0, bufs[1].limit())); //4. 聚集寫入 RandomAccessFile raf2 = new RandomAccessFile("F:\\學習筆記\\Simon學習筆記\\java\\test2.txt", "rw"); FileChannel channel2 = raf2.getChannel(); channel2.write(bufs); }
3.2Buffer的使用
對於Buffer,我們主要測試Buffer中的屬性
- 分配指定大小的緩衝區
ByteBuffer buf=ByteBuffer.allocate(1024);
- 使用put()存入數據到緩衝區
String str="今天,你是否博學了!"
ByteBuffer buf=ByteBuffer.allocate(1024);
buf.put(str.getBytes());
- 切換讀取模式
ByteBuffer.flip();
- 使用get()讀取緩存中的數據
byte[] dst = new byte[buf.limit()];
buf.get(dst);
- 可重複讀操作
ByteBuffer.rewind();
- 清空緩衝區
ByteBuffer.clear();
關注公衆號:10分鐘編程,獲得獨家整理的學習資源和日常乾貨推送