NIO基础

本文详细介绍了Java的I/O演进历程,包括传统的BIO(同步阻塞)、NIO(同步非阻塞)和AIO(异步非阻塞)。重点讲解了NIO的工作原理,如通道(Channel)、缓冲区(Buffer)和选择器(Selector)的使用,并对比了BIO与NIO的区别。此外,还提到了伪异步I/O的概念,通过线程池和任务队列实现高效的客户端连接处理。

1、Java的I/O演进之路

BIO:同步并阻塞(传统阻塞型),服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销

NIO:同步非阻塞,服务器实现模式为一个线程处理多个请求(连接),即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求就进行处理

AIO:异步非阻塞,服务器实现模式为一个有效请求一个线程,客户端的I/O请求都是由操作系统先完成了再通知服务器应用去启动线程进行处理,一般适用于连接数较多且连接时间较长的应用

2、 伪异步I/O

伪异步I/O采用线程池和任务队列实现,当客户端接入时,将客户端的Socket封装成一个Task(该任务实现java.lang.Runnable线程任务接口)交给后端的线程池中进行处理。JDK的线程池维护一个消息队列和N个活跃的线程,对消息队列中Socket任务进行处理,由于线程池可以设置消息队列的大小和最大线程数,因此,它的资源占用是可控的,无论多少个客户端并发访问,都不会导致资源的耗尽和宕机

客户端:

public class SocketClient {
    public static void main(String[] args) throws IOException {
        System.out.println("客户端启动");
        Socket socket=new Socket("127.0.0.1",8888);
      //  socket.bind(new InetSocketAddress("127.0.0.1",8888)); 会报错Address already in use: JVM_Bind
        OutputStream out= socket.getOutputStream();
        PrintStream ps=new PrintStream(out);
        Scanner scanner=new Scanner(System.in);
        while(true){
            System.out.println("please enter in:");
            String msg=scanner.nextLine();
            ps.println(msg);
            ps.flush();;
        }
    }

}

服务端:

public class SocketServer {
    public static void main(String[] args)   {
    try{
        System.out.println("服务端启动");
        ServerSocket serverSocket=new ServerSocket(8888);
        ServerSocketPoolHandler poolHandle=new ServerSocketPoolHandler(3,10);
        while(true){
           Socket socket= serverSocket.accept();
           poolHandle.execute(new ServerSocketRunnable(socket));
        }
    } catch (Exception e) {
        System.out.println("服务端接收到客户端请求失败");
        e.printStackTrace();

    }
    }
}
启动服务端并启动多个客户端发送消息,由于核心线程数=最大线程数=3,当客户端数>3时,客户端的Socket任务会到线程池的阻塞队列中等待,关闭客户端,当客户端数<=3时,Socket任务将会被服务端处理

3、 NIO

面向缓冲区,基于通道的io操作,同步非阻塞的io,可配置socket为非阻塞模式

channel(通道):打开到IO设备(例如:文件、套接字)的连,可以从通道中读取或者写入数据【一个channel对应一个buffer 】

buffer(缓冲区,):一块写入/读取数据的内存

selector(选择器):检查一个或多个channel,并确定哪些通道已准备好读取/写入【一个selector对应多个channel】

注:Channel负责传输,Buffer负责存取数据

NIO和BIO区别:

1)bio以流方式处理数据,而NIO以数据块的方式

2)BIO同步阻塞,NIO同步非阻塞

3)bio基于字节流和字符流进行操作,而nio基于channel和buffer进行操作,数据从通道读到缓冲区,或者从缓冲区写入通道,Selector用于监听多个通道事件(如:连接请求、数据读写等),使用单线程就可以监听多个客户端通道

3.1 Buffer

常用Buffer类型:ByteBuffer,charBuffer,IntBuffer,LongBuffer,ShortBuffer,loatBuffer、DoubleBuffer,MapByteBuffer

缓冲区基本属性:

  • capacity(容量):allocate(int capacity);初始化是固定值

  • limit(限制):缓冲区中可以操作数据的大小,写模式大小等于buffer容量,读取模式时,等于写入的数据量

  • position(位置):下一个要读取或者写入的数据的索引。

  • mark(标记)和reset(重置):标记是一个索引,mark指定buffer中的一个特定的position,之后通过调用reset方法恢复到position【position為mark的位置】

  • flip:写模式切换成读模式

  • clear:清空所有的数据【允许所有数据被覆盖】,是下次put的时候【读模式切换为写模式】

  • compact:清空所有读取过的数据,没有读取的数据不会被清空,可以继续读取【读模式切换为写模式】

  • put:添加数据

  • rewind:数据可以重复读取,移动position=0;

  • remaining:写入模式的情况下,就是还可以写多少个,读模式下就是还可以读多少个

注: 1、标记、位置、限制、容量遵守不变式:

0<=mark<=position<=limit<=capacity

2、重复读取解决方案

  • 通过rewind【从头开始】

  • 从指定的地方读取重复数据,先通过mark,后通过reset

3、buffer读写数据遵循以下步骤

  • 写入数据到Buffer

  • 调用flip方法,转为读模式

  • 从buffer中读取数据

  • clear()或者compact清除缓冲区–【如果还未进行put,则下步骤仍然可以获得get的数据】

    ByteBuffer buffer=ByteBuffer.allocate(1024);////执行完position=0,limit=1024 capacity=1024
    //buffer.put("徐迎燕test".getBytes());
    buffer.putInt(1234);//执行完position=4,limit=1024 capacity=1024
    buffer.putChar('a');//执行完position=6,limit=1024 capacity=1024
    
    //转为读模式
    buffer.flip();//执行完position=0,limit=6 capacity=1024
    System.out.println(buffer.getInt());//执行完position=4,limit=6 capacity=1024
    //转为写模式
    buffer.clear();//执行完position=0,limit=1024 capacity=1024
    System.out.println(buffer.getInt());//执行完position=4,limit=1024 capacity=1024
    System.out.println(buffer.getChar());//执行完position=0,limit=1024 capacity=1024
    buffer.putChar('g');//执行完position=8,limit=1024 capacity=1024
    //转为读模式
    buffer.flip();//执行完position=0,limit=8 capacity=1024
    System.out.println(buffer.getChar()); 
    
    最终数据结果打印 1234  1234 a 
    最后一次的结果未输出
    
3.2 channel

FileChannel:读取、写入,映射和操作文件的管道【阻塞】

SocketChannel:通过TCP读写网络中的数据通道

ServerSocketChannel:可以监听新进来的TCP连接,对每一个新进来的连接都会创建一个SocketChannel

DatagramChannelChannel:通过udp读写网络中的数据通道

从目标通道中去复制原通道数据:targetChannel.transferFrom(srcChannel,srcChannel.position(),srcChannel.size());

把原通道数据复制到目标通道:srcChannel.transferTo(srcChannel.position(),srcChannel.size(),targetChannel);

分散读取(Scatter):是指把Channel通道的数据读入到多个缓冲区中去

聚集写入(Gather):是指将多个Buffer中的数据聚集到Channel

3.3 selector

选择器(Selector)是非阻塞IO的核心,是SelectableChannel对象的多路复用器,Selector可以同时监控多个SelectableChannel的IO状况,也就是说,利用Selector可使一个单独的线程管理多个Channel

优点:

  • selector可以监听多个注册的通道上的事件
  • 只有连接或者读写事件发生时才进行读写操作,减少开销,且不必为每个连接创建线程,不用维护多个线程
  • 避免多线程上下切换导致的开销

selector应用:

​ 1、创建selector

​ Selector selector = Selector.open();

2、向selector注册通道

ServerSocketChannel serverSocketChannel=  ServerSocketChannel.open() ;//获取通道
serverSocketChannel.bind(new InetSocketAddress(2222));//绑定连接
serverSocketChannel.configureBlocking(false);//切换为非阻塞
Selector selector=Selector.open();//获取选择器
serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);//将通道注册到选择器上,并且监听接收事件

3、轮询的获取选择器上已经准备就绪的事件

while(selector.select()>0){
    Set<SelectionKey> selectionKeys = selector.selectedKeys();//获取当前选择器中所有注册的选择键(已就绪的监听事件)
    Iterator<SelectionKey> keys = selectionKeys.iterator();
    while(keys.hasNext()){
        SelectionKey selectionKey = keys.next();
        if(selectionKey.isAcceptable()){//接收事件
           socketchannel.register(selector,SelectionKey.OP_READ);
        }else if(selectionKey.isReadable()){//读事件
             socketchannel.register(selector,SelectionKey.OP_WRITE);
        }else if(selectionKey.isWritable()){//写事件
         it.remove();// 15)取消选择键SelectionKey   
    }
       
}
<think>好的,用户想了解Java NIO基础知识。我需要先整理相关的引用内容,确保信息准确。根据提供的引用,NIOJava的新I/O库,从1.4版本引入,主要提升I/O性能,使用块处理数据而非流,这区别传统IO。用户可能需要知道NIO的核心组件,比如通道、缓冲区、选择器等。 首先,我应该解释NIO的定义和它与传统IO的区别。引用5提到,I/O以流处理数据,而NIO以块处理,这点很重要。然后,引用3指出NIO的目标是提高I/O密集型任务的性能,所以需要强调性能优势。 接下来,核心组件部分需要详细说明。缓冲区(Buffer)是NIO操作数据的基础,通道(Channel)用于数据传输,选择器(Selector)用于多路复用,这些是关键点。引用5中的通道和缓冲区的概念需要展开,比如Buffer的工作流程(分配、写入、切换模式、读取、清空),并举例说明代码片段,如ByteBuffer的使用。 然后,多路复用技术部分,要解释Selector的作用,如何通过选择器处理多个通道,提高效率。可能引用3中的内容,说明正确使用NIO可以减少I/O操作时间。 还需要比较NIO和传统IO的差异,比如阻塞与非阻塞模式,流与块的处理方式。引用1和引用5都提到了这些区别,用户可能想知道何时选择NIO,比如高并发场景。 最后,应用场景部分,根据引用3,NIO适合需要高性能和并发的场景,如服务器、网络框架,这些例子能帮助用户理解实际用途。 相关问题部分,需要涵盖工作原理、优化方法、应用场景、与NIO.2的区别,以及示例代码,这样用户能深入了解更多细节。要确保问题与用户可能的需求相关,比如性能优化和实际应用。 检查是否有遗漏,比如NIO.2的内容,但用户主要问的是NIO基础,所以暂时不深入NIO.2。确保所有引用正确标注,例如通道和缓冲区的解释来自引用5,性能提升部分来自引用3。代码示例要符合语法,使用Buffer的分配和操作步骤,保持简洁易懂。</think>### Java NIO基础教程 #### 1. **NIO的定义与核心思想** Java NIO(New I/O)是Java 1.4引入的新I/O API,旨在提高I/O密集型任务的性能。**与传统I/O的关键区别**在于: - **流 vs 块**:传统I/O基于流(逐个字节处理),而NIO基于块(数据以缓冲区块为单位处理),减少了系统调用的开销[^5]。 - **阻塞 vs 非阻塞**:NIO支持非阻塞模式,允许单线程管理多个通道(Channel),适合高并发场景[^3]。 #### 2. **NIO的核心组件** 1. **缓冲区(Buffer)** 用于临时存储数据,是NIO操作的基础。常见类型如`ByteBuffer`、`IntBuffer`。 **工作流程**: ```java // 分配缓冲区 ByteBuffer buffer = ByteBuffer.allocate(1024); // 写入数据 buffer.put("Hello".getBytes()); // 切换为读模式 buffer.flip(); // 读取数据 while (buffer.hasRemaining()) { System.out.print((char) buffer.get()); } // 清空缓冲区(可重用) buffer.clear(); ``` 2. **通道(Channel)** 与流不同,通道是双向的(可读可写),需与缓冲区配合使用。常见实现包括: - `FileChannel`:文件操作 - `SocketChannel`/`ServerSocketChannel`:网络通信 3. **选择器(Selector)** 实现多路复用,允许单线程监听多个通道的事件(如连接、读写就绪)。 **示例**: ```java Selector selector = Selector.open(); SocketChannel channel = SocketChannel.open(); channel.configureBlocking(false); channel.register(selector, SelectionKey.OP_READ); ``` #### 3. **NIO与传统I/O的对比** | 特性 | 传统I/O | NIO | |-------------------|-----------------------|----------------------| | 数据单位 | 流(字节/字符) | 块(缓冲区) | | 阻塞模式 | 同步阻塞 | 支持非阻塞 | | 线程模型 | 一连接一线程 | 单线程多连接 | | 适用场景 | 低并发、简单I/O | 高并发、网络通信 | #### 4. **NIO的典型应用场景** - **网络服务器**:如Tomcat、Netty等框架利用NIO处理高并发连接。 - **文件高效传输**:通过`FileChannel`和内存映射文件(`MappedByteBuffer`)提升大文件读写性能[^3]。 - **实时通信**:非阻塞特性适合聊天服务器、实时数据推送。 #### 5. **NIO的局限性** - **复杂度高**:需手动管理缓冲区、选择器等,代码量较大。 - **调试困难**:非阻塞模式下的异步逻辑可能增加调试难度。 --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值