1. 程式人生 > >java NIO入門詳解

java NIO入門詳解

在開始之前

關於本教程

新的輸入/輸出 (NIO) 庫是在 JDK 1.4 中引入的。NIO 彌補了原來的 I/O 的不足,它在標準 Java 程式碼中提供了高速的、面向塊的 I/O。通過定義包含資料的類,以及通過以塊的形式處理這些資料,NIO 不用使用本機程式碼就可以利用低階優化,這是原來的 I/O 包所無法做到的。

在本教程中,我們將討論 NIO 庫的幾乎所有方面,從高階的概念性內容到底層的程式設計細節。除了學習諸如緩衝區和通道這樣的關鍵 I/O 元素外,您還有機會看到在更新後的庫中標準 I/O 是如何工作的。您還會了解只能通過 NIO 來完成的工作,如非同步 I/O 和直接緩衝區。

在本教程中,我們將使用展示 NIO 庫的不同方面的程式碼示例。幾乎每一個程式碼示例都是一個大的 Java 程式的一部分,您可以在 

參考資料 中找到這個 Java 程式。在做這些練習時,我們推薦您在自己的系統上下載、編譯和執行這些程式。在您學習了本教程以後,這些程式碼將為您的 NIO 程式設計努力提供一個起點。

本教程是為希望學習更多關於 JDK 1.4 NIO 庫的知識的所有程式設計師而寫的。為了最大程度地從這裡的討論中獲益,您應該理解基本的 Java 程式設計概念,如類、繼承和使用包。多少熟悉一些原來的 I/O 庫(來自 java.io.* 包)也會有所幫助。

雖然本教程要求掌握 Java 語言的工作詞彙和概念,但是不需要有很多實際程式設計經驗。除了徹底介紹與本教程有關的所有概念外,我還保持程式碼示例儘可能短小和簡單。目的是讓即使沒有多少 Java 程式設計經驗的讀者也能容易地開始學習 NIO。

如何執行程式碼

原始碼歸檔檔案(在 參考資料 中提供)包含了本教程中使用的所有程式。每一個程式都由一個 Java 檔案構成。每一個檔案都根據名稱來識別,並且可以容易地與它所展示的程式設計概念相關聯。

教程中的一些程式需要命令列引數才能執行。要從命令列執行一個程式,只需使用最方便的命令列提示符。在 Windows 中,命令列提供符是 “Command” 或者 “command.com” 程式。在 UNIX 中,可以使用任何 shell。

需要安裝 JDK 1.4 並將它包括在路徑中,才能完成本教程中的練習。如果需要安裝和配置 JDK 1.4 的幫助,請參見 參考資料 。

輸入/輸出:概念性描述

I/O 簡介

I/O ? 或者輸入/輸出 ? 指的是計算機與外部世界或者一個程式與計算機的其餘部分的之間的介面。它對於任何計算機系統都非常關鍵,因而所有 I/O 的主體實際上是內建在作業系統中的。單獨的程式一般是讓系統為它們完成大部分的工作。

在 Java 程式設計中,直到最近一直使用 流 的方式完成 I/O。所有 I/O 都被視為單個的位元組的移動,通過一個稱為 Stream 的物件一次移動一個位元組。流 I/O 用於與外部世界接觸。它也在內部使用,用於將物件轉換為位元組,然後再轉換回物件。

NIO 與原來的 I/O 有同樣的作用和目的,但是它使用不同的方式? 塊 I/O。正如您將在本教程中學到的,塊 I/O 的效率可以比流 I/O 高許多。

為什麼要使用 NIO?

NIO 的建立目的是為了讓 Java 程式設計師可以實現高速 I/O 而無需編寫自定義的本機程式碼。NIO 將最耗時的 I/O 操作(即填充和提取緩衝區)轉移回作業系統,因而可以極大地提高速度。

流與塊的比較

原來的 I/O 庫(在 java.io.*中) 與 NIO 最重要的區別是資料打包和傳輸的方式。正如前面提到的,原來的 I/O 以流的方式處理資料,而 NIO 以塊的方式處理資料。

面向流 的 I/O 系統一次一個位元組地處理資料。一個輸入流產生一個位元組的資料,一個輸出流消費一個位元組的資料。為流式資料建立過濾器非常容易。連結幾個過濾器,以便每個過濾器只負責單個複雜處理機制的一部分,這樣也是相對簡單的。不利的一面是,面向流的 I/O 通常相當慢。

一個 面向塊 的 I/O 系統以塊的形式處理資料。每一個操作都在一步中產生或者消費一個數據塊。按塊處理資料比按(流式的)位元組處理資料要快得多。但是面向塊的 I/O 缺少一些面向流的 I/O 所具有的優雅性和簡單性。

整合的 I/O

在 JDK 1.4 中原來的 I/O 包和 NIO 已經很好地集成了。 java.io.* 已經以 NIO 為基礎重新實現了,所以現在它可以利用 NIO 的一些特性。例如, java.io.* 包中的一些類包含以塊的形式讀寫資料的方法,這使得即使在更面向流的系統中,處理速度也會更快。

也可以用 NIO 庫實現標準 I/O 功能。例如,可以容易地使用塊 I/O 一次一個位元組地移動資料。但是正如您會看到的,NIO 還提供了原 I/O 包中所沒有的許多好處。

通道和緩衝區

概述

通道 和 緩衝區 是 NIO 中的核心物件,幾乎在每一個 I/O 操作中都要使用它們。

通道是對原 I/O 包中的流的模擬。到任何目的地(或來自任何地方)的所有資料都必須通過一個 Channel 物件。一個 Buffer 實質上是一個容器物件。傳送給一個通道的所有物件都必須首先放到緩衝區中;同樣地,從通道中讀取的任何資料都要讀到緩衝區中。

在本節中,您會了解到 NIO 中通道和緩衝區是如何工作的。

什麼是緩衝區?

Buffer 是一個物件, 它包含一些要寫入或者剛讀出的資料。 在 NIO 中加入 Buffer 物件,體現了新庫與原 I/O 的一個重要區別。在面向流的 I/O 中,您將資料直接寫入或者將資料直接讀到 Stream 物件中。

在 NIO 庫中,所有資料都是用緩衝區處理的。在讀取資料時,它是直接讀到緩衝區中的。在寫入資料時,它是寫入到緩衝區中的。任何時候訪問 NIO 中的資料,您都是將它放到緩衝區中。

緩衝區實質上是一個數組。通常它是一個位元組陣列,但是也可以使用其他種類的陣列。但是一個緩衝區不 僅僅 是一個數組。緩衝區提供了對資料的結構化訪問,而且還可以跟蹤系統的讀/寫程序。

緩衝區型別

最常用的緩衝區型別是 ByteBuffer。一個 ByteBuffer 可以在其底層位元組陣列上進行 get/set 操作(即位元組的獲取和設定)。

ByteBuffer 不是 NIO 中唯一的緩衝區型別。事實上,對於每一種基本 Java 型別都有一種緩衝區型別:

  • ByteBuffer
  • CharBuffer
  • ShortBuffer
  • IntBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer

每一個 Buffer 類都是 Buffer 介面的一個例項。 除了 ByteBuffer,每一個 Buffer 類都有完全一樣的操作,只是它們所處理的資料型別不一樣。因為大多數標準 I/O 操作都使用 ByteBuffer,所以它具有所有共享的緩衝區操作以及一些特有的操作。

現在您可以花一點時間執行 UseFloatBuffer.java,它包含了型別化的緩衝區的一個應用例子。

什麼是通道?

Channel是一個物件,可以通過它讀取和寫入資料。拿 NIO 與原來的 I/O 做個比較,通道就像是流。

正如前面提到的,所有資料都通過 Buffer 物件來處理。您永遠不會將位元組直接寫入通道中,相反,您是將資料寫入包含一個或者多個位元組的緩衝區。同樣,您不會直接從通道中讀取位元組,而是將資料從通道讀入緩衝區,再從緩衝區獲取這個位元組。

通道型別

通道與流的不同之處在於通道是雙向的。而流只是在一個方向上移動(一個流必須是 InputStream 或者 OutputStream 的子類), 而 通道 可以用於讀、寫或者同時用於讀寫。

因為它們是雙向的,所以通道可以比流更好地反映底層作業系統的真實情況。特別是在 UNIX 模型中,底層作業系統通道是雙向的。

從理論到實踐:NIO 中的讀和寫

概述

讀和寫是 I/O 的基本過程。從一個通道中讀取很簡單:只需建立一個緩衝區,然後讓通道將資料讀到這個緩衝區中。寫入也相當簡單:建立一個緩衝區,用資料填充它,然後讓通道用這些資料來執行寫入操作。

在本節中,我們將學習有關在 Java 程式中讀取和寫入資料的一些知識。我們將回顧 NIO 的主要元件(緩衝區、通道和一些相關的方法),看看它們是如何互動以進行讀寫的。在接下來的幾節中,我們將更詳細地分析這其中的每個元件以及其互動。

從檔案中讀取

在我們第一個練習中,我們將從一個檔案中讀取一些資料。如果使用原來的 I/O,那麼我們只需建立一個 FileInputStream 並從它那裡讀取。而在 NIO 中,情況稍有不同:我們首先從 FileInputStream 獲取一個 Channel 物件,然後使用這個通道來讀取資料。

在 NIO 系統中,任何時候執行一個讀操作,您都是從通道中讀取,但是您不是 直接 從通道讀取。因為所有資料最終都駐留在緩衝區中,所以您是從通道讀到緩衝區中。

因此讀取檔案涉及三個步驟:(1) 從 FileInputStream 獲取 Channel,(2) 建立 Buffer,(3) 將資料從 Channel 讀到 Buffer 中。

現在,讓我們看一下這個過程。

三個容易的步驟

第一步是獲取通道。我們從 FileInputStream 獲取通道:

FileInputStream fin = new FileInputStream( "readandshow.txt" );
FileChannel fc = fin.getChannel();

下一步是建立緩衝區:

ByteBuffer buffer = ByteBuffer.allocate( 1024 );

最後,需要將資料從通道讀到緩衝區中,如下所示:

fc.read( buffer );

您會注意到,我們不需要告訴通道要讀 多少資料 到緩衝區中。每一個緩衝區都有複雜的內部統計機制,它會跟蹤已經讀了多少資料以及還有多少空間可以容納更多的資料。我們將在 緩衝區內部細節 中介紹更多關於緩衝區統計機制的內容。

寫入檔案

在 NIO 中寫入檔案類似於從檔案中讀取。首先從 FileOutputStream 獲取一個通道:

FileOutputStream fout = new FileOutputStream( "writesomebytes.txt" );
FileChannel fc = fout.getChannel();

下一步是建立一個緩衝區並在其中放入一些資料 - 在這裡,資料將從一個名為 message 的陣列中取出,這個陣列包含字串 "Some bytes" 的 ASCII 位元組(本教程後面將會解釋 buffer.flip() 和 buffer.put() 呼叫)。

ByteBuffer buffer = ByteBuffer.allocate( 1024 );

for (int i=0; i<message.length; ++i) {
     buffer.put( message[i] );
}
buffer.flip();

最後一步是寫入緩衝區中:

fc.write( buffer );

注意在這裡同樣不需要告訴通道要寫入多資料。緩衝區的內部統計機制會跟蹤它包含多少資料以及還有多少資料要寫入。

讀寫結合

下面我們將看一下在結合讀和寫時會有什麼情況。我們以一個名為 CopyFile.java 的簡單程式作為這個練習的基礎,它將一個檔案的所有內容拷貝到另一個檔案中。CopyFile.java 執行三個基本操作:首先建立一個 Buffer,然後從原始檔中將資料讀到這個緩衝區中,然後將緩衝區寫入目標檔案。這個程式不斷重複 ― 讀、寫、讀、寫 ― 直到原始檔結束。

CopyFile 程式讓您看到我們如何檢查操作的狀態,以及如何使用 clear() 和 flip() 方法重設緩衝區,並準備緩衝區以便將新讀取的資料寫到另一個通道中。

執行 CopyFile 例子

因為緩衝區會跟蹤它自己的資料,所以 CopyFile 程式的內部迴圈 (inner loop) 非常簡單,如下所示:

fcin.read( buffer );
fcout.write( buffer );

第一行將資料從輸入通道 fcin 中讀入緩衝區,第二行將這些資料寫到輸出通道 fcout 。

檢查狀態

下一步是檢查拷貝何時完成。當沒有更多的資料時,拷貝就算完成,並且可以在 read() 方法返回 -1 是判斷這一點,如下所示:

int r = fcin.read( buffer );

if (r==-1) {
     break;
}

重設緩衝區

最後,在從輸入通道讀入緩衝區之前,我們呼叫 clear() 方法。同樣,在將緩衝區寫入輸出通道之前,我們呼叫 flip() 方法,如下所示:

buffer.clear();
int r = fcin.read( buffer );

if (r==-1) {
     break;
}

buffer.flip();
fcout.write( buffer );

clear() 方法重設緩衝區,使它可以接受讀入的資料。 flip() 方法讓緩衝區可以將新讀入的資料寫入另一個通道。

緩衝區內部細節

概述

本節將介紹 NIO 中兩個重要的緩衝區元件:狀態變數和訪問方法 (accessor)。

狀態變數是前一節中提到的"內部統計機制"的關鍵。每一個讀/寫操作都會改變緩衝區的狀態。通過記錄和跟蹤這些變化,緩衝區就可能夠內部地管理自己的資源。

在從通道讀取資料時,資料被放入到緩衝區。在有些情況下,可以將這個緩衝區直接寫入另一個通道,但是在一般情況下,您還需要檢視資料。這是使用 訪問方法 get() 來完成的。同樣,如果要將原始資料放入緩衝區中,就要使用訪問方法 put()

在本節中,您將學習關於 NIO 中的狀態變數和訪問方法的內容。我們將描述每一個元件,並讓您有機會看到它的實際應用。雖然 NIO 的內部統計機制初看起來可能很複雜,但是您很快就會看到大部分的實際工作都已經替您完成了。您可能習慣於通過手工編碼進行簿記 ― 即使用位元組陣列和索引變數,現在它已在 NIO 中內部地處理了。

狀態變數

可以用三個值指定緩衝區在任意時刻的狀態:

  • position
  • limit
  • capacity

這三個變數一起可以跟蹤緩衝區的狀態和它所包含的資料。我們將在下面的小節中詳細分析每一個變數,還要介紹它們如何適應典型的讀/寫(輸入/輸出)程序。在這個例子中,我們假定要將資料從一個輸入通道拷貝到一個輸出通道。

Position

您可以回想一下,緩衝區實際上就是美化了的陣列。在從通道讀取時,您將所讀取的資料放到底層的陣列中。 position 變數跟蹤已經寫了多少資料。更準確地說,它指定了下一個位元組將放到陣列的哪一個元素中。因此,如果您從通道中讀三個位元組到緩衝區中,那麼緩衝區的 position 將會設定為3,指向陣列中第四個元素。

同樣,在寫入通道時,您是從緩衝區中獲取資料。 position 值跟蹤從緩衝區中獲取了多少資料。更準確地說,它指定下一個位元組來自陣列的哪一個元素。因此如果從緩衝區寫了5個位元組到通道中,那麼緩衝區的 position 將被設定為5,指向陣列的第六個元素。

Limit

limit 變量表明還有多少資料需要取出(在從緩衝區寫入通道時),或者還有多少空間可以放入資料(在從通道讀入緩衝區時)。

position 總是小於或者等於 limit

Capacity

緩衝區的 capacity 表明可以儲存在緩衝區中的最大資料容量。實際上,它指定了底層陣列的大小 ― 或者至少是指定了准許我們使用的底層陣列的容量。

limit 決不能大於 capacity

觀察變數

我們首先觀察一個新建立的緩衝區。出於本例子的需要,我們假設這個緩衝區的 總容量 為8個位元組。 Buffer 的狀態如下所示:

Buffer state

回想一下 ,limit 決不能大於 capacity,此例中這兩個值都被設定為 8。我們通過將它們指向陣列的尾部之後(如果有第8個槽,則是第8個槽所在的位置)來說明這點。

Array

position 設定為0。如果我們讀一些資料到緩衝區中,那麼下一個讀取的資料就進入 slot 0 。如果我們從緩衝區寫一些資料,從緩衝區讀取的下一個位元組就來自 slot 0 。 position 設定如下所示:

Position setting

相關推薦

java NIO入門

在開始之前關於本教程新的輸入/輸出 (NIO) 庫是在 JDK 1.4 中引入的。NIO 彌補了原來的 I/O 的不足,它在標準 Java 程式碼中提供了高速的、面向塊的 I/O。通過定義包含資料的類,以及通過以塊的形式處理這些資料,NIO 不用使用本機程式碼就可以利用低階優

Java異常入門

引子 先來一起看看下面的程式碼: package com.huangzx.Exception; /** * @author huangzx * @date 2018/11/27 */ public class ExceptionTypeTest { public

Java NIO用法

election 線程 tro conn .com 一次 關註 運行 bind 原文: https://my.oschina.net/zhangxufeng/blog/3048735 對於Java NIO,其主要由三個組件組成:Channel、Selector和Buffer

Java NIO入門

nio 是 New I/O 的簡稱,屬於當時 jdk1.4 提供的新 api。如今 jdk 版本已經到 1.8 了,新 IO 這個稱謂有點不合適了,nio 還有一個更合適的叫法——非阻塞(non-blocking)IO。 1. nio與io對比 1

JAVA BIO,NIO,AIO(附程式碼實現)

這幾天在看面試的東西,可能是自己比較笨,花了快兩天的時間才理清楚。特此記錄一下。 首先我們要理解的一個很重要概念是,客戶端連線和傳送資料是分開的,連線不代表立馬會傳輸資料。 說說BIO,NIO,AIO到底是什麼東西 BIO:同步堵塞 NIO:非同步堵塞 AIO:非同步非堵塞

Java入門提高篇】Day34 Java容器類(十五)WeakHashMap

public class WeakHashMapTest { public static void main(String[] args){ testWeakHashMap(); } private static void testWeakHashMap

針對初學者的A*演算法入門(附帶Java原始碼)

      英文題目,漢語內容,有點掛羊頭賣狗肉的嫌疑,不過請不要打擊我這顆想學好英語的心。當了班主任我才發現大一18本書,11本是英語的,能多用兩句英語就多用,個人認為這樣也是積累的一種方法。      Thanks open source pioneers dedicated to computer s

Java 標準IO框架與NIO框架

    在看這篇文章之前,可以先去看看我部落格中另一篇關於同步與非同步、阻塞與非阻塞的理解Java標準IO(BIO)    BIO全稱Blocking IO又叫做同步阻塞IO,它存在如下特點:面向流同步阻塞package com.xdong.bio.client; impo

java阻塞和NIO概念

 java阻塞非阻塞  阻塞與非阻塞IO   JAVA IO的各種流是阻塞的,這意味著,當一個執行緒呼叫read()或write()時,該執行緒被阻塞,直到有一些資料被讀取,或資料完全寫入。   該執行緒在此期間不能再幹任何事情了。   阻塞式網路 IO 的特點:多執行

Java NIO 的前生今世 之三 NIO Buffer

Java NIO Buffer    當我們需要與 NIO Channel 進行互動時, 我們就需要使用到 NIO Buffer, 即資料從 Buffer讀取到 Channel 中, 並且從 Channel 中寫入到 Buffer 中.    實際上, 一個 Buffer 其

Java 8 中新的 Date 和 Time 類入門, DateUtil ,以後可以少寫幾個了,關鍵是執行緒安全了

這篇文章主要是java8中新的Date和Time API的實戰。新的Date和Time類是java開發者社群千呼萬喚始出來的。Java8 之前存在的Date類一直都受人詬病,很多人都會選擇使用第三方的date庫joda-time。Java8中的date和time api

java.util包(二)——Connection接口

操作 相同 元素 叠代 cat roo soft true nbsp Connection接口介紹   Connection接口是java集合的root接口,沒有實現類,只有子接口和實現子接口的各種容器。主要用來表示java集合這一大的抽象概念。   Connection接

java對象

add splay view created 元素 繼承關系 外部 優化 csdn java對象詳解 內存布局 普通對象布局 數組的內存布局 內部類的內存布局 對象分解 對象頭-mark word(8字節) 實例數據 對齊填充(可選) java鎖分析

Java泛型

對象數組 整形 泛型方法 tty 接受 一個 div -m color 泛型的本質是參數化類型,也就是說所操作的數據類型被指定為一個參數。 假定我們有這樣一個需求:寫一個排序方法,能夠對整形數組、字符串數組甚至其他任何類型的數組進行排序,該如何實現? 答案是可以使用 Ja

Asp.Net MVC3 簡單入門過濾器Filter

添加 重復 權限 組件 再次 ace text ext 開發 前言 在開發大項目的時候總會有相關的AOP面向切面編程的組件,而MVC(特指:Asp.Net MVC,以下皆同)項目中不想讓MVC開發人員去關心和寫類似身份驗證,日誌,異常,行為截取等這部分重復的代碼,那我們可以

Java常量池

回收 array 數值 編譯期 二進制格式 new 保持 占用 get 轉自:http://www.cnblogs.com/iyangyuan/p/4631696.html jvm虛擬內存分布圖: 程序計數器:JVM執行程序的流水線。 本地方法棧:JVM調用操作系統方法所

Java線程(一)

線程 thread runnable 程序、進程、線程的概念 程序(program):是為完成特定任務、用某種語言編寫的一組指令的集合。即指一段靜態的代碼,靜態對象。 進程(process):是程序的一次執行過程,或是正在運行的一個程序。動態過程:有它自身的產生、存在和消亡的過程。 如

Java反射機制

java 反射 反射機制 工廠模式 1反射機制是什麽反射機制是在運行狀態中,對於任意一個類,都能夠知道這個類的所有屬性和方法;對於任意一個對象,都能夠調用它的任意一個方法和屬性;這種動態獲取的信息以及動態調用對象的方法的功能稱為java語言的反射機制。在面向對象的世界裏,萬事萬物皆對象.在ja

JAVA環境配置

指向 微軟 ssp cep 解釋 引入 bin testcase loader 步驟:一下載安裝JDK(註意版本)二配置環境變量 JAVA_HOME:JDK的安裝路徑 CLASSPATH:.;%JAVA_HOME%\lib;%JAVA_HOME%\lib\too

Java反射機制

ride length 數組大小 conf array arraycopy 動態調用 ray info Java反射機制詳解 |目錄 1反射機制是什麽 2反射機制能做什麽 3反射機制的相關API ·通過一個對象獲得完整的包名和類名 ·實例化Class類對象 ·獲