1. 程式人生 > >Java NIO入門到精通

Java NIO入門到精通

NIO(New I/O)系統,即Java的第二個I/O系統。NIO提供了與標準I/O API不同的I/O處理方式。它是Java用來替代傳統I/O API(Java 1.4)。它支援面向緩衝的,基於通道的I/O操作方法。隨著JDK 7的推出,NIO系統得到了擴充套件,為檔案系統功能和檔案處理提供了增強的支援。 由於NIO檔案類支援的這些新的功能,NIO被廣泛應用於檔案處理。
NIO為Java程式設計師實現高速I/O,而不使用自定義本機程式碼。 NIO將填充,排放緩衝區等的時間性I/O活動移回作業系統,從而大大提高了操作速度。

NIO類包含在一個叫作java.nio包的包中。基於流的I/O是在java.io包中,為了更好地學習NIO中的知識內容,下面看下IO和NIO區別:

IO NIO
基於阻塞I/O操作 基於非阻塞I/O操作
面向流的 面向快取的
通道不可用 通道可用於非阻塞I/O操作
選擇器不可用 選擇器可用於非阻塞I/O操作

阻塞I/O 阻塞IO等待資料寫入或返回前的讀取。Java IO的各種流是阻塞的。這意味著當執行緒呼叫write()或read()時,執行緒會被阻塞,直到有一些資料可用於讀取或資料被完全寫入。
非阻塞I/O 非阻塞IO不等待返回前讀取或寫入資料。 Java NIO非阻塞模式允許執行緒請求向通道寫入資料,但不等待它被完全寫入。允許執行緒繼續進行,並做其他事情。

面向流 Java IO是面向流的I/O,這意味著我們需要從流中讀取一個或多個位元組。它使用流來在資料來源/槽和java程式之間傳輸資料。使用此方法的I/O操作較慢。
面向緩衝

Java NIO是面向快取的I/O方法。 將資料讀入緩衝器,使用通道進一步處理資料。 在NIO中,使用通道和緩衝區來處理I/O操作。
通道和流之間的主要區別是:流可以用於單向資料傳輸,通道提供雙向資料傳輸。因此,通過在java NIO中引入通道,可以執行非阻塞I/O操作。

通道 在Java NIO中,通道是在實體和位元組緩衝區之間有效傳輸資料的媒介。它從一個實體讀取資料,並將其放在緩衝區塊中以供消費。通道作為Java NIO提供的閘道器來訪問I/O機制。通常,通道與作業系統檔案描述符具有一對一關係,用於提供平臺獨立操作功能。通道使用原生代碼執行實際工作。通道介面允許我們以便攜和受控的方式訪問低階I/O服務。

選擇器 在Java NIO中,選擇器是可選擇通道的多路複用器,可用作可以進入非阻塞模式的特殊型別的通道。它可以檢查一個或多個NIO通道,並確定哪個通道準備好進行通訊,即讀取或寫入。選擇器用於使用單個執行緒處理多個通道。因此,它需要較少的執行緒來處理這些通道。執行緒之間的切換對於作業系統來說是昂貴的。 因此,為了提高系統效率選擇器是有用的。

Java NIO基本元件如下:

  • 通道和緩衝區(Channels and Buffers):在標準I/O API中,使用字元流和位元組流。 在NIO中,使用通道和緩衝區。資料總是從緩衝區寫入通道,並從通道讀取到緩衝區。
  • 選擇器(Selectors):Java NIO提供了“選擇器”的概念。這是一個可以用於監視多個通道的物件,如資料到達,連線開啟等。因此,單執行緒可以監視多個通道中的資料。
  • 非阻塞I/O(Non-blocking I/O):Java NIO提供非阻塞I/O的功能。這裡應用程式立即返回任何可用的資料,應用程式應該具有池化機制,以查明是否有更多資料準備就緒。

在Java中,NIO讀寫是I/O的基本過程。從通道讀取:建立一個緩衝區,然後請求通道讀取資料。通道寫入:建立一個緩衝區,填充資料,並要求通道寫入資料。
讀寫操作中使用的核心部件有:Channels,Buffers 和 Selectors。Java NIO還有其它更多的元件和類,但是Channel,Buffer和Selector用作API的核心。

在標準I/O API中,使用字元流和位元組流。 在NIO中使用通道和緩衝區。 NIO中的所有I/O都是通過一個通道開始的。資料總是從緩衝區寫入通道,並從通道讀取到緩衝區。

在Java NIO中,主要使用的通道如下:DatagramChannel,SocketChannel,FileChannel 和 ServerSocketChannel。上述通道涵蓋UDP(使用者資料報協議)+ TCP(傳輸控制協議)網路I/O和檔案I/O。

DatagramChannel:資料報通道可以通過UDP(使用者資料報協議)通過網路讀取和寫入資料。它使用工廠方法來建立新物件。
開啟和關閉DatagramChannel的語法:

DatagramChannel ch = DatagramChannel.open();

DatagramChannel ch = DatagramChannel.close();

SocketChannel:資料報通道可以通過TCP(傳輸控制協議)通過網路讀取和寫入資料。它還使用工廠方法來建立新物件。
開啟和關閉SocketChannel的語法:

SocketChannel ch = SocketChannel.open();  
ch.connect(new InetSocketAddress(host, port));

SocketChannel ch = SocketChannel.close();  
ch.connect(new InetSocketAddress(host, port));

Java NIO SocketChannel用於將通道與TCP(傳輸控制協議)網路套接字連線。它相當於網路程式設計中使用的Java網路套接字(Socket)。

FileChannel:檔案通道用於從檔案讀取資料。它只能通過呼叫getChannel()方法來建立物件。不能直接建立FileChannel物件。

public class ChannelDemo {
	public static void main(String args[]) throws IOException {
        ReadableByteChannel source = new FileInputStream("article.json").getChannel();
        
        WritableByteChannel destination = new FileOutputStream("article2.json").getChannel();
        
        copyData(source, destination);
        
        source.close();
        destination.close();
    }

    private static void copyData(ReadableByteChannel src, WritableByteChannel dest) throws IOException {
        ByteBuffer buffer = ByteBuffer.allocateDirect(20 * 1024);
        while (src.read(buffer) != -1) {
            buffer.flip();
            while (buffer.hasRemaining()) {
                dest.write(buffer);
            }
            buffer.clear();
        }
    }
}

ServerSocketChannel:ServerSocketChannel允許使用者監聽傳入的TCP連線,與Web伺服器相同。對於每個傳入連線,都會為連線建立一個SocketChannel。

ServerSocketChannel ch = ServerSocketChannel.open();  
ch.socket().bind (new InetSocketAddress (port));

Java NIO ServerSocketChannel還可以用來將通道與TCP(傳輸控制協議)網路套接字連線起來。它相當於網路程式設計中使用的Java網路套接字。ServerSocketChannel類位於java.nio.channels包中。

在Java NIO中,通道是用於在實體和位元組緩衝區之間有效傳輸資料的介質。它從一個實體讀取資料,並將其放在緩衝區塊中以供消費。通道作為Java NIO提供的閘道器來訪問I/O機制。通常,通道與作業系統檔案描述符具有一對一關係,用於提供平臺獨立操作功能。

在Java NIO中,通道提供了稱為分散/聚集或向量I/O的重要功能。 這是一種簡單但功能強大的技術,通過這種技術,使用單個write()函式將位元組從一組緩衝區寫入流,並且可以使用單個read()函式將位元組從流讀取到一組緩衝區中。

“分散讀取”用於將資料從單個通道讀取多個緩衝區中的資料。
“聚集寫入”用於將資料從多個緩衝區寫入單個通道。

public class ScatterGatherDemo {
	private static Charset cs = Charset.forName("UTF-8");
	
	public static void main(String params[]) {
        String str = "Scattering and Gathering Demo from Angelia";
        gatherBytes(str);
        scatterBytes();
    }

    public static void gatherBytes(String data) {
        ByteBuffer buffer1 = ByteBuffer.allocate(8);
        buffer1 = cs.encode("88888888");
        
        ByteBuffer buffer2 = ByteBuffer.allocate(400);
        buffer2 = cs.encode(data);
        try {
        	//建立 GatheringByteChannel, 資料寫到檔案
        	createChannelInstance("scattergather.txt", true).write(new ByteBuffer[] { buffer1, buffer2 });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void scatterBytes() {
    	
    	
        ByteBuffer buffer1 = ByteBuffer.allocate(8);
        ByteBuffer buffer2 = ByteBuffer.allocate(400);
        try {
        	createChannelInstance("scattergather.txt", false).read(new ByteBuffer[] { buffer1, buffer2 });
        } catch (Exception e) {
            e.printStackTrace();
        }
        buffer1.rewind();
        buffer2.rewind();

        System.out.println(cs.decode(buffer1).toString());
        System.out.println(cs.decode(buffer2).toString());
    }

    public static FileChannel createChannelInstance(String file, boolean isOutput) {
        FileChannel FChannel = null;
        try {
            if (isOutput) {
                FChannel = new FileOutputStream(file).getChannel();
            } else {
                FChannel = new FileInputStream(file).getChannel();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return FChannel;
    }
}

在Java NIO中,可以非常頻繁地將資料從一個通道傳輸到另一個通道。批量傳輸檔案資料是非常普遍的,因為幾個優化方法已經新增到FileChannel類中,使其更有效率。
通道之間的資料傳輸在FileChannel類中的兩種方法是:
    FileChannel.transferTo()方法
    FileChannel.transferFrom()方法

public class TransferDemo {
	public static void main(String[] argv) throws Exception {
        String[] inF = new String[] { "article.json", "article2.json" };
       
        FileOutputStream output = new FileOutputStream(new File("combine_article.txt"));
        WritableByteChannel targetChannel = output.getChannel();
        for (int i = 0; i < inF.length; i++) {
            FileInputStream input = new FileInputStream(inF[i]);
            FileChannel inputChannel = input.getChannel();

            inputChannel.transferTo(0, inputChannel.size(), targetChannel);

            inputChannel.close();
            input.close();
        }
        targetChannel.close();
        output.close();
    }
}

Java NIO中核心緩衝區如下:CharBuffer,DoubleBuffer,IntBuffer,LongBuffer,ByteBuffer,ShortBuffer 和 FloatBuffer。這些緩衝區覆蓋了通過I/O傳送的基本資料型別:characters,double,int,long,byte,short 和 float。

基本緩衝區BufferedReader示例:

public class BufferedDemo {
	public static void main(String[] args) {
        try (InputStream inputStream = Files.newInputStream(Paths.get("article.json"));
        		BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))){
           
        	System.out.println(bufferedReader.readLine());
        	
        } catch (IOException e) {
			e.printStackTrace();
		}
    }
}

在Java NIO中,選擇器(Selector)是可選擇通道的多路複用器,可用作可以進入非阻塞模式的特殊型別的通道。它可以檢查一個或多個NIO通道,並確定哪個通道準備好了可以進行通訊,即讀取或寫入。

選擇器(Selector)用於使用單個執行緒處理多個通道。 因此,它需要較少的執行緒來處理這些通道。 執行緒之間的切換對於作業系統來說是昂貴的。 因此,使用它可以提高系統效率。