NIO

傳統的IO流都是都是阻塞式的輸入、輸出。比如在讀取輸入流中的數據時,如果沒有讀取到有效數據,程序將會在此處阻塞該線程的執行。
並且傳統的IO流都是通過字節的移動來處理的(即使不直接去處理字節流,但底層的實現還是依賴於字節處理),也就是說,面向流的輸入/輸出系統一次只能處理一個字節,因此面向流的輸入輸出的效率不高。
從JDK1.4開始,Java提供了一系列改進的輸入/輸出處理的新功能,這些功能被統稱爲新IO(New IO,簡稱NIO)。

NIO它既可以說成“新I/O”,也可以說成非阻塞式I/O。下面是java NIO的工作原理:
1. 由一個專門的線程來處理所有的 IO 事件,並負責分發。
2. 事件驅動機制:事件到的時候觸發,而不是同步的去監視事件。
3. 線程通訊:線程之間通過 wait,notify 等方式通訊。保證每次上下文切換都是有意義的。減少無謂的線程切換。

      NIO(New IO,簡稱NIO),使用內存映射文件的方式來處理輸入/輸出,將文件或文件的一段區域映射到內存中,這樣就可以像訪問內存一樣來訪問文件了(這種方式模擬了操作系統上的虛擬內存的概念)。通過這種方式來進行輸入/輸出比傳統的輸入/輸出要快得多。
      在NIO中,所有數據都需要通過通道Channel傳輸。它提供了一個map()方法,通過該方法可以直接將“一塊數據”映射到內存中,是面向塊的處理。
      發送到Channel中的所有對象都必須首先放到Buffer中,而從Channel中讀取的數據也必須先放到Buffer中。Buffer可以被理解爲一個容器,它的本質是一個數組。Buffer是一個抽象類,它的常用子類是ByteBuffer,它可以在底層字節數組上進行get/set操作。其他子類比如CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer。
這裏寫圖片描述


Channel

Channel與傳統的流的區別:
(1) Channel可以直接將指定文件的部分或全部直接映射成Buffer
(2) 程序不能直接訪問Channel中的數據,包括讀寫,Channel只能和Buffer進行交互
     也就是說,如果要從Channel中取得數據,必須先用Buffer從Channel中取出一些數據,然後讓程序從Buffer中取出這些數據;
     如果要將程序中的數據寫入Channel,先讓程序將數據放入Buffer中,程序再將Buffer裏的數據寫入Channel中

Channel接口的實現類有:
DatagramChannel、FileChannel、Pipe.SinkChannel、Pipe.SourceChannel、SelectableChannel、ServerSocketChannel、ServerSocketChannel、SocketChannel等

所有的 Channel都不可以直接通過構造器來創建,而是通過傳統的節點 InputStream、OutputStream 的 getChannel()方法來返回對應的Channel,不同的節點流對象獲得的CHannel不一樣。比如FileInputStream的 getChannel()返回的是FileChannel,PipedInputStream的getChannel()返回的是Pipe.SinkChannel、Pipe.SourceChannel。

Channel中常用的三類方法:
(1)public abstract MappedByteBuffer map(FileChannel.MapMode mode,long position,long size):將Channel對應的部分或
        全部數據映射成ByteBuffer。
        第一個參數:只讀(MapMode.READ_ONLY) 、讀/寫(MapMode.READ_WRITE) 等模式。
(2)read():從Buffer中讀取數據
(3)write():向Buffer中寫數據


Buffer

Buffer中三個重要的概念:
(1) 容量 (capacity):緩衝區的容量capacity表示該Buffer的最大數據容量,即最多可以存儲多少數據。capacity不可能爲負值,創建後不能被改變。
(2) 界限 (limit):第一個不應該被讀出或者寫入的緩衝區位置索引。也就是說位於limit後面的數據即不可被讀,也不可被寫。
(3) 位置 (position):用於指明下一個可以被讀出的或者寫入的緩衝區位置索引。當使用Buffer從Channel中讀取數據時,position的值恰好等於已經讀到了多少數據。當剛剛新建一個Buffer對象時,其position爲0;如果從Channel中讀取了2個數據到該Buffer中,則position爲2,指向Buffer中第3個位置(索引從0開始)。
這裏寫圖片描述


Buffer的作用就是裝入數據,然後輸出數據:
(1) 裝入數據。開始時的Buffer的position爲0,limit爲capacity,程序可以通過put()方法向Buffer中放入一些數據(或從Channel中獲取一些數據),每放入一些數據,Buffer的position相應地向後移動一些位置。
(2) 裝入數據結束後,調用Buffer的flip()方法,該方法將limit設置爲position的位置,將position設爲0,這就使Buffer的讀寫指針又移到了開始位置。該方法調用之後,Buffer爲輸出數據做好準備。
(3) 輸出數據。使用Buffer的get()方法取出數據。
(4) 輸出數據結束後,Buffer調用clear()方法,clear()方法不是清空數據,而是將position置爲0,將limit置爲capacity,再次爲Buffer裝入數據做準備。
這裏寫圖片描述
這裏寫圖片描述
這裏寫圖片描述
這裏寫圖片描述


當使用get()和put()方法訪問Buffer中的數據時,分爲絕對和相對兩種:
(1) 相對 (Relative):從Buffer的當前position處開始讀取或寫入,然後將位置(position)的值按處理元素的個數增加
(2) 絕對 (Absolute):直接根據索引向Buffer中讀取或寫入數據,使用絕對方式訪問Buffer裏的數據時,並不會影響位置(position)的值

public class BufferTest {
    public static void main(String[] args) throws IOException {
        // 創建Buffer
        CharBuffer charBuffer = CharBuffer.allocate(8);
        System.out.println("剛創建Buffer之後的position、limit、capacity分別爲:" + charBuffer.position() + " "+ charBuffer.limit()+ " " 
        + charBuffer.capacity());
        System.out.println();

        // 放入數據
        charBuffer.put('i');
        charBuffer.put('y');
        System.out.println("放入2個數據");
        System.out.println("放入數據後的position、limit、capacity分別爲:" + charBuffer.position() + " " + charBuffer.limit() + " "
                + charBuffer.capacity());
        System.out.println();

        // 調用flip()方法(調用完這個方法之後,就爲輸出數據做準備,position置爲0,limit置爲position所在的位置)
        charBuffer.flip();
        System.out.println("調用flip()方法,爲讀數據做準備。讀的範圍應該是所寫入數據的範圍0-position,因此把limit置爲position,再把position置爲0");
        System.out.println("調用flip()方法之後的position、limit、capacity分別爲:" + charBuffer.position() + " " + charBuffer.limit()
                + " " + charBuffer.capacity());
        System.out.println();

        // 相對:取出元素,position的值會改變
        System.out.println("以相對Relative的方式取值");
        System.out.println("取出第一個元素(position=0):" + charBuffer.get());
        System.out.println("取出第一個元素之後position變成了:" + charBuffer.position());
        System.out.println();

        // 調用clear()方法
        charBuffer.clear();
        System.out.println("調用clear方法,爲裝入數據做準備,因此position置爲0,limit置爲capacity");
        System.out.println("調用clear()方法之後的position、limit、capacity分別爲:" + charBuffer.position() + " "
                + charBuffer.limit() + " " + charBuffer.capacity());
        System.out.println("執行clear()方法之後,緩衝區的內容並沒有被清除,第一個元素爲:" + charBuffer.get(1));
        System.out.println();

        // 放入數據
        System.out.println("重新放入兩個字符數據");
        charBuffer.put('n');
        charBuffer.put('m');
        System.out.println("放入數據後的position、limit、capacity分別爲:" + charBuffer.position() + " " + charBuffer.limit() + " "
                + charBuffer.capacity());
        System.out.println();

        charBuffer.flip();

        System.out.println("根據索引取數據,第二個數據爲:" + charBuffer.get(1));
        System.out.println("根據position取數據:" + charBuffer.get());
    }
}

控制檯輸出:

剛創建Buffer之後的position、limit、capacity分別爲:0 8 8

放入2個數據
放入數據後的position、limit、capacity分別爲:2 8 8

調用flip()方法,爲讀數據做準備。讀的範圍應該是所寫入數據的範圍0-position,因此把limit置爲position,再把position置爲0
調用flip()方法之後的position、limit、capacity分別爲:0 2 8

以相對Relative的方式取值
取出第一個元素(position=0):i
取出第一個元素之後position變成了:1

調用clear方法,爲裝入數據做準備,因此position置爲0,limit置爲capacity
調用clear()方法之後的position、limit、capacity分別爲:0 8 8
執行clear()方法之後,緩衝區的內容並沒有被清除,第一個元素爲:y

重新放入兩個字符數據
放入數據後的position、limit、capacity分別爲:2 8 8

根據索引取數據,第二個數據爲:m
根據position取數據:n

使用NIO複製文件:

public class NioCopyFileTest {
    public static void nioCopyFile(String resource,String destination) throws IOException{
        //文件輸入流
        FileInputStream fis = new FileInputStream(resource);

        //文件輸出流
        FileOutputStream fos = new FileOutputStream(destination);

        //讀文件通道
        FileChannel readChannel = fis.getChannel();

        //寫文件通道
        FileChannel writeChannel = fos.getChannel();

        //創建一個容量爲1024的ByteBuffer對象
        ByteBuffer buffer = ByteBuffer.allocate(1024);

        while (true) {
            buffer.clear();                         //清空buffer中的內容
            int len = readChannel.read(buffer);     //讀入數據
            if(len == -1){                          //讀取完畢
                break;                                  
            }
            buffer.flip();                          //將寫模式轉換爲讀模式。
            writeChannel.write(buffer);             //寫入數據
        }

        fis.close();
        fos.close();
    }

    public static void main(String[] args) throws IOException {
        NioCopyFileTest.nioCopyFile("D:\\temp_buffer.txt", "c:\\temp_buffer.txt");
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章