程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> Java I/O(一) NIO概述

Java I/O(一) NIO概述

編輯:JAVA綜合教程

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();
        }
    }
}

測試結果如下:
這裡寫圖片描述

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved