淺談Java IO流

今天主要來回顧一下有關Java IO流的知識,後面還會對NIO進行介紹。html

Java IO

定義

Java的IO流是實現輸入和輸出的基礎,能夠方便的實現數據的輸入和輸出操做。java

IO流的分類

  • 按照流的流向分,能夠分爲輸入流和輸出流;
  • 按照操做單元劃分,能夠劃分爲字節流和字符流;
  • 按照流的角色劃分爲節點流和處理流。

Java Io流的40多個類都是從以下4個抽象類基類中派生出來的。數組

  • InputStream/Reader: 全部的輸入流的基類,前者是字節輸入流,後者是字符輸入流。
  • OutputStream/Writer: 全部輸出流的基類,前者是字節輸出流,後者是字符輸出流。

它們都是一些抽象基類,沒法直接建立實例。異步

經常使用的IO流的用法

在InputStreamhe裏面包含以下3個方法:socket

int read();
從輸入流中讀取單個字節,返回所讀取的字節數據。
int read(byte[] b);
從輸入流中最多讀取b.length個字節的數據,並將其存儲在字節數組b中,返回實際讀取的字節數。
int read(byte[] b,int off,int len); 
從輸入流中最多讀取len個字節的數據,並將其存儲在數組b中,放入數組b中時,並非從數組起點開始,而是
從off位置開始,返回實際讀取的字節數。

OutputStream和Writer的用法也很是類似,兩個流都提供了以下三個方法:學習

void write(int c); 
將指定的字節/字符輸出到輸出流中,其中c便可以表明字節,也能夠表明字符。
void write(byte[]/char[] buf);
將字節數組/字符數組中的數據輸出到指定輸出流中。
void write(byte[]/char[] buf, int off,int len ); 
將字節數組/字符數組中從off位置開始,長度爲len的字節/字符輸出到輸出流中。

由於字符流直接以字符做爲操做單位,因此Writer能夠用字符串來代替字符數組,即以String對象做爲參數。Writer裏面還包含以下兩個方法。大數據

void write(String str);
將str字符串裏包含的字符輸出到指定輸出流中。
void write (String str, int off, int len); 
將str字符串裏面從off位置開始,長度爲len的字符輸出到指定輸出流中。

IO文件流

下面咱們來看看Java中基於IO的文件流的使用方式spa

  • FileInputStream 是InputStream子類,以FileInputStream 爲例進行文件讀取
File f =new File("d:/czy.txt");
            //建立基於文件的輸入流
            FileInputStream fis =new FileInputStream(f);
            //建立字節數組,其長度就是文件的長度
            byte[] all =new byte[(int) f.length()];
            //以字節流的形式讀取文件全部內容並返回給all數組
            fis.read(all);
            for (byte b : all) {
                System.out.println(b);
            }             
            //每次使用完流,都應該進行關閉
            fis.close();

FileReader使用方法和上面同樣,只不過是把byte改爲char而已。3d

  • FileWriter 是Writer的子類,以FileWriter 爲例把字符串寫入到文件
File f = new File("d:/czy.txt");
            // 建立基於文件的Writer
            FileWriter fr = new FileWriter(f)
            // 以字符流的形式把數據寫入到文件中
            String data="abcdefg1234567890";
            char[] cs = data.toCharArray();
            fr.write(cs);

NIO

定義

Java NIO 是JDK 1.4以後新出的一套IO接口,NIO中的N能夠理解爲Non-blocking。
原來的I/O以流的方式處理數據,而NIO以塊的方式處理數據。code

組成

NIO最重要的組成部分:

  • 通道 Channel
  • 緩衝區 Buffer
  • 選擇器 Selector

什麼是通道?

Channel是一個對象,能夠經過它讀取和寫入數據。

全部數據都經過 Buffer 對象來處理。您永遠不會將字節直接寫入通道中,相反,您是將數據寫入緩衝區。一樣,您不會直接從通道中讀取字節,而是將數據從通道讀入緩衝區,再從緩衝區獲取這個字節。

緩衝區實質上是一個數組。一般它是一個字節數組,可是也可使用其餘種類的數組。
最經常使用的緩衝區類型是 ByteBuffer

讀寫操做

從文件中讀取

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

FileInputStream fin = new FileInputStream( "d:/czy.txt" );
FileChannel fc = fin.getChannel();

下一步是建立緩衝區:

ByteBuffer buffer = ByteBuffer.allocate( 1024 );

最後,須要將數據從通道讀到緩衝區中,以下所示:

fc.read( buffer );

allocate() 方法分配一個具備指定大小的底層數組,並將它包裝到一個緩衝區對象中,在本例中是一個 ByteBuffer。

您還能夠將一個現有的數組轉換爲緩衝區,以下所示:

byte array[] = new byte[1024];
ByteBuffer buffer = ByteBuffer.wrap( array );

寫入文件

在 NIO 中寫入文件相似於從文件中讀取。首先從 FileOutputStream 獲取一個通道:

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

下一步是建立一個緩衝區並在其中放入一些數據

ByteBuffer buffer = ByteBuffer.allocate(1024);
//從message數組取出數據 
for (int i=0; i<message.length; ++i) {
     buffer.put(message[i]);
}
buffer.flip();

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

fc.write( buffer );

flip() 方法讓緩衝區能夠將新讀入的數據寫入另外一個通道,後面咱們會詳細介紹。

緩衝區內部細節

狀態變量

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

  • position
  • limit
  • capacity

Position

緩衝區實際上就是一個數組。
若是是寫入數據,position指定了下一個字節將放到數組的哪個元素。
若是是輸出數據,position`指定下一個字節來自數組的哪個元素。

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

因爲capacity不會改變,因此咱們在下面的討論中能夠忽略它。

第一次讀取

如今咱們能夠開始在新建立的緩衝區上進行讀/寫操做。首先從輸入通道中讀一些數據到緩衝區中。第一次讀取獲得三個字節。它們被放到數組中從position開始的位置,這時 position 被設置爲 0。讀完以後,position 就增長到 3,以下所示:

Position increased to 3

limit沒有改變。

第二次讀取

在第二次讀取時,咱們從輸入通道讀取另外兩個字節到緩衝區中。這兩個字節儲存在由position所指定的位置上,position於是增長 2:

Position increased by 2

limit沒有改變。

flip

如今咱們要將數據寫到輸出通道中。在這以前,咱們必須調用flip()方法。這個方法作兩件很是重要的事:

  1. 它將limit設置爲當前position
  2. 它將position設置爲 0。

前一小節中的圖顯示了在 flip 以前緩衝區的狀況。下面是在 flip 以後的緩衝區:

Buffer after the flip

咱們如今能夠將數據從緩衝區寫入通道了。position被設置爲 0,這意味着咱們獲得的下一個字節是第一個字節。limit已被設置爲原來的position,這意味着它包括之前讀到的全部字節,而且一個字節也很少。

第一次寫入

在第一次寫入時,咱們從緩衝區中取四個字節並將它們寫入輸出通道。這使得position增長到 4,而limit不變,以下所示:

Position advanced to 4, limit unchanged

第二次寫入

咱們只剩下一個字節可寫了。limit在咱們調用flip()時被設置爲 5,而且position不能超過limit。因此最後一次寫入操做從緩衝區取出一個字節並將它寫入輸出通道。這使得position增長到 5,並保持limit不變,以下所示:

Position advanced to 5, limit unchanged

clear

最後一步是調用緩衝區的clear()方法。這個方法重設緩衝區以便接收更多的字節。Clear作兩種很是重要的事情:

  1. 它將limit設置爲與capacity相同。
  2. 它設置position爲 0。

下圖顯示了在調用clear()後緩衝區的狀態:

State of the buffer after clear() has been called

緩衝區如今能夠接收新的數據了。

緩衝區的使用

下面的內部循環歸納了使用緩衝區將數據從輸入通道拷貝到輸出通道的過程。

while (true) {
     buffer.clear();
     int r = fcin.read(buffer);
 
     if (r==-1) {
       break;
     }
 
     buffer.flip();
     fcout.write(buffer);
}

read()write() 調用獲得了極大的簡化,由於許多工做細節都由緩衝區完成了。
clear()flip() 方法用於讓緩衝區在讀和寫之間切換。

Selectors

主要用於異步I/O

Selector就是您註冊對各類 I/O 事件的興趣的地方,並且當那些事件發生時,就是這個對象告訴您所發生的事件。

咱們須要作的第一件事就是建立一個 Selector

Selector selector = Selector.open();

而後,咱們將對不一樣的通道對象調用 register() 方法,以便註冊咱們對這些對象中發生的 I/O 事件的興趣。register() 的第一個參數老是這個 Selector。

打開一個 ServerSocketChannel

爲了接收鏈接,咱們須要一個 ServerSocketChannel。事實上,咱們要監聽的每個端口都須要有一個 ServerSocketChannel 。對於每個端口,咱們打開一個 ServerSocketChannel,以下所示:

ServerSocketChannel ssc = ServerSocketChannel.op
en();
ssc.configureBlocking( false );//非阻塞
 
ServerSocket ss = ssc.socket();
InetSocketAddress address = new InetSocketAddress(ports[i]);
ss.bind(address);//綁定端口

第一行建立一個新的 ServerSocketChannel ,最後三行將它綁定到給定的端口。第二行將 ServerSocketChannel 設置爲 非阻塞的 。咱們必須對每個要使用的套接字通道調用這個方法,不然異步 I/O 就不能工做。

註冊

將新打開的 ServerSocketChannels 註冊到 Selector上

SelectionKey key = ssc.register( selector, SelectionKey.OP_ACCEPT );

內部循環

使用 Selector 的幾乎每一個程序都像下面這樣使用內部循環:

咱們調用 Selector 的 select() 方法。這個方法會阻塞,直到至少有一個已註冊的事件發生
select() 方法將返回所發生的事件的數量

int num = selector.select();

調用 Selector 的 selectedKeys() 方法,它返回發生了事件的 SelectionKey 對象的一個集合

Set selectedKeys = selector.selectedKeys();
Iterator it = selectedKeys.iterator();
 
while (it.hasNext()) {
     SelectionKey key = (SelectionKey)it.next();
     // ... deal with I/O event ...
}

接受新的鏈接

經過channel()方法能夠取得通道對象

ServerSocketChannel ssc = (ServerSocketChannel)key.channel();
SocketChannel sc = ssc.accept();

下一步是將新鏈接的 SocketChannel 配置爲非阻塞的

sc.configureBlocking( false );
SelectionKey newKey = sc.register( selector, SelectionKey.OP_READ );

關於Java IO和NIO的知識暫且學習到這裏,後面有時間會繼續補充!

參考

NIO入門

相關文章
相關標籤/搜索