1. 程式人生 > >Java NIO使用及原理分析 (一)

Java NIO使用及原理分析 (一)

最近由於工作關係要做一些Java方面的開發,其中最重要的一塊就是Java NIO(New I/O),儘管很早以前瞭解過一些,但並沒有認真去看過它的實現原理,也沒有機會在工作中使用,這次也好重新研究一下,順便寫點東西,就當是自己學習 Java NIO的筆記了。本文為NIO使用及原理分析的第一篇,將會介紹NIO中幾個重要的概念。

在Java1.4之前的I/O系統中,提供的都是面向流的I/O系統,系統一次一個位元組地處理資料,一個輸入流產生一個位元組的資料,一個輸出流消費一個位元組的資料,面向流的I/O速度非常慢,而在Java 1.4中推出了NIO,這是一個面向塊的I/O系統,系統以塊的方式處理處理,每一個操作在一步中產生或者消費一個數據庫,按塊處理要比按位元組處理資料快的多。

在NIO中有幾個核心物件需要掌握:緩衝區(Buffer)、通道(Channel)、選擇器(Selector)。

緩衝區Buffer

緩衝區實際上是一個容器物件,更直接的說,其實就是一個數組,在NIO庫中,所有資料都是用緩衝區處理的。在讀取資料時,它是直接讀到緩衝區中的; 在寫入資料時,它也是寫入到緩衝區中的;任何時候訪問 NIO 中的資料,都是將它放到緩衝區中。而在面向流I/O系統中,所有資料都是直接寫入或者直接將資料讀取到Stream物件中。

在NIO中,所有的緩衝區型別都繼承於抽象類Buffer,最常用的就是ByteBuffer,對於Java中的基本型別,基本都有一個具體Buffer型別與之相對應,它們之間的繼承關係如下圖所示:

下面是一個簡單的使用IntBuffer的例子:

import java.nio.IntBuffer;

public class TestIntBuffer {
	public static void main(String[] args) {
		// 分配新的int緩衝區,引數為緩衝區容量
		// 新緩衝區的當前位置將為零,其界限(限制位置)將為其容量。它將具有一個底層實現陣列,其陣列偏移量將為零。
		IntBuffer buffer = IntBuffer.allocate(8);

		for (int i = 0; i < buffer.capacity(); ++i) {
			int j = 2 * (i + 1);
			// 將給定整數寫入此緩衝區的當前位置,當前位置遞增
			buffer.put(j);
		}

		// 重設此緩衝區,將限制設定為當前位置,然後將當前位置設定為0
		buffer.flip();

		// 檢視在當前位置和限制位置之間是否有元素
		while (buffer.hasRemaining()) {
			// 讀取此緩衝區當前位置的整數,然後當前位置遞增
			int j = buffer.get();
			System.out.print(j + "  ");
		}

	}

}
執行後可以看到:

在後面我們還會繼續分析Buffer物件,以及它的幾個重要的屬性。

通道Channel

通道是一個物件,通過它可以讀取和寫入資料,當然了所有資料都通過Buffer物件來處理。我們永遠不會將位元組直接寫入通道中,相反是將資料寫入包含一個或者多個位元組的緩衝區。同樣不會直接從通道中讀取位元組,而是將資料從通道讀入緩衝區,再從緩衝區獲取這個位元組。

在NIO中,提供了多種通道物件,而所有的通道物件都實現了Channel介面。它們之間的繼承關係如下圖所示:

使用NIO讀取資料

在前面我們說過,任何時候讀取資料,都不是直接從通道讀取,而是從通道讀取到緩衝區。所以使用NIO讀取資料可以分為下面三個步驟:
1. 從FileInputStream獲取Channel
2. 建立Buffer
3. 將資料從Channel讀取到Buffer中

下面是一個簡單的使用NIO從檔案中讀取資料的例子:

import java.io.*;
import java.nio.*;
import java.nio.channels.*;

public class Program {
    static public void main( String args[] ) throws Exception {
        FileInputStream fin = new FileInputStream("c:\\test.txt");
        
        // 獲取通道
        FileChannel fc = fin.getChannel();
        
        // 建立緩衝區
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        
        // 讀取資料到緩衝區
        fc.read(buffer);
        
        buffer.flip();
        
        while (buffer.remaining()>0) {
            byte b = buffer.get();
            System.out.print(((char)b));
        }
        
        fin.close();
    }
}

使用NIO寫入資料

使用NIO寫入資料與讀取資料的過程類似,同樣資料不是直接寫入通道,而是寫入緩衝區,可以分為下面三個步驟:
1. 從FileInputStream獲取Channel
2. 建立Buffer
3. 將資料從Channel寫入到Buffer中

下面是一個簡單的使用NIO向檔案中寫入資料的例子:

import java.io.*;
import java.nio.*;
import java.nio.channels.*;

public class Program {
    static private final byte message[] = { 83, 111, 109, 101, 32,
        98, 121, 116, 101, 115, 46 };

    static public void main( String args[] ) throws Exception {
        FileOutputStream fout = new FileOutputStream( "c:\\test.txt" );
        
        FileChannel fc = fout.getChannel();
        
        ByteBuffer buffer = ByteBuffer.allocate( 1024 );
        
        for (int i=0; i<message.length; ++i) {
            buffer.put( message[i] );
        }
        
        buffer.flip();
        
        fc.write( buffer );
        
        fout.close();
    }
}

本文介紹了Java NIO中三個核心概念中的兩個,並且看了兩個簡單的示例,分別是使用NIO進行資料的讀取和寫入,Java NIO中最重要的一塊Nonblocking I/O將在第三篇中進行分析,下篇將會介紹Buffer內部實現。

(未完待續)