JAVA NIO

NIO是非阻塞的IO,Java NIO由一下幾個核心部分組成:BuffersChannelsSelectors

Java NIO中有很多類和組件,但是BuffersChannelsSelectors構成了核心的API。其他組件如PipeFileLock,只不過是與其他三個核心組件共同使用的工具類。

  • Channel:基本上所有的IO在NIO中都從一個Channel開始,Channel有點像流。數據可以從Channel讀到Buffer中,也可以從Buffer寫到Channel中。Channel和Buffer有好多類型,Channel主要有:FileChannel、DataGramChannel、SocketChannel、ServerSocketChannel。涵蓋了UDP和TCP網絡的IO以及文件IO。
  • Buffer:NIO主要的Buffer有:ByteBuffer、CharBuffer、DoubleBuffer、FloatBuffer、IntBuffer、LongBuffer、ShortBuffer,這些Buffer涵蓋了你能通過IO發送的基本數據類型。
  • Selector:允許單線程處理多個Channel。如果你的應用打開了多個連接(通道),但每個連接的流量都很低,使用Selector就會很方便。例如一個聊天服務器中。要使用Selector,得先向Selector注冊Channel然后調用它的select()方法。這個方法會一直堵塞知道某個注冊的通道有事件就緒。一旦這個方法返回線程就可以處理這些事件,事件的例子有如新連接進來,數據接收等。

緩沖區&Buffer

在整個Java新IO中,所有的操作都是以緩沖區進行的,使用緩沖區,則操作的性能將是最高的。

在Buffer中存在一系列的狀態變量,隨著寫入或讀取都有可能被改變,在緩沖區可以使用三個值表示緩沖區狀態

  • position 位置
  • limit 界限
  • capacity 容量

ByteBuffer的使用

  1. 創建ByteBuffer

(1)使用allocate()靜態方法ByteBuffer buffer=ByteBuffer.allocate(256);
以上方法將創建一個容量為256字節的ByteBuffer,如果發現創建的緩沖區容量太小,唯一的選擇就是重新創建一個大小合適的緩沖區.

(2)通過包裝一個已有的數組來創建
如下,通過包裝的方法創建的緩沖區保留了被包裝數組內保存的數據.
ByteBuffer buffer=ByteBuffer.wrap(byteArray);
如果要將一個字符串存入ByteBuffer,可以如下操作:
String sendString="你好,服務器. "; ByteBuffer sendBuffer=ByteBuffer.wrap(sendString.getBytes("UTF-16"));

  1. 緩沖區
  • buffer.flip();

這個方法用來將緩沖區準備為數據傳出狀態,執行以上方法后,輸出通道會從數據的開頭而不是末尾開始.回繞保持緩沖區中的數據不變,只是準備寫入而不是讀取.

  • buffer.clear();

這個方法實際上也不會改變緩沖區的數據,而只是簡單的重置了緩沖區的主要索引值.不必為了每次讀寫都創建新的緩沖區,那樣做會降低性能.相反,要重用現在的緩沖區,在再次讀取之前要清除緩沖區.

ByteBuffer轉為其他的Buffer,如:CharBuffer、DoubleBuffer、IntBuffer、LongBuffer、ShortBuffer,都有對應的asXXXBuffer()方法。

  • 創建子緩沖區 buf.slice()
    子緩沖區可以修改數據
import java.nio.IntBuffer ;
public class IntBufferDemo02{
 public static void main(String args[]){
   IntBuffer buf = IntBuffer.allocate(10) ;  // 準備出10個大小的緩沖區
   IntBuffer sub = null ;  // 定義子緩沖區
   for(int i=0;i<10;i++){
     buf.put(2 * i + 1) ;  // 在主緩沖區中加入10個奇數
   }
   
   // 需要通過slice() 創建子緩沖區
   buf.position(2) ;
   buf.limit(6) ;
   sub = buf.slice() ;
   for(int i=0;i<sub.capacity();i++){
     int temp = sub.get(i) ;
     sub.put(temp-1) ;
   }

   buf.flip() ;  // 重設緩沖區
   buf.limit(buf.capacity()) ;
   System.out.print("主緩沖區中的內容:") ;
   while(buf.hasRemaining()){
     int x = buf.get() ;
     System.out.print(x + "、") ;
   }
 }
}
  • 創建只讀緩沖區 buf.asReadOnlyBuffer()
import java.nio.IntBuffer ;
public class IntBufferDemo03{
  public static void main(String args[]){
    IntBuffer buf = IntBuffer.allocate(10) ;  // 準備出10個大小的緩沖區
    IntBuffer read = null ; // 定義子緩沖區
    for(int i=0;i<10;i++){
      buf.put(2 * i + 1) ;  // 在主緩沖區中加入10個奇數
    }
    read = buf.asReadOnlyBuffer()  ;// 創建只讀緩沖區
    
    read.flip() ; // 重設緩沖區
    System.out.print("主緩沖區中的內容:") ;
    while(read.hasRemaining()){
      int x = read.get() ;
      System.out.print(x + "、") ;
    }
    read.put(30) ;  // 修改,錯誤
  }
}
  • 創建直接緩沖區 public static ByteBuffer allocateDirect(int capacity)
    只能提高一些盡可能的性能。
import java.nio.ByteBuffer ;
public class ByteBufferDemo01{
  public static void main(String args[]){
    ByteBuffer buf = ByteBuffer.allocateDirect(10) ;  // 準備出10個大小的緩沖區
    byte temp[] = {1,3,5,7,9} ; // 設置內容
    buf.put(temp) ; // 設置一組內容
    buf.flip() ;

    System.out.print("主緩沖區中的內容:") ;
    while(buf.hasRemaining()){
      int x = buf.get() ;
      System.out.print(x + "、") ;
    }
  }
}

通道

  • 可讀、可寫。程序不會直接操作通道,所有的內容讀到或者寫入緩沖區,再通過緩沖區中取得或寫入。
  • 傳統的流操作分為輸入或輸出流,而通道本身是雙向操作的,可以完成輸入也可以完成輸出。

讀入方式

  • RandomAccessFile:較慢
  • FileInputStream:較慢
  • 緩存讀取:速度較快
  • 內存映射MapByteBuffer:最快!

FileChannel的三種內存映射模式

NO. 常量 類型 描述
1 public static final FileChannel.MapMode.READ_ONLY 常量 只讀映射模式
2 public static final FileChannel.MapMode.READ_WRITE 常量 讀取/寫入映射模式
3 public static final FileChannel.MapMode.PRIVATE 常量 專用(寫入時拷貝)映射模式
import java.nio.ByteBuffer ;
import java.nio.MappedByteBuffer ;
import java.nio.channels.FileChannel ;
import java.io.File ;
import java.io.FileOutputStream ;
import java.io.FileInputStream ;
public class FileChannelDemo03{
  public static void main(String args[]) throws Exception{
    File file = new File("d:" + File.separator + "mldn.txt") ;  
    FileInputStream input = null ;
    input = new FileInputStream(file) ;
    FileChannel fin = null ;  // 定義輸入的通道
    fin = input.getChannel() ;  // 得到輸入的通道
    MappedByteBuffer mbb = null ; 
    mbb = fin.map(FileChannel.MapMode.READ_ONLY,0,file.length()) ;
    byte data[] = new byte[(int)file.length()] ;  // 開辟空間接收內容
    int foot = 0 ;
    while(mbb.hasRemaining()){
      data[foot++] = mbb.get() ;  // 讀取數據
    }
    System.out.println(new String(data)) ;  // 輸出內容
    fin.close() ;
    input.close() ;
  }
}

內存映射在讀取時速度快,但是如果在使用以上操作代碼的時候,執行的是寫入操作則有可能非常危險,因為僅僅是改變數組中單個元素這種簡單的操作,就可能直接修改磁盤上的文件,因為修改數據與將數據保存在磁盤上是一樣的。


文件鎖FileLock

當一個線程將文件鎖定之后,其它線程無法操作此文件,要想進行文件鎖定操作,需要使用FileLock類完成,此類的對象需要依靠FileChannel進行實例化操作。

實例化`FileLock`對象的方法

鎖定方式

  • 共享鎖:允許多個線程進行文件的讀/寫操作

  • 獨占鎖:只允許一個線程進行文件的讀/寫操作


字符集 Charset

整個NIO中,對于不同平臺的編碼操作,java都可以進行自動適應,因為可以使用字符集進行字符編碼的轉換。

在java中,所有信息都是以UNICODE進行編碼,計算機中存在多種編碼,NIO中提供了charset類來處理編碼問題,包括了創建編碼器(CharsetEndoder)和創建解碼器(CharsetDecoder)的操作。

得到所有字符集
        Charset latin1 = Charset.forName("ISO-8859-1") ;  // 只能表示的英文字符
    CharsetEncoder encoder = latin1.newEncoder() ;  // 得到編碼器
    CharsetDecoder decoder = latin1.newDecoder() ;  // 得到解碼器
    // 通過CharBuffer類中的
    CharBuffer cb = CharBuffer.wrap("啊哈哈哈哈") ;
    ByteBuffer buf = encoder.encode(cb) ; // 進行編碼操作
    System.out.println(decoder.decode(buf)) ; // 進行解碼操作

Selector

解決服務器端的通訊性能。

  • 使用Selector可以構建一個非阻塞的網絡服務
  • 在NIO中實現網絡程序需要依靠ServerSocketChannel類與SocketChannel
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.util.Set;
import java.util.Iterator;
import java.util.Date;
import java.nio.channels.ServerSocketChannel;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
import java.nio.channels.Selector;
import java.nio.channels.SelectionKey;

public class DateServer {
  public static void main(String args[]) throws Exception {
    int ports[] = { 8000, 8001, 8002, 8003, 8005, 8006 }; // 表示五個監聽端口
    Selector selector = Selector.open(); // 通過open()方法找到Selector
    for (int i = 0; i < ports.length; i++) {
      ServerSocketChannel initSer = null;
      initSer = ServerSocketChannel.open(); // 打開服務器的通道
      initSer.configureBlocking(false); // 服務器配置為非阻塞
      ServerSocket initSock = initSer.socket();
      InetSocketAddress address = null;
      address = new InetSocketAddress(ports[i]); // 實例化綁定地址
      initSock.bind(address); // 進行服務的綁定
      initSer.register(selector, SelectionKey.OP_ACCEPT); // 等待連接
      System.out.println("服務器運行,在" + ports[i] + "端口監聽。");
    }
    // 要接收全部生成的key,并通過連接進行判斷是否獲取客戶端的輸出
    int keysAdd = 0;
    while ((keysAdd = selector.select()) > 0) { // 選擇一組鍵,并且相應的通道已經準備就緒
      Set<SelectionKey> selectedKeys = selector.selectedKeys();// 取出全部生成的key
      Iterator<SelectionKey> iter = selectedKeys.iterator();
      while (iter.hasNext()) {
        SelectionKey key = iter.next(); // 取出每一個key
        if (key.isAcceptable()) {
          ServerSocketChannel server = (ServerSocketChannel) key.channel();
          SocketChannel client = server.accept(); // 接收新連接
          client.configureBlocking(false);// 配置為非阻塞
          ByteBuffer outBuf = ByteBuffer.allocateDirect(1024); //
          outBuf.put(("當前的時間為:" + new Date()).getBytes()); // 向緩沖區中設置內容
          outBuf.flip();
          client.write(outBuf); // 輸出內容
          client.close(); // 關閉
        }
      }
      selectedKeys.clear(); // 清楚全部的key
    }

  }
}

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,527評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,687評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,640評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,957評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,682評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,011評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,009評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,183評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,714評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,435評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,665評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,148評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,838評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,251評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,588評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,379評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,627評論 2 380

推薦閱讀更多精彩內容