Java NIO基础

NIO是jdk1.4版本以后发布的新特性,主要类位于包java.nio下,NIO主要由以下几个部分组成:

  • Buffer
    缓冲区,通常用于普通类与通道之间交换数据。缓冲区提供了一个会合点:通道既可提取放在缓冲区中的数据(写),也可向缓冲区存入数据供读取(读)。
  • Channel
    通道,和传统IO类中的流概念有些相似,但IO流是单向的,通道则是双向的,基于NIO的数据交换都从通道中经过,可以将通道中的数据写入缓冲区,也可以读取缓冲区的数据进通道。
  • Selector
    选择器,基于多路复用的IO模型,作用类似于多路复用器,允许通过一个线程处理多个注册的Channel。下图表示了单线程一个选择器同时处理三个通道的情况:
process.png

Buffer

缓冲区是包在一个对象内的基本数据元素数组。Buffer类相比一个简单数组的优点是它将关于数据的数据内容和信息包含在一个单一的对象中。Buffer类以及它专有的子类定义了一个用于处理数据缓冲区的API。
缓冲区维护了四个属性来操作内部数据:
1.Capacity
容量,缓冲区所能容纳的最大数据字节值,一旦固定即不可改变。
2.Limit
上界,缓冲区中第一个不能被操作的位置,可等同于现存数据计数。
3.Position
下一个操作位,由put和get方法更新。
4.Mark
备忘位置,不太常用。
缓冲区有读和写两种模式,一般通过filp()clear()来切换这两种操作模式。

public abstract class Buffer {
    
    public final Buffer flip() {
        limit = position;
        position = 0;
        mark = -1;
        return this;
    }
    
    public final Buffer clear() {
        position = 0;
        limit = capacity;
        mark = -1;
        return this;
    }
}    

position和limit的含义取决于Buffer处在读模式还是写模式:

modes.png

Channel

Channel是一个通道,类似于输入输出流的概念。区别在于流是单向的,一般在传统IO模型中进行数据读写需要输入输出流搭配使用,但是Channel则提供了双向数据通信的机制。
Channel的继承关系比Buffer要复杂,其具体实现依赖于不同的操作系统。大体上来说,IO通道可以分为两大类:文件通道和socket通道,常用的Channel类有FileChannel,SocketChannel,ServerSocketChannelDatagramChannel,适用于文件IO和网络流IO。
常用的Channel类一般都实现了ReadableByteChannelWriteableByteChannel,因此他们具有双向读写能力:

public class NIOTest {

    public static void main(String[] args) throws IOException {
        NIOTest nt = new NIOTest();
        
        ReadableByteChannel src = Channels.newChannel(System.in);
        WritableByteChannel dest = Channels.newChannel(System.out);
        nt.channelCopy(src, dest);
        src.close();
        dest.close();
    }
    
    private void channelCopy(ReadableByteChannel src, WritableByteChannel dest) throws IOException {
         ByteBuffer buffer = ByteBuffer.allocate(1024);
         while(src.read(buffer) != -1){
             buffer.flip();
             while (buffer.hasRemaining()) {
                 dest.write(buffer);
             }
             buffer.clear();
         }
         
    }
}

通道内容的复制即可通过这两个类来实现。

Selector

选择器类管理着一个被注册的通道集合的信息和它们的就绪状态。通道是和选择器一起被注册的,并且使用选择器来更新通道的就绪状态。
SelectableChannel提供了实现通道的可选择性所需要的公共方法,FileChannel并未继承此抽象类,因此不具有可选择性。SelectableChannel可以被注册到Selector对象上,同时可以指定对那个选择器而言,应该关注何种IO操作。
SelectionKey封装了特定的通道与特定的选择器的注册关系,指示了该注册关系所关心的通道操作,以及通道已经准备好的操作。
以下通过简单的回声服务器来实例NIO的基本编程模型:

  • 服务器端,接收客户端发送的消息并发送回给客户端
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

/**
 * Created by Administrator on 2017/7/19.
 * Intellij IDEA
 */
public class EchoServer {

    private Selector selector;
    private int port;
    private ByteBuffer buffer = ByteBuffer.allocate(1024);

    public EchoServer(int port) {
        this.port = port;
    }

    public static void main(String[] args) throws IOException {
        new EchoServer(8000).start();
    }

    private void start() throws IOException {
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(port));
        serverSocketChannel.configureBlocking(false);

        this.selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        //ServerHandler handler = new ServerHandler();
        while (true) {
            selector.select();

            Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
            while (keys.hasNext()) {
                SelectionKey key = keys.next();

                try {
                    if (key.isAcceptable())
                        handleAccept(key);
                    else if (key.isReadable())
                        handleRead(key);
                    else if (key.isWritable())
                        handleWrite(key);
                } catch (Exception e) {
                    keys.remove();
                    continue;
                }
                keys.remove();
            }
        }
    }

    private void handleWrite(SelectionKey key) throws IOException {
        buffer.clear();
        SocketChannel socketChannel = (SocketChannel) key.channel();
        String message = "[message from server]";
        buffer.put(message.getBytes());
        buffer.flip();//prepare for write
        socketChannel.write(buffer);//write to client channel
        System.out.println("Server send message to client"+message);
        socketChannel.register(selector, SelectionKey.OP_READ);
    }

    private void handleRead(SelectionKey key) throws IOException {
        SocketChannel socketChannel = (SocketChannel) key.channel();
        buffer.clear();//clear this buffer for next read event
        int length = socketChannel.read(buffer);
        if (length > 0){
            String message = new String(buffer.array(), 0, length);
            System.out.println("Server received message from client:" + message);
            socketChannel.register(selector, SelectionKey.OP_WRITE);
        }

    }

    private void handleAccept(SelectionKey key) throws IOException {
        //waiting for client to connect
        ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
        SocketChannel socketChannel = serverSocketChannel.accept();
        socketChannel.configureBlocking(false);
        socketChannel.register(selector, SelectionKey.OP_READ);
    }

}
  • 客户端,接收服务端发送的消息并发送消息给服务端
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;

/**
 * Created by Administrator on 2017/7/19.
 * Intellij IDEA
 */
public class EchoClient {
    private int port;
    private Selector selector;
    private ByteBuffer buffer = ByteBuffer.allocate(1024);

    public static void main(String[] args) throws IOException {
        new EchoClient(8000).start();
    }

    public EchoClient(int port) {
        this.port = port;
    }

    public void start() throws IOException {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false);

        this.selector = Selector.open();
        socketChannel.register(selector, SelectionKey.OP_CONNECT);
        socketChannel.connect(new InetSocketAddress(8000));//connect to server.

        while (true){
            selector.select();
            Iterator<SelectionKey> keys = selector.selectedKeys().iterator();
            while (keys.hasNext()) {
                SelectionKey key = keys.next();
                if (key.isConnectable())
                    handleConnect(key);
                else if (key.isReadable())
                    handleRead(key);
                else if (key.isWritable())
                    handleWrite(key);
                keys.remove();
            }
        }
    }

    private void handleWrite(SelectionKey key) throws IOException {
        buffer.clear();
        SocketChannel socketChannel = (SocketChannel) key.channel();
        String message = "#message from client#";
        buffer.put(message.getBytes());
        buffer.flip();
        socketChannel.write(buffer);
        System.out.println("Client send message to server:" + message);
        socketChannel.register(selector, SelectionKey.OP_READ);
    }

    private void handleRead(SelectionKey key) throws IOException {
        SocketChannel socketChannel = (SocketChannel) key.channel();
        buffer.clear();
        int length = socketChannel.read(buffer);//read data from channel
        if (length > 0) {
            String serverMessage = new String(buffer.array(), 0, length);
            System.out.println("Client received message from server::" + serverMessage);
            socketChannel.register(selector, SelectionKey.OP_WRITE);
        }

    }

    private void handleConnect(SelectionKey key) throws IOException {
        SocketChannel socketChannel = (SocketChannel) key.channel();
        if (socketChannel.isConnectionPending()) {
            socketChannel.finishConnect();
            buffer.clear();
            buffer.put("Message from client".getBytes());
            buffer.flip();
            socketChannel.write(buffer);
        }
        socketChannel.register(selector, SelectionKey.OP_READ);
    }
}

可见,基于NIO的socket编程确实比传统Blocking IO模型要复杂,但无疑效率更高。这个实例中,Server端仅开启了一个线程用于处理IO操作,由于Selector的引入使得IO操作不再需要频繁切换线程上下文,该Selector不断轮询注册在它上面的key,完成相应的IO操作。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,417评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,921评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,850评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,945评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,069评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,188评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,239评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,994评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,409评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,735评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,898评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,578评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,205评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,916评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,156评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,722评论 2 363
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,781评论 2 351

推荐阅读更多精彩内容