Java NIO

java 1.4之后,有了NIO,很有意思。。。

 

介绍NIO,需要介绍一些基本的概念:

 

Reactor模式



 

 

Selector

Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。

 

这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。

 

这个组件,是与操作系统相关的,充分利用操作系统API,非常本地化的实现。

 

 

Channel

Java NIO的通道类似流,但又有些不同:

 

既可以从通道中读取数据,又可以写数据到通道。但流的读写通常是单向的。

 

通道可以异步地读写。

 

通道中的数据总是要先读到一个Buffer,或者总是要从一个Buffer中写入。

 

 

Buffer

容量(Capacity ) 

缓冲区能够容纳的数据元素的最大数量。这一容量在缓冲区创建时被设定,并且永远不能被改变。 

 

上界(Limit) 

缓冲区的第一个不能被读或写的元素。或者说,缓冲区中现存元素的计数。 

 

位置(Position ) 

下一个要被读或写的元素的索引。位置会自动由相应的 get(  )和put(  )函数更新。 

 

标记(Mark ) 

一个备忘位置。调用 mark(  )来设定 mark =  postion。调用 reset(  )设定 position  = mark。标记在设定前是未定义的(undefined )。 

 

相等的Buffer



 

不相等的Buffer



 

推荐一本书:Java NIO,有中文译本,有电子书。见本文附件。

 

看Demo:

 

Server端:

package me.hello.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;

/**
 * God Bless You!
 * Author: Fangniude
 * Date: 2013-07-11
 */
public class NioServer {
    //通道管理器
    private Selector selector;

    /**
     * 获得一个ServerSocket通道,并对该通道做一些初始化的工作
     *
     * @param port 绑定的端口号
     * @throws IOException
     */
    public void initServer(int port) throws IOException {
        // 获得一个ServerSocket通道
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        // 设置通道为非阻塞
        serverChannel.configureBlocking(false);
        // 将该通道对应的ServerSocket绑定到port端口
        serverChannel.socket().bind(new InetSocketAddress(port));
        // 获得一个通道管理器
        this.selector = Selector.open();
        //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后,
        //当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞。
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);

        System.out.println("Server已经启动,监听端口: " + port + ", 等待客户端注册。。。");
    }

    /**
     * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理
     *
     * @throws IOException
     */
    @SuppressWarnings("unchecked")
    public void listen() throws IOException {
        // 轮询访问selector
        while (true) {
            //当注册的事件到达时,方法返回;否则,该方法会一直阻塞
            selector.select();
            // 获得selector中选中的项的迭代器,选中的项为注册的事件
            Iterator ite = this.selector.selectedKeys().iterator();
            while (ite.hasNext()) {
                SelectionKey key = (SelectionKey) ite.next();
                // 删除已选的key,以防重复处理
                ite.remove();
                // 客户端请求连接事件
                if (key.isAcceptable()) {
                    ServerSocketChannel server = (ServerSocketChannel) key.channel();
                    // 获得和客户端连接的通道
                    SocketChannel channel = server.accept();
                    // 设置成非阻塞
                    channel.configureBlocking(false);

//                    //在这里可以给客户端发送信息哦
//                    channel.write(ByteBuffer.wrap(new String("向客户端发送了一条信息").getBytes()));
                    //在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。
                    channel.register(this.selector, SelectionKey.OP_READ);

                    System.out.println("有一个客户端注册上来了。。。");
                    System.out.println("Client:" + channel.getRemoteAddress());
                    System.out.println("Server:" + channel.getLocalAddress());

                    System.out.println("\n等待客户端输入。。。");
                } else if (key.isReadable()) { // 获得了可读的事件
                    read(key);
                }

            }

        }
    }

    /**
     * 处理读取客户端发来的信息 的事件
     *
     * @param key
     * @throws IOException
     */
    public void read(SelectionKey key) throws IOException {
        // 服务器可读取消息:得到事件发生的Socket通道
        SocketChannel channel = (SocketChannel) key.channel();
        // 创建读取的缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(100);
        channel.read(buffer);
        byte[] data = buffer.array();
        String msg = new String(data).trim();
        System.out.println("服务端收到信息:" + msg);
        ByteBuffer outBuffer = ByteBuffer.wrap(("Server已收到刚发送的:" + msg).getBytes());
        channel.write(outBuffer);// 将消息回送给客户端

        System.out.println("\n等待客户端输入。。。");
    }

    /**
     * 启动服务端测试
     *
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        NioServer server = new NioServer();
        server.initServer(8000);
        server.listen();
    }

}

 

 

Client端:

 

package me.hello.nio;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
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;

/**
 * God Bless You!
 * Author: Fangniude
 * Date: 2013-07-11
 */
public class NioClient {
    //通道管理器
    private Selector selector;

    private BufferedReader sin = new BufferedReader(new InputStreamReader(System.in));

    /**
     * 获得一个Socket通道,并对该通道做一些初始化的工作
     *
     * @param ip   连接的服务器的ip
     * @param port 连接的服务器的端口号
     * @throws IOException
     */
    public void initClient(String ip, int port) throws IOException {
        // 获得一个Socket通道
        SocketChannel channel = SocketChannel.open();
        // 设置通道为非阻塞
        channel.configureBlocking(false);
        // 获得一个通道管理器
        this.selector = Selector.open();

        // 客户端连接服务器,其实方法执行并没有实现连接,需要在listen()方法中调
        //用channel.finishConnect();才能完成连接
        channel.connect(new InetSocketAddress(ip, port));
        //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_CONNECT事件。
        channel.register(selector, SelectionKey.OP_CONNECT);

        System.out.println("已经与Server建立连接。。。。");
        System.out.println("\n请输入要发送的信息:");
    }

    /**
     * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理
     *
     * @throws IOException
     */
    @SuppressWarnings("unchecked")
    public void listen() throws IOException {
        // 轮询访问selector
        while (true) {
            selector.select();
            // 获得selector中选中的项的迭代器
            Iterator ite = this.selector.selectedKeys().iterator();
            while (ite.hasNext()) {
                SelectionKey key = (SelectionKey) ite.next();
                // 删除已选的key,以防重复处理
                ite.remove();
                // 连接事件发生
                if (key.isConnectable()) {
                    SocketChannel channel = (SocketChannel) key.channel();
                    // 如果正在连接,则完成连接
                    if (channel.isConnectionPending()) {
                        channel.finishConnect();

                    }
                    // 设置成非阻塞
                    channel.configureBlocking(false);

                    //在这里可以给服务端发送信息哦
                    String input = sin.readLine();
                    channel.write(ByteBuffer.wrap(input.getBytes()));
                    //在和服务端连接成功之后,为了可以接收到服务端的信息,需要给通道设置读的权限。
                    channel.register(this.selector, SelectionKey.OP_READ);

                    // 获得了可读的事件
                } else if (key.isReadable()) {
                    read(key);
                }

            }

        }
    }

    /**
     * 处理读取服务端发来的信息 的事件
     *
     * @param key
     * @throws IOException
     */
    public void read(SelectionKey key) throws IOException {
        // 得到事件发生的Socket通道
        SocketChannel channel = (SocketChannel) key.channel();
        // 创建读取的缓冲区
        ByteBuffer buffer = ByteBuffer.allocate(100);
        channel.read(buffer);
        byte[] data = buffer.array();
        String msg = new String(data).trim();
        System.out.println(msg);

        System.out.println("\n请输入要发送的信息:");
        String input = sin.readLine();
        ByteBuffer outBuffer = ByteBuffer.wrap(input.getBytes());
        channel.write(outBuffer);
    }

    /**
     * 启动客户端测试
     *
     * @throws IOException
     */
    public static void main(String[] args) throws IOException {
        NioClient client = new NioClient();
        client.initClient("localhost", 8000);
        client.listen();
    }

}

 

 

Java NIO(New I/O)是Java 1.4引入的新的I/O API,用于替代标准的Java I/O API。它提供了非阻塞I/O操作,能显著提高程序的性能和可扩展性,尤其适用于处理大量并发连接的场景。 ### 核心组件 - **Channel(通道)**:Channel是对传统I/O中流的模拟,用于在缓冲区和实体(如文件、套接字)之间传输数据。常见的Channel实现有FileChannel、SocketChannel、ServerSocketChannel和DatagramChannel等。例如,FileChannel用于文件读写,SocketChannel用于TCP网络通信。 - **Buffer(缓冲区)**:Buffer是一个用于存储特定基本类型数据的容器。所有的缓冲区都是Buffer抽象类的子类,如ByteBuffer、CharBuffer、IntBuffer等。使用时,数据先被写入Buffer,再从Buffer读取到Channel,反之亦然。 - **Selector(选择器)**:Selector是Java NIO实现非阻塞I/O的关键。它允许一个线程处理多个Channel的I/O事件。通过将多个Channel注册到一个Selector上,Selector可以不断轮询这些Channel,当某个Channel有可用的I/O操作时,就会被Selector选中,从而实现单线程处理多个连接的目的。 - **SelectionKey(选择键)**:SelectionKey用于维护Selector和SelectableChannel的关系,每个Channel注册到Selector时都会产生一个SelectionKey,它聚合了Channel和Selector,有点类似EventKey。通过SelectionKey可以获取对应的Channel和Selector,还可以设置和查询感兴趣的I/O事件类型,如读、写、连接和接受连接事件等 [^1]。 ### 使用指南 #### 1. 使用FileChannel进行文件读写 ```java import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.FileChannel; public class FileChannelExample { public static void main(String[] args) { try (FileInputStream fis = new FileInputStream("input.txt"); FileOutputStream fos = new FileOutputStream("output.txt"); FileChannel inChannel = fis.getChannel(); FileChannel outChannel = fos.getChannel()) { ByteBuffer buffer = ByteBuffer.allocate(1024); while (inChannel.read(buffer) != -1) { buffer.flip(); // 切换为读模式 outChannel.write(buffer); buffer.clear(); // 清空缓冲区,准备下一次写入 } } catch (IOException e) { e.printStackTrace(); } } } ``` #### 2. 使用Selector实现非阻塞网络编程 ```java 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; import java.util.Set; public class NioServerExample { public static void main(String[] args) { try (ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); Selector selector = Selector.open()) { serverSocketChannel.socket().bind(new InetSocketAddress(8080)); serverSocketChannel.configureBlocking(false); serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT); while (true) { int readyChannels = selector.select(); if (readyChannels == 0) continue; Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> keyIterator = selectedKeys.iterator(); while (keyIterator.hasNext()) { SelectionKey key = keyIterator.next(); if (key.isAcceptable()) { ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel(); SocketChannel socketChannel = serverChannel.accept(); socketChannel.configureBlocking(false); socketChannel.register(selector, SelectionKey.OP_READ); } else if (key.isReadable()) { SocketChannel socketChannel = (SocketChannel) key.channel(); ByteBuffer buffer = ByteBuffer.allocate(1024); int bytesRead = socketChannel.read(buffer); if (bytesRead > 0) { buffer.flip(); byte[] data = new byte[buffer.remaining()]; buffer.get(data); System.out.println(new String(data)); } } keyIterator.remove(); } } } catch (IOException e) { e.printStackTrace(); } } } ``` ### 原理 Java NIO的非阻塞I/O原理基于操作系统的I/O多路复用机制。在传统的阻塞I/O模型中,一个线程只能处理一个连接,当线程在等待某个连接的数据时会被阻塞,无法处理其他连接。而在Java NIO中,Selector利用操作系统提供的I/O多路复用功能,如Linux的select、poll和epoll,通过一个线程监控多个Channel的I/O状态。当某个Channel有数据可读或可写时,Selector会感知到并通知应用程序进行相应的处理,从而实现单线程处理多个连接,提高了系统的并发处理能力。 ### 应用场景 - **网络编程**:在构建高性能的网络服务器时,如Web服务器、聊天服务器、游戏服务器等,Java NIO的非阻塞I/O特性可以显著减少线程数量,降低系统资源消耗,提高服务器的并发处理能力。 - **文件处理**:对于大文件的读写操作,使用FileChannel和ByteBuffer可以提高文件读写的效率,尤其是在需要随机访问文件内容时。 - **实时数据处理**:在处理实时数据流时,如视频流、音频流等,Java NIO可以高效地处理数据的传输和处理,确保数据的实时性。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值