讀書筆記:NIO的使用總結(jié)

概述:

NIO包是JDK1.4引入的新的I/O類庫环形,目的是為了提高文件讀寫的速度银室。NIO的讀寫模式和舊的I/O有一些不同磺浙,NIO是通過緩沖器和通道來對文件進行操作的护盈,當我們需要對文件進行數(shù)據(jù)的讀取時堂湖,我們先將數(shù)據(jù)讀取到緩沖器中闲先,再從緩沖器上讀取我們所要的數(shù)據(jù),當我們想往文件中寫入數(shù)據(jù)時无蜂,那么我們先朝著緩沖器寫入數(shù)據(jù)伺糠,再利用緩沖器往文件里進行寫入。在整個文件的讀寫操作時斥季,我們并沒有與文件進行直接的交互训桶。
我們先來看一個NIO讀寫文件的例子

public static void testChannel() throws IOException {
        //1.利用RandomAccessFile打開文件data.txt
        RandomAccessFile aFile = new RandomAccessFile(dir + "data.txt","rw");
        //2.獲取文件的通道
        FileChannel inChannel = aFile.getChannel();

        //3.創(chuàng)建一個字節(jié)緩存器
        ByteBuffer buf = ByteBuffer.allocate(48);
        //4.根據(jù)緩存器的大小將文件內(nèi)容讀取到緩存中
        int bytesRead = inChannel.read(buf);

        //5.根據(jù)讀取到的字節(jié)數(shù)做判斷
        while (bytesRead != -1){
            System.out.println("Read " + bytesRead);
            //切換緩沖區(qū)的模式
            buf.flip();

            //6.打印緩存中的內(nèi)容
            while (buf.hasRemaining()){
                System.out.print((char) buf.get());
            }
            System.out.println("");

            //7.清空緩存的內(nèi)容,假如不執(zhí)行這一步,read(buf)將會返回0,因為無法讀入到buf中
            buf.clear();
            bytesRead = inChannel.read(buf);
        }
        aFile.close();
    }

如上所示,這是一個利用Channel和緩沖器從文件中讀取數(shù)據(jù)的代碼酣倾,上面的代碼分為這幾個步驟:

  1. 打開一個文件
  2. 獲取文件的Channel
  3. 創(chuàng)建一個固定大小的緩沖器
  4. 將文件內(nèi)容讀入到緩沖器中
  5. 讀出緩沖器的內(nèi)容舵揭,讀完之后清空緩沖器以便接下來繼續(xù)讀

NIO的組成

如下所示,一個NIO的系統(tǒng)的組成大致包含下面的三大部分躁锡,分別是通道(Channel)午绳、緩沖器(Buffers)以及Selector


NIO包的主要組成部分

其中Selector的作用是可以在單個線程中管理多個通道的讀寫

Buffers(緩沖器)

緩沖器是文件數(shù)據(jù)讀取和寫入的一段內(nèi)存,當我們需要從通道中取數(shù)據(jù)時映之,我么先將通道中的數(shù)據(jù)讀到緩沖器中拦焚,再從緩沖器中獲取數(shù)據(jù)。當我們需要往通道中寫入數(shù)據(jù)時杠输,我們先將數(shù)據(jù)寫到緩沖器中耕漱,再把數(shù)據(jù)寫入通道。

緩沖器的三個標識

capacity抬伺、limitposition螟够,下面我們來分別介紹一下
capacity
表示緩存的容量,比如說我們使用下面的代碼創(chuàng)建了一個緩沖器

ByteBuffer buf = ByteBuffer.allocate(48);//分配48個Byte的緩沖器

那么緩沖器的capacity就是48,注意capacity的值與緩沖器的類型無關(guān)

limit

  1. 在讀模式下妓笙,limit表示緩沖器還有多少數(shù)據(jù)可以讀
  2. 在寫模式下若河,limit表示緩沖器還有多少空間可以往里寫

position
表示緩沖區(qū)的當前位置

  1. 在緩沖區(qū)切到寫模式時,position的值被設(shè)為0
  2. 在緩沖區(qū)切換到讀模式時寞宫,position的值被設(shè)為緩沖區(qū)中第一個能寫入的空間位置
buffer一些操作的方法
往buffer里面寫入數(shù)據(jù)
  1. 通過Channel往buffer里寫入數(shù)據(jù)萧福,如下所示
 int byteRead = inChannel.read(buf);

上面是往緩存buffer里面寫入數(shù)據(jù),并返回寫入到緩存中的字節(jié)個數(shù)

  1. 通過buffer的put()方法往緩存中寫入數(shù)據(jù)
buf.put(127);

往緩存中寫入ASCII編碼為127的數(shù)辈赋,寫入之后鲫忍,limit減1,position加1

flip()方法

這個方法是將緩存切換到讀模式以讀取緩存中的數(shù)據(jù)钥屈,具體的變化是將position的值設(shè)為0悟民,limit的值設(shè)為原先position的值,在這樣的情況下篷就,就可以讀取buffer中從positionlimit之間的所有數(shù)據(jù)

從buffer中讀取數(shù)據(jù)
  1. 從buffer中讀取數(shù)據(jù)到Channel
 int bytesWritten = inChannel.write(buf);

上面是將buffer中的數(shù)據(jù)讀取到Channel中射亏,并返回讀取數(shù)據(jù)的個數(shù)

  1. 使用get()方法讀取數(shù)據(jù),如下所示
byte aByte = buf.get();

獲取buffer中當前位置的值竭业,并將結(jié)果賦值給aByte

rewind()方法

這個方法的作用是重新讀取buffer里面的內(nèi)容智润,做法是,將position置為0未辆,而limit保持不變

clear()compact()方法

clear()方法是對buffer進行清空操作窟绷,但是并不是真正意義上的清空,而是修改其中的標識的值咐柜,將position的值設(shè)為0钾麸,而將limit的值設(shè)為capacity,這樣再往buffer里面寫入數(shù)據(jù)時就會覆蓋原先的內(nèi)容炕桨,已達到清空的目的饭尝,但是如果隨后沒有執(zhí)行寫操作,那么原來的數(shù)據(jù)還是能讀的出來
compact()則是將buffer中所有未讀的數(shù)據(jù)拷貝到buffer的起始位置献宫,再將position的值設(shè)置到最后一個未讀元素的后面

mark()reset()方法
mark()方法將buffer中的position的值記錄下來(賦值給mark)钥平,假如我們繼續(xù)往后面讀取數(shù)據(jù),這時候我們調(diào)用reset()方法時姊途,我們會回到我們之前標記的位置涉瘾,實際上是將mark的值回賦給position

equals()compareTo()方法
這兩個方法都是對兩個buffer之間的比較
對于equals(),滿足下面的幾個條件時捷兰,我們會返回true值,表示兩個buffer相同

  1. 兩個buffer有相同的類型
  2. Buffer中剩余的byte立叛、char等的個數(shù)相等
  3. Buffer中所有剩余的byte、char等都相同

從上面的條件我們可以看出贡茅,equals()方法實際上是比較從positionlimit之間的數(shù)據(jù)是否相等秘蛇,而對position之前的數(shù)據(jù)則并不關(guān)心

compareTo()方法在滿足下列所有條件時其做,表示一個Buffer小于另一個Buffer

  1. 第一個不相等的元素小于另一個Buffer中對應(yīng)的元素
  2. 所有元素都相等,但第一個Buffer比另一個先耗盡

compareTo()方法返回的是第一個buffer和第二個buffer中第一個不相等值的差

scatter和gather

scatter

scatter是指在讀操作時將一個Channel中的數(shù)據(jù)讀出到多個buffer中去赁还,代碼如下所示

  ByteBuffer header = ByteBuffer.allocate(128);
  ByteBuffer body   = ByteBuffer.allocate(1024);
  ByteBuffer[] bufferArray = { header, body };
  channel.read(bufferArray);

如上的代碼是一個將一個Channel中的數(shù)據(jù)讀出到多個buffer中的例子妖泄,在讀出的過程中,會沿著buffer數(shù)組的下標依次進行填滿艘策,這樣的做法不適合于動態(tài)的信息

gather

gather指在寫操作時將多個buffer的數(shù)據(jù)寫入到同一個Channel蹈胡,代碼如下所示

  ByteBuffer header = ByteBuffer.allocate(128);
  ByteBuffer body   = ByteBuffer.allocate(1024);
  ByteBuffer[] bufferArray = { header, body };
  channel.write(bufferArray);

如上的代碼是將不同的buffer沿著buffer的下標依次寫入到Channel中去,這樣的操作很適合處理動態(tài)的信息

Channel之間的數(shù)據(jù)傳輸

利用Channel的transferFrom()transferTo()方法可以在不借助額外的buffer而完成兩個Channel之間的數(shù)據(jù)傳輸

transferFrom()

將任何Channel中的數(shù)據(jù)傳輸?shù)紽ileChannel中去朋蔫,示例代碼如下所示

  RandomAccessFile fromFile = new RandomAccessFile("fromFile.txt", "rw");
  FileChannel      fromChannel = fromFile.getChannel();
  RandomAccessFile toFile = new RandomAccessFile("toFile.txt", "rw");
  FileChannel      toChannel = toFile.getChannel();
  long position = 0;
  long count = fromChannel.size();
  toChannel.transferFrom(position, count, fromChannel);
transferTo()

將FileChannel中的數(shù)據(jù)傳輸?shù)饺我獾腃hannel中去罚渐,示例代碼如下

  RandomAccessFile fromFile = new RandomAccessFile(dir + "data.txt","rw");
  FileChannel fromChannel = fromFile.getChannel();

  RandomAccessFile toFile = new RandomAccessFile(dir + "data2.txt","rw");
  FileChannel toChannel = toFile.getChannel();

  long position = 0;
  long count = fromChannel.size();

  fromChannel.transferTo(position,count,toChannel);

Selector

Selector的作用是在單一的線程下對多個Channel中的數(shù)據(jù)進行管理,這樣就很有利于對于單個Channel數(shù)據(jù)量少驯妄,但是Channel的總數(shù)多的情形進行管理

創(chuàng)建一個Selector
  Selector selector = Selector.open();
向Selector注冊通道
  channel.configureBlocking(false);//將Channel設(shè)為非阻塞
  SelectionKey key = channel.register(selector,SelectionKey.OP_READ);//將Channel注冊到Selector對象上去

注冊在Selector中的Channel必須處于非阻塞模式荷并,這意味著不能將Selector和FileChannel一起使用,因為FileChannel是阻塞通道

interest集合

Channel的register方法的第二個參數(shù)是interest的集合富玷,意味著第二個參數(shù)是多個類型的疊加,類型包括:

  Connect(OP_CONNECT)
  Accept(OP_ACCEPT)
  Read(OP_READ)
  Write(OP_WRITE)
  第二個參數(shù)不僅可以是上面4個單獨的類型,而且可以是上面4個類型的疊加既穆,即使用按位或的方式赎懦,例如: SelectionKey.OP_READ | SelectionKey.OP_ACCEPT,這表示對這個Channel的連接和讀取感興趣
SelectionKey

當Channel向Selector注冊時,會返回一個SelectionKey的對象幻工,該對象包含以下的屬性

  interest集合(int interestSet = selectionKey.readOps();)
  ready集合(int readySet = selectionKey.readyOps();)
  Channel(Channel channel = selectionKey.channel();)
  Selector(Selector selector = selectionKey.selector();)
  附加的對象
select()

這個方法表示對注冊在Selector上的Channel進行選擇励两,select()的方法有幾種變體,如下
int select():阻塞并一直等到通道上有一個Channel中發(fā)生了其在注冊時指定的事件囊颅,比如定義了OP_READ屬性且出現(xiàn)了Channel的讀取行為
int select(long timeout):阻塞并且等到設(shè)置的時間后自動返回
int selectNow():立即返回当悔,不管有沒有選擇到正在發(fā)生注冊操作的Channel

selectedKeys()

在使用select()方法后會知道有一個或多個通道已經(jīng)處于就緒狀態(tài)了,那么此時可以使用selectedKeys()來獲取SelectionKey對象的集合踢代,如下所示

  Set selectedKeys = selector.selectedKeys();

對這些結(jié)果集的操作如下列代碼所示

  Set selectedKeys = selector.selectedKeys();
  Iterator keyIterator = selectedKeys.iterator();
  while(keyIterator.hasNext()) {
      SelectionKey key = keyIterator.next();
      if(key.isAcceptable()) {
         // a connection was accepted by a ServerSocketChannel.
      } else if (key.isConnectable()) {
        // a connection was established with a remote server.
      } else if (key.isReadable()) {
        // a channel is ready for reading
      } else if (key.isWritable()) {
        // a channel is ready for writing
      }
      keyIterator.remove();//Selector不會自己移除SelectionKey實例盲憎,因此需要利用這個方法來進行移除操作
  }
wakeUp()

如果一個線程正在執(zhí)行select()并且因此而處于阻塞的狀態(tài),那么可以讓其他線程在第一個處于調(diào)用select()而阻塞的線程上調(diào)用Selector.WakeUp()而立即返回

close()

用完Selector后可以利用close()方法來關(guān)閉Selector胳挎,這樣會使得注冊在其上的Channel無效饼疙,但是這并不能關(guān)閉Channel

FileChannel

FileChannel是文件操作的通道,其操作的方法見下面

打開FileChannel
  RandomAccessFile aFile = new RandomAccessFile("data/nio-data.txt", "rw");
  FileChannel inChannel = aFile.getChannel();
從FileChannel讀取數(shù)據(jù)
  ByteBuffer buf = ByteBuffer.allocate(48);
  int bytesRead = inChannel.read(buf);
向FileChannel中寫入數(shù)據(jù)
  String newData = "New String to write to file..." + System.currentTimeMillis();
  ByteBuffer buf = ByteBuffer.allocate(48);
  buf.clear();
  buf.put(newData.getBytes());
  buf.flip();
  while(buf.hasRemaining()) {
      channel.write(buf);
  }
用完FileChannel之后關(guān)閉Channel
  channel.close();
position方法

position方法有兩種形式慕爬,帶參數(shù)和不帶參數(shù)窑眯,例子如下

  long pos = channel.position();//無參形式為獲取當前通道的當前位置
  channel.position(pos + 123);//帶參形式為設(shè)置通道的當前位置

需注意的是,在設(shè)置通道當前位置的時候医窿,假如設(shè)置的位置超出了文件長度磅甩,并且會在設(shè)置的該位置處寫下數(shù)據(jù),那么就會造成文件的空洞

size()

獲取通道關(guān)聯(lián)的文件的大小

  long fileSize = channel.size();
truncate(int)

從頭截取通道關(guān)聯(lián)文件的大小,并且丟棄掉后面的數(shù)據(jù)

  channel.truncate(1024);//只需要關(guān)聯(lián)文件的頭1024個字節(jié)
force()

將通道中尚未寫入到磁盤里的數(shù)據(jù)寫入到磁盤里

  channel.force(true);

SocketChannel

SocketChannel是一個連接到TCP套接字上的通道姥卢,創(chuàng)建一個SocketChannel的方式有下面兩種

  1. 打開一個SocketChannel并連接到互聯(lián)網(wǎng)上的某臺服務(wù)器卷要。
  2. 一個新連接到達ServerSocketChannel時渣聚,會創(chuàng)建一個SocketChannel
打開SocketChannel
  SocketChannel socketChannel = SocketChannel.open();
  socketChannel.connect(new InetSocketAddress("http://www.baidu.com",80));
關(guān)閉SocketChannel
  socketChannel.close();
從SocketChannel中讀取數(shù)據(jù)
  ByteBuffer buf = ByteBuffer.allocate(48);
  int bytesRead = socketChannel.read(buf);
寫入SocketChannel
  String newData = "New String to write to file..." + System.currentTimeMillis();
  ByteBuffer buf = ByteBuffer.allocate(48);
  buf.clear();
  buf.put(newData.getBytes());
  buf.flip();
  while(buf.hasRemaining()) {
      channel.write(buf);
  }
非阻塞模式
  socketChannel.configureBlocking(false);//將SocketChannel對象設(shè)為非阻塞
  socketChannel.connect(new InetSocketAddress("http://jenkov.com",80));
  while(! socketChannel.finishConnect()){
  }
write()

在非阻塞模式下,write()方法在尚未寫出任何內(nèi)容時就可能返回了却妨,所以在循環(huán)中調(diào)用write()

read()

非阻塞模式下饵逐,read()方法在尚未讀取到任何數(shù)據(jù)時就可能返回了,所以要關(guān)注其返回的字節(jié)數(shù)

DatagramChannel

DatagramChannel的打開方式
  DatagramChannel channel = DatagramChannel.open();
  channel.socket().bind(new InetSocketAddress(9999));

打開一個DatagramChannel彪标,并將這個Channel綁定到UDP的9999端口

接收數(shù)據(jù)
  ByteBuffer buf = ByteBuffer.allocate(48);
  buf.clear();
  channel.receive(buf);//利用分配的buffer進行接收數(shù)據(jù)倍权,超出buffer大小的數(shù)據(jù)將被丟棄
發(fā)送數(shù)據(jù)
  String newData = "New String to write to file..." +   System.currentTimeMillis();
  ByteBuffer buf = ByteBuffer.allocate(48);
  buf.clear();
  buf.put(newData.getBytes());
  buf.flip();
  int bytesSent = channel.send(buf, new InetSocketAddress("fanwn.com", 80));

以上一個發(fā)送數(shù)據(jù)的代碼,由于其發(fā)送的地址并沒有處于監(jiān)聽狀態(tài)捞烟,因此這樣不會發(fā)生任何反應(yīng)

連接到特定的地址
  channel.connect(new InetSocketAddress("fanwn.com",80));
  int bytesRead = channel.read(buf);
  int bytesWritten = channel.write(buf);

將Channel連接到特定的地址薄声,可以讀取通道中的數(shù)據(jù)到buffer中,也可以將buffer中的數(shù)據(jù)讀取到通道中

Pipe

管道是對兩個線程之間交換數(shù)據(jù)的方式题画,一個管道包含了兩個通道類:sink通道和source通道,其中sink是往通道里面寫入數(shù)據(jù)默辨,source是從通道內(nèi)讀出數(shù)據(jù)

打開管道
  Pipe pipe = Pipe.open();
向管道寫入數(shù)據(jù)
  Pipe.SinkChannel sinkChannel = pipe.sink();
  String newData = "New String to write to file..." + System.currentTimeMillis();
  ByteBuffer buf = ByteBuffer.allocate(48);
  buf.clear();
  buf.put(newData.getBytes());
  buf.flip();
  while(buf.hasRemaining()) {
      sinkChannel.write(buf);
  }
從管道中讀出數(shù)據(jù)
  Pipe.SourceChannel sourceChannel = pipe.source();
  ByteBuffer buf = ByteBuffer.allocate(48);
  int bytesRead = sourceChannel.read(buf);
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市苍息,隨后出現(xiàn)的幾起案子缩幸,更是在濱河造成了極大的恐慌,老刑警劉巖竞思,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件表谊,死亡現(xiàn)場離奇詭異,居然都是意外死亡盖喷,警方通過查閱死者的電腦和手機爆办,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來课梳,“玉大人距辆,你說我怎么就攤上這事∧喝校” “怎么了跨算?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長椭懊。 經(jīng)常有香客問我漂彤,道長,這世上最難降的妖魔是什么灾搏? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任挫望,我火速辦了婚禮,結(jié)果婚禮上狂窑,老公的妹妹穿的比我還像新娘媳板。我一直安慰自己,他們只是感情好泉哈,可當我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布蛉幸。 她就那樣靜靜地躺著破讨,像睡著了一般。 火紅的嫁衣襯著肌膚如雪奕纫。 梳的紋絲不亂的頭發(fā)上提陶,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天,我揣著相機與錄音匹层,去河邊找鬼隙笆。 笑死,一個胖子當著我的面吹牛升筏,可吹牛的內(nèi)容都是我干的撑柔。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼您访,長吁一口氣:“原來是場噩夢啊……” “哼铅忿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起灵汪,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤檀训,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后享言,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體峻凫,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年担锤,在試婚紗的時候發(fā)現(xiàn)自己被綠了蔚晨。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片乍钻。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡肛循,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出银择,到底是詐尸還是另有隱情多糠,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布浩考,位于F島的核電站夹孔,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏析孽。R本人自食惡果不足惜搭伤,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望袜瞬。 院中可真熱鬧怜俐,春花似錦、人聲如沸邓尤。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至季稳,卻和暖如春擅这,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背景鼠。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工仲翎, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人莲蜘。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓谭确,卻偏偏與公主長得像,于是被迫代替她去往敵國和親票渠。 傳聞我的和親對象是個殘疾皇子逐哈,可洞房花燭夜當晚...
    茶點故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容