Java NIO原理图文分析及代码实现

Java NIO原理图文分析及代码实现

简介

Java NIO(Non-blocking I/O)是一种可替代Java标准I/O的I/O API。相比传统的I/O,Java NIO提供的I/O操作更快速、更灵活,并且支持更多的操作(如块传输和多路复用)。

基本组成部分

Java NIO的核心组件主要包含以下几个部分:

  • Channel(通道):基本的I/O操作(如写入和读取)在通道中完成。
  • Buffer(缓冲区):通道读写数据的存储区域,读取数据时首先将数据读入缓冲区,写入数据时首先将数据写入缓冲区。
  • Selector(选择器):用于多路复用I/O操作,同时能够检测某个通道上是否有事件发生。

Channel

Java NIO中的通道(Channel)类似于流(Stream)的概念,用于进行数据的读取和写入。不过,与流不同的是:

  • 通道是双向的,可以在一个通道上进行读取和写入操作。
  • 通道可以通过多个线程进行操作(前提是线程安全)。

Java NIO中的通道主要分为以下几类:

  • FileChannel:用于读取、写入和映射文件的通道。
  • DatagramChannel:通过UDP协议读取或写入网络数据。
  • SocketChannel:通过TCP协议进行网络连接的通道。
  • ServerSocketChannel:可以监听新进来的TCP连接,并且为每个新连接创建一个通道。

Buffer

缓冲区(Buffer)实际上就是一个内存块,通道读取或写入时通过缓冲区读写数据。在Java NIO中,缓冲区主要分为以下几种类型:

  • ByteBuffer
  • CharBuffer
  • ShortBuffer
  • IntBuffer
  • LongBuffer
  • FloatBuffer
  • DoubleBuffer

这些缓冲区类型的创建方法基本相同,可以通过以下方法进行创建:

Buffer buffer = ByteBuffer.allocate(1024);

缓冲区从内部来说,实际上就是一个数组。除了存储数据之外,缓冲区还提供了三个核心操作:

  • 写入数据到缓冲区:put()
  • 从缓冲区读取数据:get()
  • 读写模式切换:flip()

其中,读写模式切换是这样用的:

// 读模式
buffer.flip();

// 写模式
buffer.clear();

Selector

多路复用器(Selector)是一种可以同时处理多个通道的Java NIO组件。多路复用器基于底层操作系统提供的select()方法(或者epoll、KQueue等方法),可以检测某些事件是否发生。

多路复用器的运行主要流程如下:

  1. 创建多路复用器(Selector)。
  2. 将各个通道注册到多路复用器上(SocketChannel.register())。
  3. 轮询多路复用器中发生事件的通道(Selector.select())。
  4. 处理具体的事件(如连接、读取、写入等)。

示例说明

示例1:使用FileChannel读取文件

public static void readFile(String filePath) throws IOException {
    // 获取文件的FileChannel
    File file = new File(filePath);
    RandomAccessFile randomAccessFile = new RandomAccessFile(file, "rw");
    FileChannel fileChannel = randomAccessFile.getChannel();

    // 创建buffer
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    while (fileChannel.read(buffer) != -1) {
        // 切换到读模式
        buffer.flip();

        while (buffer.hasRemaining()) {
            System.out.print((char) buffer.get());
        }

        // 切换到写模式
        buffer.clear();
    }

    // 关闭FileChannel
    fileChannel.close();
}

以上代码中,我们首先通过RandomAccessFile获取到需要读取的文件,然后通过getChannel()方法获取到文件的FileChannel。接下来,我们创建一个ByteBuffer缓冲区,通过FileChannelread()方法从文件通道读取数据,并通过ByteBufferflip()方法将缓冲区切换到读模式。最后,我们通过ByteBufferget()方法从缓冲区读取数据,并在控制台上输出。当ByteBuffer中没有数据时,我们将缓冲区切换到写模式,并继续执行下一轮读操作,直到文件读取完毕。

示例2:使用ServerSocketChannel监听并处理连接

public static void startServer(int port) throws IOException {
    // 创建ServerSocketChannel
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

    // 绑定端口
    serverSocketChannel.socket().bind(new InetSocketAddress(port));

    // 设置为非阻塞模式
    serverSocketChannel.configureBlocking(false);

    // 创建Selector
    Selector selector = Selector.open();

    // 将ServerSocketChannel注册到Selector上,并监听连接事件
    serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

    while (true) {
        // 阻塞等待事件
        selector.select();

        // 获取事件和事件通道
        Set<SelectionKey> selectionKeys = selector.selectedKeys();
        Iterator<SelectionKey> it = selectionKeys.iterator();

        // 处理事件
        while (it.hasNext()) {
            SelectionKey selectionKey = it.next();
            it.remove();

            if (selectionKey.isAcceptable()) {
                // 处理连接事件
                SocketChannel socketChannel = serverSocketChannel.accept();
                socketChannel.configureBlocking(false);
                socketChannel.register(selector, SelectionKey.OP_READ);
            } else if (selectionKey.isReadable()) {
                // 处理读取事件
                SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                ByteBuffer buffer = ByteBuffer.allocate(1024);
                socketChannel.read(buffer);
                buffer.flip();
                String message = Charset.forName("UTF-8").newDecoder().decode(buffer).toString();
                System.out.println("读取信息:" + message);
                socketChannel.write(ByteBuffer.wrap(("收到信息:" + message).getBytes()));
                socketChannel.close();
            }
        }
    }
}

以上代码中,我们首先创建一个ServerSocketChannel,并将它的端口绑定到我们要监听的端口上。然后,我们将ServerSocketChannel注册到Selector中并监听连接事件(SelectionKey.OP_ACCEPT)。接下来,我们进入一个无限循环,调用Selectorselect()方法模拟轮询事件。当select()返回时,我们调用SelectorselectedKeys()方法获取到所有发生的事件。然后,我们遍历选择键(Selection Key)集合,并处理每一个事件。当我们监听到一个连接事件时,我们通过ServerSocketChannelaccept()方法接受连接,并将其注册到Selector中监听读事件。当我们监听到读事件时,我们可以通过SocketChannelread()方法读取客户端发送的数据,并在控制台上输出。最后,我们通过SocketChannelwrite()方法向客户端发送数据,然后关闭连接。

总结

Java NIO提供了一种新的I/O操作方式,相较于传统I/O具有更高的处理效率和更灵活的使用方式,这在某些高并发场景下会提供很大的帮助作用。具体的操作与实现方式还需要在实践中去逐步掌握。

本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:Java NIO原理图文分析及代码实现 - Python技术站

(0)
上一篇 2023年5月19日
下一篇 2023年5月19日

相关文章

  • 详解Spring Boot 访问Redis的三种方式

    详解Spring Boot访问Redis的三种方式 Redis是一个开源的、基于内存的数据结构存储系统,它可以用作数据库、缓存和消息中间件。Spring Boot是一个非常流行的Java开发框架,它提供了多种方式来访问和操作Redis。 在本文中,我们将介绍Spring Boot访问Redis的三种方式,并提供相应的代码示例。 方式一:使用Spring Da…

    Java 2023年6月2日
    00
  • Android实现上传文件功能的方法

    Android实现上传文件功能的方法主要有两种:使用HttpURLConnection或使用OkHttp库。 使用HttpURLConnection上传文件 步骤一:添加网络权限 在AndroidManifest.xml文件中添加以下权限: <uses-permission android:name="android.permission.I…

    Java 2023年6月15日
    00
  • SpringBoot结合JWT登录权限控制的实现

    下面就来详细讲解“SpringBoot结合JWT登录权限控制的实现”的攻略。 第一步:添加Maven依赖 在pom.xml文件中添加以下Maven依赖: <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt</artifactId…

    Java 2023年5月20日
    00
  • 详解在Spring MVC中使用注解的方式校验RequestParams

    在Spring MVC中使用注解的方式校验RequestParams 在Spring MVC中,我们可以使用注解的方式来校验请求参数,这样可以避免在控制器中编写大量的校验代码。本文将详细介绍在Spring MVC中使用注解的方式校验RequestParams,并提供两个示例说明。 校验注解 在Spring MVC中,我们可以使用以下注解来校验请求参数: @N…

    Java 2023年5月17日
    00
  • SpringBoot自动配置实现的详细步骤

    Spring Boot自动配置是Spring Boot框架的核心特性之一,它可以帮助开发人员快速构建应用程序,减少了很多繁琐的配置工作。在本文中,我们将详细讲解Spring Boot自动配置实现的详细步骤。 Spring Boot自动配置实现的详细步骤 Spring Boot自动配置实现的详细步骤如下: Spring Boot启动时,会扫描classpath…

    Java 2023年5月15日
    00
  • java怎么创建目录(删除/修改/复制目录及文件)代码实例

    要在Java中创建、删除、修改和复制目录及文件,可以使用Java中自带的File类和方法。下面将在markdown文本中详细讲解此过程。 1. 创建目录 要在Java中创建一个新目录,可以使用如下代码: File dir = new File("path/to/directory"); boolean isCreated = dir.mk…

    Java 2023年5月20日
    00
  • JSP 前端数据本地排序实例代码

    当我们需要对表格数据进行排序时,我们可以使用前端的JavaScript进行排序。下面是一个使用JSP和JavaScript实现前端数据本地排序的示例代码: 首先,我们可以创建一个包含表格的HTML代码,表格中的每行数据都由一个对象构成。对象中的每个属性对应每一列的数据,例如姓名、身高、年龄等。 <table id="myTable"…

    Java 2023年6月15日
    00
  • 基于SpringIOC创建对象的四种方式总结

    下面是“基于SpringIOC创建对象的四种方式总结”的详细攻略。 什么是SpringIOC SpringIOC是Spring框架中的一个重要概念,全称是Spring Inversion of Control,中文也可以称之为控制反转。简单来说,控制反转就是将对象的创建和管理交给了Spring容器。通过SpringIOC容器,我们可以实现松耦合,降低代码的依…

    Java 2023年5月26日
    00
合作推广
合作推广
分享本页
返回顶部