NIO編程,New IO?

在學習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分鐘編程,獲得獨家整理的學習資源和日常乾貨推送
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章