1. 程式人生 > >Java I/O(一) NIO概述

Java I/O(一) NIO概述

基本概念

  • BIO:是阻塞I/O,不管是磁碟I/O,還是網路I/O,資料在寫入OutputStream和InputStream都可能發生阻塞,一旦有阻塞,執行緒會失去CPU的使用權(阻塞)。
  • NIO:簡單的說就是非阻塞式I/O(單個I/O阻塞時不阻塞執行緒),一個執行緒可以負責多個I/O連線(利用serverSocketChannels來接收),取一個準備好接收資料的連線(註冊到Selector輪詢排程),儘快地用連線向快取區(利用buffer優化I/O)填充資料,然後轉向下一個準備好的連線。
  • 快取區(buffer):通訊通道(channel)用來傳輸資料的介質,在NOI模型中,不再通向輸出流寫入資料或從輸入流讀取資料,而是在快取區中讀寫資料,能夠有效減少I/O中斷次數,調優I/O。(我會寫部落格專門講快取區的…連結留個位置)
  • 通道(channel):負責將緩衝區的資料塊移入或移出到各種I/O源(我會寫部落格專門講通道的…連結留個位置)
  • 就緒選擇(selector):為完成就緒選擇,要將不同的通道註冊到一個Selector物件。每個通道分配有一個SeletionKey。然後程式可以通過詢問Selector物件,得知哪些通道已經準備就緒,可以無阻塞地完成I/O操作,可以向Selector物件查詢相應的鍵集合。

通道和I/O流的區別

  • 流和通道間的關鍵區別是流是基於位元組的,而通道是基於塊的,塊的單次中斷傳輸的資料量遠大於位元組,所以效能是有優勢,當然,出於效能考慮,流也可以傳輸位元組陣列。
  • 通常情況下(如網路通道),通道的緩衝區支援單個通道的讀寫
    ,而流是只讀或者只寫的。CDROM是隻讀通道,但這只是特例

理解I/O:效能的瓶頸

  • 現代計算機基於馮諾依曼的儲存執行模型,所以資料在計算機部件間的傳輸速率決定了計算機的執行效率,但是各級儲存(CPU暫存器、快取、記憶體、硬碟)的傳輸速度差異巨大(數量級上的差距),在傳統的BIO模式下,這導致高速計算部件的大量時間浪費在等待資料傳輸上。然而計算機作為節點的計算機網路中,問題依舊存在。
  • 傳輸速度上:CPU>>>()
  • 傳統的做法是通過快取多執行緒解決這一問題:
    • 快取記憶體能夠減少中斷次數(單次傳輸的資料量增大,資料總量不變)和中斷的時間(快取一般比原始儲存位置傳輸速率快)
    • 多執行緒可以讓單個執行緒處理一個I/O,不會影響執行緒獲得CPU資源,但是當I/O連線數量增多時,執行緒的數量隨之增加,生成多個執行緒以及執行緒之間切換的開銷是不容忽略的,執行緒管理的開銷(時間+資源)極大地降低了系統性能
    • 借鑑多執行緒對於I/O的解決方案,我們進一步解決的就是優化掉建立執行緒和執行緒間切換帶來的巨大的開銷,那麼也就是採用單個執行緒非阻塞地管理多個I/O連線,也就是我們要講的NIO

例講Channel和buffer的簡單使用

枯燥的講解API沒有意義,也太沒意思了,所以我採取更加直觀的例子來說明API,不嚴謹但更簡單直觀,首先為了方便測試我們利用Channel寫的Client,我們先用簡單的幾行程式碼實現一個本地的測試伺服器:

package com.company;

import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class TestServer {

    public static void main(String[] args){
        //伺服器監聽請求的埠
        int port = 9999;
        ServerSocket server=null;
        try {
            server = new ServerSocket(port);
        }catch( IOException e ){
            System.out.println("伺服器建立失敗");
            e.printStackTrace();
        }
        Socket temp=null;
        try{
            temp = server.accept();
        }catch( IOException e ){
            System.out.println("獲取連線失敗");
            e.printStackTrace();
        }

        OutputStream output = null;
        try{
            output = temp.getOutputStream();
        }catch ( Exception e ){
            e.printStackTrace();
        }
        byte [] buffer = "llin of Tianjin University love JAVA and Alibaba!".getBytes();
        while ( true ) {
            try {
                output.write(buffer);
            } catch (IOException e) {
                e.printStackTrace();
            }
            try {
                //防止傳輸資訊過於快,不方便我們測試
                Thread.sleep(1000);
            }catch ( InterruptedException e ){
                e.printStackTrace();
            }
        }
    }
}

主要作用是間隔一段時間向客戶端傳送一段資訊,用來測試客戶端的channel是否實際發揮了作用
下面是一個例子,可以用來簡單熟悉一下JAVA中的Buffer和Channel介面的使用,雖然實際中這樣使用的意義不大

package com.company;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;

/**
 * Created by liulin on 16-4-12.
 */
public class TestClient {

    //預設的IP
    public static final String ipAddress = "127.0.0.1";
    //預設埠
    public static final int  DEFAULT_PORT = 9999;
    //快取的大小
    public static final int BUFFER_SIZE = 200;

    public static void  main ( String [] args ){

        if ( args.length == 0 ){//如果沒有給出IP地址那麼沒辦法找到測試伺服器啊
            System.out.println( "請輸入伺服器的IP地址或域名");
        }

        //給定埠那麼使用指定埠,否則使用預設埠
        int port=0;
        try{
            port = Integer.parseInt(args[1]);
        }catch ( Exception e ){
            port = DEFAULT_PORT;
            e.printStackTrace();
        }

        try{//本例只是讓大家熟悉JAVA提供的Buffer,Channel的API,讓不熟悉Socket的同學學習下Socket機制
            //所以不採用非阻塞機制,也不使用selector
            SocketAddress address = new InetSocketAddress( ipAddress , port );
            //通過靜態工廠獲得指定address的通道例項
            SocketChannel client = SocketChannel.open(address);
            //通過靜態工廠獲得指定大小的快取
            ByteBuffer buffer = ByteBuffer.allocate( BUFFER_SIZE );
            WritableByteChannel out = Channels.newChannel( System.out );//輸出通道
            //從伺服器讀取資料
            while ( client.read(buffer) != -1 ){
                //將快取的position置為buffer內建陣列的初始位置,當前position位置設定為limit
                //position,limit,capacity的概念會在專門介紹buffer的部落格中一齊講解
                //簡單地講,就是告訴buffer,我們要開始從頭讀取/修改你了
                buffer.flip();
                out.write( buffer );
                //為了方便檢視除錯資訊,輸出一個換行
                System.out.write( "\n".getBytes());
                //只是修改position為buffer內建陣列初始位置,limit設定為capacity
                //目前可以簡單地理解為清空快取(詳情我會在專門介紹buffer的部落格中一齊講解)
                buffer.clear();
            }

        }catch ( Exception e ){
            e.printStackTrace();
        }


    }
}

主要就是利用Channel連線到伺服器並且通過buffer進行讀寫,輸出到控制檯的一個簡單的例子,執行效果如下(測試的時候一定要先開啟測試伺服器):
這裡寫圖片描述

用一個NIO的HTTP伺服器講解NIO

作為一個有著TDD思維的程式猴子,怎麼能不先寫測試用的客戶端呢….其實只是無恥地在之前客戶端添了一行程式碼

package com.company;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;

/**
 * Created by liulin on 16-4-12.
 */
public class TestClient {

    //預設的IP
    public static final String ipAddress = "127.0.0.1";
    //預設埠
    public static final int  DEFAULT_PORT = 9999;
    //快取的大小
    public static final int BUFFER_SIZE = 200;

    public static void  main ( String [] args ){

        if ( args.length == 0 ){//如果沒有給出IP地址那麼沒辦法找到測試伺服器啊
            System.out.println( "請輸入伺服器的IP地址或域名");
        }

        //給定埠那麼使用指定埠,否則使用預設埠
        int port=0;
        try{
            port = Integer.parseInt(args[1]);
        }catch ( Exception e ){
            port = DEFAULT_PORT;
            e.printStackTrace();
        }

        try{//本例只是讓大家熟悉JAVA提供的Buffer,Channel的API,讓不熟悉Socket的同學學習下Socket機制
            //所以不採用非阻塞機制,也不使用selector
            SocketAddress address = new InetSocketAddress( ipAddress , port );
            //通過靜態工廠獲得指定address的通道例項
            SocketChannel client = SocketChannel.open(address);
            //通過靜態工廠獲得指定大小的快取
            ByteBuffer buffer = ByteBuffer.allocate( BUFFER_SIZE );
            WritableByteChannel out = Channels.newChannel( System.out );//輸出通道
            //從伺服器讀取資料
            client.write( ByteBuffer.wrap("testing...".getBytes()));

            while ( client.read(buffer) != -1 ){
                //將快取的position置為buffer內建陣列的初始位置,當前position位置設定為limit
                //position,limit,capacity的概念會在專門介紹buffer的部落格中一齊講解
                //簡單地講,就是告訴buffer,我們要開始從頭讀取/修改你了
                buffer.flip();
                out.write( buffer );
                //為了方便檢視除錯資訊,輸出一個換行
                System.out.write( "\n".getBytes());
                //只是修改position為buffer內建陣列初始位置,limit設定為capacity
                //目前可以簡單地理解為清空快取(詳情我會在專門介紹buffer的部落格中一齊講解)
                buffer.clear();
            }

        }catch ( Exception e ){
            e.printStackTrace();
        }
    }
}

下面是由selector排程的HTTP伺服器,講解寫在了註釋中,如果還有不明白的可以在評論中問

package com.company;

import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;

/**
 * Created by liulin on 16-4-12.
 */
public class TestClient {

    //預設的IP
    public static final String ipAddress = "127.0.0.1";
    //預設埠
    public static final int  DEFAULT_PORT = 9999;
    //快取的大小
    public static final int BUFFER_SIZE = 200;

    public static void  main ( String [] args ){

        if ( args.length == 0 ){//如果沒有給出IP地址那麼沒辦法找到測試伺服器啊
            System.out.println( "請輸入伺服器的IP地址或域名");
        }

        //給定埠那麼使用指定埠,否則使用預設埠
        int port=0;
        try{
            port = Integer.parseInt(args[1]);
        }catch ( Exception e ){
            port = DEFAULT_PORT;
            e.printStackTrace();
        }

        try{//本例只是讓大家熟悉JAVA提供的Buffer,Channel的API,讓不熟悉Socket的同學學習下Socket機制
            //所以不採用非阻塞機制,也不使用selector
            SocketAddress address = new InetSocketAddress( ipAddress , port );
            //通過靜態工廠獲得指定address的通道例項
            SocketChannel client = SocketChannel.open(address);
            //通過靜態工廠獲得指定大小的快取
            ByteBuffer buffer = ByteBuffer.allocate( BUFFER_SIZE );
            WritableByteChannel out = Channels.newChannel( System.out );//輸出通道
            //從伺服器讀取資料
            client.write( ByteBuffer.wrap("testing...".getBytes()));

            while ( client.read(buffer) != -1 ){
                //將快取的position置為buffer內建陣列的初始位置,當前position位置設定為limit
                //position,limit,capacity的概念會在專門介紹buffer的部落格中一齊講解
                //簡單地講,就是告訴buffer,我們要開始從頭讀取/修改你了
                buffer.flip();
                out.write( buffer );
                //為了方便檢視除錯資訊,輸出一個換行
                System.out.write( "\n".getBytes());
                //只是修改position為buffer內建陣列初始位置,limit設定為capacity
                //目前可以簡單地理解為清空快取(詳情我會在專門介紹buffer的部落格中一齊講解)
                buffer.clear();
            }

        }catch ( Exception e ){
            e.printStackTrace();
        }
    }
}

測試結果如下:
這裡寫圖片描述
具體的細節,我還會在新的部落格裡詳談,這篇篇幅太長了

參考書目

Java網路程式設計(第三版)
深入分析Java Web技術內幕

相關推薦

Java I/O() NIO概述

基本概念 BIO:是阻塞I/O,不管是磁碟I/O,還是網路I/O,資料在寫入OutputStream和InputStream都可能發生阻塞,一旦有阻塞,執行緒會失去CPU的使用權(阻塞)。 NIO:簡單的說就是非阻塞式I/O(單個I/O阻塞時不阻塞執行緒),

Java NIO學習筆記---I/ONIO概述

文章目錄 一、什麼是IO 二、什麼是Java NIO 三、I/O常見概念 3.1 DMA 3.2 核心空間和使用者空間 3.3 虛擬記憶體 3.4 現代作業系統的分頁技術 3.5 面向塊(檔案)的I/O和流I/

java I/O總結(

roc rec blob term interface new 數據源 回調 acc File類:即能代表一個特定文件的名稱,又能代表一個目錄下的一組文件的名稱。File類也可以用來創建新的目錄。File file = new File( "D:/test.txt

Java I/O總結(二)NIO

數據 keys 如果 等待 都是 int har 接口 key I/O的同步異步,阻塞非阻塞:阻塞:當執行的操作所需的數據還沒準備好時,線程進行等待非阻塞:當數據還沒準備好時,線程不等待同步:執行操作,一直等操作執行完才向下執行異步:執行操作,調用接口後不用等待,向下執行常

Java I/O不迷茫,文為你導航!

servers 當我 取數據 地址空間 緩沖 else if 類名 odi 連接 前言:在之前的面試中,每每問到關於Java I/O 方面的東西都感覺自己吃了大虧..所以這裏搶救一下..來深入的了解一下在Java之中的 I/O 到底是怎麽回事..文章可能說明類的文字有點

《深入分析Java Web技術內幕》讀後感之2- JAVA I/O NIO

一、Java I/O的基本架構 Java的I/O操作類在java.io包下,大概有80多個類,這些類可以分成以下4組: ▶ 基於位元組操作的I/O介面:InputStream和OutputStream ▶ 基於字元操作的I/O介面:Reader和Writer

《深入分析Java Web技術內幕》讀後感之JAVA I/O NIO

一、Java I/O的基本架構 Java的I/O操作類在java.io包下,大概有80多個類,這些類可以分成以下4組: ▶ 基於位元組操作的I/O介面:InputStream和OutputStream ▶ 基於字元操作的I/O介面:Reader和Writer ▶ 基於

Java進階(五)Java I/O模型從BIO到NIO和Reactor模式

本文介紹了Java中的四種I/O模型,同步阻塞,同步非阻塞,多路複用,非同步阻塞。同時將NIO和BIO進行了對比,並詳細分析了基於NIO的Reactor模式,包括經典單執行緒模型以及多執行緒模式和多Reactor模式。 原創文章,轉載請務必將下面這段話置於文章開頭處(保留超連結)。 本文

深入分析Java I/O的工作機制 (

此篇部落格看至許令波的深入分析javaWeb內幕書籍, 此篇部落格寫的是自己看完之後理解的重點內容,加一些理解,希望對你有幫助。 1.Java的I/O類庫的基本架構 先說一下什麼是類庫:可以說是類的集合,類庫包括介面、抽象類、具體類等。 I/O是機器獲取和互動資訊的主要渠道。 java在I/O上也一直在

Java——I/O學習(

Java I/O學習(一)什麼是I/O?I/O就是資料輸入輸出資料流,也稱作資料流。Java I/O操作主要指的是使用Java進行輸入、輸出操作,Java中的所有操作類都存放在Java.io包中,在使用時需要匯入此包。在整個Java.io包中最重要的就是5個類和1個介面,這5

Java I/O 知識點(

1、“流”遮蔽了實際I/O裝置中處理資料的細節;Java類庫中的I/O類分成了輸入和輸出兩部分。任何自inputStream或Reader派生的而來的類都含有名為read()的基本方法,用於讀取位元組或位元組陣列。同樣,任何來自outputStream或者Writer派生而

Java I/O 概述---檔案讀寫總結

以前寫Java讀寫檔案的程式碼,基本上都是到處拷貝,沒有深入研究過。以至於有段時間都搞不清楚,使用完一個File物件時候,要不要close。最近寫了一些程式碼也看了一些文章,現在把掌握的I/O知識梳理一下,以備有序補充擴充套件。 一、Java I/O概述 先放一張圖,對J

Java I/O系統學習系列:File和RandomAccessFile

  I/O系統即輸入/輸出系統,對於一門程式語言來說,建立一個好的輸入/輸出系統並非易事。因為不僅存在各種I/O源端和想要與之通訊的接收端(檔案、控制檯、網路連結等),而且還需要支援多種不同方式的通訊(順序、隨機存取、緩衝、二進位制、按字元、按行、按字等)。   Java類庫的設計者通過建立大量的類來解決這個

Java I/O體系從原理到應用,這篇全說清楚了

本文介紹作業系統I/O工作原理,Java I/O設計,基本使用,開源專案中實現高效能I/O常見方法和實現,徹底搞懂高效能I/O之道 基礎概念 在介紹I/O原理之前,先重溫幾個基礎概念: (1) 作業系統與核心 作業系統:管理計算機硬體與軟體資源的系統軟體 核心:作業系統的核心軟體,負責管理系統的程

Java I/O流的總結

註意 size 標記接口 ble ansi 寫入 就會 另一個 span I/O的類結構圖 I/O的分類 根據處理的數據類型分為:字節流和字符流。 根據數據流向分為:輸入流和輸出流。 流又可分為節點流和處理流。 節點流 直接與數據源相連 處理流 與節點流一

深入理解JAVA I/O系列三:字符流詳解

buffer 情況 二進制文件 感到 復制代碼 使用範圍 轉換 fileread 方式 字符流為何存在 既然字節流提供了能夠處理任何類型的輸入/輸出操作的功能,那為什麽還要存在字符流呢?容我慢慢道來,字節流不能直接操作Unicode字符,因為一個字符有兩個字節,字節流一次只

Java I/O 操作及優化建議

java.net 底層 str 面向對象 div 選擇 static 右移 linux Java I/OI/O。即 Input/Output(輸入/輸出) 的簡稱。就 I/O 而言。概念上有 5 種模型:blocking I/O,nonblocking I/O。I/O

[Java]I/O底層原理之二:Socket工作機制

tcp連接 fin 連接建立 src 並發 如果 send rec 轉換 一、TCP狀態轉化 TCP連接的狀態轉換圖如下 註:SYN 表示建立鏈接、FIN 表示關閉鏈接、ACK 表示響應、PSH 表示有數據傳輸、RST 表示鏈接重置。 CLOSED:初始狀態,在超時或

Java I/O 從0到1 - 第Ⅰ滴血 File

介紹 文章 mage 根據 ado () 第四版 操作 類名 前言   File 類的介紹主要會依據《Java 編程思想》以及官網API 。相信大家在日常工作中,肯定會遇到文件流的讀取等操作,但是在搜索過程中,並沒有找到一個介紹的很簡潔明了的文章。因此,在最近比較輕松的時間

Java Web 深入分析(4) Java I/O 深入分析

lock 異步 瓶頸 系統 基本結構 java 同步異步 nio -i I/O問題可以說是現在大部分web系統的瓶頸。我們要了解的java I/O(後面簡稱為(io)) io類庫的基本結構 -磁盤io的工作機制 -網絡io的工作機制 -NIO的工作方式 -同步異步、阻