当前位置: 首页 > news >正文

内蒙古住房和城乡建设部网站/中山360推广

内蒙古住房和城乡建设部网站,中山360推广,医疗机构网站,公司做网站都需要什么材料引言 在 Java 编程的世界里,输入输出(I/O)操作是基石般的存在,从文件的读取写入,到网络通信的数据传输,I/O 操作贯穿于各种应用程序的核心。BIO(Blocking I/O,阻塞式 I/O&#xff0…

引言

在 Java 编程的世界里,输入输出(I/O)操作是基石般的存在,从文件的读取写入,到网络通信的数据传输,I/O 操作贯穿于各种应用程序的核心。BIO(Blocking I/O,阻塞式 I/O)作为 Java 早期的 I/O 模型,以其简单直观的编程方式,在早期的 Java 应用开发中扮演了重要角色 ,为开发者提供了基础的 I/O 能力。但随着互联网应用的快速发展,尤其是在高并发场景下,BIO 的局限性逐渐暴露,如线程资源消耗大、I/O 操作阻塞导致效率低下等问题,难以满足日益增长的性能需求。

为了突破 BIO 的困境,NIO(New I/O 或 Non - Blocking I/O,新 I/O 或非阻塞 I/O)应运而生。NIO 自 Java 1.4 版本引入后,带来了全新的 I/O 编程理念和方式,它通过 Channel(通道)、Buffer(缓冲区)和 Selector(选择器)等核心组件,构建了一种基于事件驱动的非阻塞 I/O 模型,极大地提升了 I/O 操作的效率和并发处理能力,成为 Java I/O 领域的一次重大变革。

本文将深入探讨 BIO 到 NIO 的演变历程,从背景历史、功能点、业务场景、底层原理等多个维度进行剖析,详细介绍其实现原理,并通过 Java 代码给出至少三个不同的样例,帮助读者全面理解和掌握这两种 I/O 模型的精髓,以及它们在不同场景下的应用。

一、BIO 的诞生与发展

1.1 计算机 IO 的基础概念

在计算机系统中,I/O(Input/Output,输入 / 输出)是指计算机与外部世界进行数据交互的过程。计算机通过输入设备(如键盘、鼠标、摄像头等)接收外部数据,再将处理后的数据通过输出设备(如显示器、打印机、扬声器等)输出到外部 。在这个过程中,数据在内存和外部设备之间流动,而 I/O 操作就是负责管理和控制这种数据流动的机制。

I/O 操作涉及到计算机硬件和软件的多个层面。从硬件角度看,I/O 设备通过设备控制器与计算机的总线相连,设备控制器负责管理设备的具体操作,如数据传输、设备状态监测等。从软件角度看,操作系统提供了设备驱动程序,用于与设备控制器进行通信,使得应用程序能够通过操作系统提供的接口来访问 I/O 设备。

1.2 BIO 的初步实现

BIO(Blocking I/O,同步阻塞 I/O)是 Java 早期的 I/O 模型,它基于流(Stream)的概念来实现数据的读写操作。在 BIO 中,当进行 I/O 操作时,线程会被阻塞,直到操作完成。例如,使用InputStream从文件或网络连接中读取数据时,线程会一直等待,直到有数据可读或读取操作完成;使用OutputStream向文件或网络连接中写入数据时,线程会一直等待,直到数据被完全写入。

以从文件中读取数据为例,Java 代码如下:

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
public class BIOExample {public static void main(String[] args) {try (InputStream inputStream = new FileInputStream("example.txt")) {int data;while ((data = inputStream.read())!= -1) {System.out.print((char) data);}} catch (IOException e) {e.printStackTrace();}}
}

在上述代码中,FileInputStream是InputStream的子类,用于从文件中读取数据。read()方法会阻塞线程,直到读取到一个字节的数据或到达文件末尾(返回 -1)。每次读取一个字节,然后将其转换为字符并打印出来。

1.3 BIO 的优化与工具类扩展

为了提升 BIO 的读写效率,Java 引入了缓冲区(Buffer)的概念。通过使用BufferedInputStream和BufferedOutputStream,可以减少系统 I/O 调用的次数,从而提高性能。BufferedInputStream内部维护了一个缓冲区,当读取数据时,它会一次性从底层输入流中读取多个字节到缓冲区中,然后从缓冲区中返回数据给应用程序。当缓冲区中的数据耗尽时,它会再次从底层输入流中读取数据到缓冲区。BufferedOutputStream的工作原理类似,它会将数据先写入缓冲区,当缓冲区满或调用flush()方法时,才将缓冲区中的数据一次性写入到底层输出流中。

以下是使用BufferedInputStream和BufferedOutputStream进行文件复制的示例代码:

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
public class BufferedIOExample {public static void main(String[] args) {try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("source.txt"));BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("target.txt"))) {int data;while ((data = bis.read())!= -1) {bos.write(data);}} catch (IOException e) {e.printStackTrace();}}
}

此外,为了方便字符数据的读写,Java 还提供了转换流InputStreamReader和OutputStreamWriter,它们可以将字节流转换为字符流,并指定字符编码。例如:

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
public class ConvertStreamExample {public static void main(String[] args) {try (InputStreamReader isr = new InputStreamReader(new FileInputStream("source.txt"), "UTF-8");OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream("target.txt"), "UTF-8")) {int data;while ((data = isr.read())!= -1) {osw.write(data);}} catch (IOException e) {e.printStackTrace();}}
}

进一步地,Java 还提供了更便捷的字符流操作类FileReader和FileWriter,它们默认使用平台的默认字符编码,简化了字符文件的读写操作。例如:

import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
public class FileReaderWriterExample {public static void main(String[] args) {try (FileReader fr = new FileReader("source.txt");FileWriter fw = new FileWriter("target.txt")) {int data;while ((data = fr.read())!= -1) {fw.write(data);}} catch (IOException e) {e.printStackTrace();}}
}

1.4 BIO 的特点与局限性

BIO 在处理少量连接请求时,具有响应速度高、编程简单等优势。因为每个连接由一个独立的线程处理,代码逻辑清晰,开发人员容易理解和实现。例如,在一些简单的客户端 - 服务器应用中,BIO 可以快速搭建起基本的通信框架。

然而,当面对大量连接请求时,BIO 的局限性就会凸显出来。首先,为了处理每个连接,服务器需要创建大量的监听线程,而线程的创建和销毁会消耗大量的系统资源,包括内存、CPU 时间等。其次,由于 I/O 操作是阻塞的,当一个线程在进行 I/O 操作时,它会被阻塞,无法执行其他任务,这就导致了线程资源的浪费。例如,当一个客户端连接到服务器后,如果长时间没有数据发送,那么处理该客户端的线程就会一直阻塞在读取数据的操作上,无法为其他客户端提供服务。此外,大量线程的存在还会导致线程上下文切换频繁,进一步降低系统的性能。

二、NIO 的应运而生

2.1 时代需求催生 NIO

随着互联网的飞速发展,软件系统面临的并发访问压力与日俱增。在传统的 BIO 模型下,服务器为每个客户端连接创建一个独立的线程进行处理。当并发连接数达到一定规模时,大量线程的创建、管理和销毁会消耗大量的系统资源,包括内存、CPU 时间等 。同时,由于 I/O 操作的阻塞特性,当线程在进行 I/O 操作时,会被阻塞,无法执行其他任务,导致线程资源的浪费,系统的整体性能和并发处理能力受到严重制约。

为了应对这些挑战,Java 在 1.4 版本引入了 NIO(New I/O 或 Non - Blocking I/O)。NIO 的出现旨在提供一种更高效、更灵活的 I/O 处理方式,以满足高并发场景下的性能需求。NIO 基于通道(Channel)和缓冲区(Buffer)进行操作,采用非阻塞 I/O 和多路复用技术,允许一个线程管理多个 I/O 通道,大大减少了线程的数量和上下文切换开销,提高了系统的并发处理能力和 I/O 操作效率。

2.2 NIO 的核心组件

NIO 包含了三个核心组件:Selector(多路复用器)、Channel(通道)和 Buffer(缓冲区)。这三个组件相互协作,共同实现了 NIO 的高效非阻塞 I/O 操作。

2.2.1 Selector(多路复用器)

Selector 是 NIO 的核心组件之一,它允许一个线程同时监听多个 Channel 的事件,如连接建立、数据可读、数据可写等。通过 Selector,线程可以在多个 Channel 之间进行高效的切换,避免了线程的阻塞和资源的浪费 。

在使用 Selector 时,首先需要将 Channel 注册到 Selector 上,并指定需要监听的事件类型。Selector 会不断地轮询注册在其上的 Channel,当某个 Channel 上有感兴趣的事件发生时,Selector 会返回对应的 SelectionKey 集合,通过这些 SelectionKey 可以获取到发生事件的 Channel,并进行相应的处理。

例如,在一个服务器应用中,可以使用 Selector 来监听多个客户端的连接请求和数据传输。当有新的客户端连接请求到达时,Selector 会通知服务器线程,服务器线程可以创建新的 Channel 来处理该连接;当有客户端发送数据时,Selector 也会通知服务器线程,服务器线程可以从对应的 Channel 中读取数据并进行处理。这样,一个服务器线程就可以同时处理多个客户端的请求,大大提高了服务器的并发处理能力。

2.2.2 Channel(通道)

Channel 是对操作系统底层 I/O 通道的抽象,它提供了一种与 I/O 设备进行交互的方式。与传统的 BIO 中的流(Stream)不同,Channel 是双向的,可以同时进行读和写操作 。

在 NIO 中,有多种类型的 Channel,如 FileChannel 用于文件 I/O 操作,SocketChannel 和 ServerSocketChannel 用于 TCP 网络通信,DatagramChannel 用于 UDP 网络通信等。每个 Channel 都可以注册到 Selector 上,以便 Selector 能够监听其事件。

例如,使用 SocketChannel 进行网络通信时,可以通过调用SocketChannel.open()方法创建一个 SocketChannel 实例,然后通过configureBlocking(false)方法将其设置为非阻塞模式,再将其注册到 Selector 上,监听连接建立和数据可读事件。当有数据可读时,就可以从 SocketChannel 中读取数据。

2.2.3 Buffer(缓冲区)

Buffer 是一个用于存储数据的缓冲区,它在 NIO 中扮演着数据读写的中间角色。所有的数据操作都需要通过 Buffer 来进行,Channel 从数据源读取数据到 Buffer 中,然后程序从 Buffer 中读取数据进行处理;程序将处理后的数据写入 Buffer,再由 Channel 将 Buffer 中的数据写入到目标数据源 。

Java NIO 提供了多种类型的 Buffer,如 ByteBuffer、CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer 和 DoubleBuffer 等,分别用于存储不同类型的数据。其中,ByteBuffer 是最常用的 Buffer 类型,它可以用于存储字节数据。

ByteBuffer 又分为 HeapByteBuffer 和 DirectByteBuffer。HeapByteBuffer 是基于 Java 堆内存的缓冲区,它的创建和销毁由 Java 虚拟机的垃圾回收机制管理,优点是使用方便,与 Java 对象交互简单;缺点是在进行 I/O 操作时,可能需要在堆内存和直接内存之间进行数据拷贝,影响性能。DirectByteBuffer 是基于直接内存(堆外内存)的缓冲区,它直接在操作系统的物理内存中分配空间,不需要经过 Java 堆,因此在进行 I/O 操作时可以减少数据拷贝,提高性能,但它的创建和销毁需要手动管理,使用不当可能会导致内存泄漏。

例如,在使用 FileChannel 读取文件时,可以创建一个 ByteBuffer,然后调用 FileChannel 的read(ByteBuffer buffer)方法将文件中的数据读取到 ByteBuffer 中,再从 ByteBuffer 中读取数据进行处理。在写入数据时,先将数据写入 ByteBuffer,然后调用 FileChannel 的write(ByteBuffer buffer)方法将 ByteBuffer 中的数据写入文件。

三、BIO 与 NIO 的全面对比

3.1 功能点差异

BIO 和 NIO 在功能实现上存在显著差异,这些差异决定了它们在不同场景下的适用性。

从数据处理方式来看,BIO 基于流(Stream)进行操作,数据是顺序、连续地从流中读取或写入 ,就像水流一样,数据的读取和写入是线性的。而 NIO 引入了缓冲区(Buffer)的概念,数据先被读取到缓冲区中,然后再从缓冲区进行处理 。缓冲区提供了更灵活的数据处理方式,例如可以对缓冲区中的数据进行随机访问、标记和重置等操作。

在阻塞特性方面,BIO 是阻塞式的 I/O 模型。当一个线程执行 I/O 操作时,如从输入流中读取数据或向输出流中写入数据,线程会被阻塞,直到操作完成 。这意味着在 I/O 操作执行期间,线程无法执行其他任务,只能等待。例如,在一个简单的网络通信程序中,当服务器线程调用InputStream的read()方法读取客户端发送的数据时,如果没有数据可读,线程就会一直阻塞在这个read()操作上。而 NIO 是非阻塞式的,当线程执行 I/O 操作时,如果数据还没有准备好,线程不会被阻塞,而是立即返回 。线程可以继续执行其他任务,然后在适当的时候再次检查 I/O 操作的状态。例如,在 NIO 的网络编程中,SocketChannel在设置为非阻塞模式后,调用read()方法时,如果没有数据可读,会立即返回一个状态值,告知调用者当前没有数据可读,而不会阻塞线程。

从操作基础来看,BIO 主要基于字节流(InputStream和OutputStream)和字符流(Reader和Writer)进行操作,字节流用于处理二进制数据,字符流用于处理文本数据 。而 NIO 则基于通道(Channel)和缓冲区(Buffer)进行操作。通道是对 I/O 设备的抽象,它提供了一种与 I/O 设备进行交互的方式,并且是双向的,可以同时进行读和写操作 。缓冲区则是用于存储数据的内存块,所有的数据操作都需要通过缓冲区来进行。

在数据传输方向上,BIO 中的流通常是单向的,要么是输入流(InputStream或Reader)用于读取数据,要么是输出流(OutputStream或Writer)用于写入数据 。而 NIO 中的通道是双向的,可以同时进行读和写操作,这使得 NIO 在数据传输上更加灵活 。例如,使用SocketChannel进行网络通信时,可以通过同一个通道既读取来自客户端的数据,又向客户端发送数据。

3.2 底层原理剖析

3.2.1 BIO 的底层原理

BIO 的底层原理基于操作系统的阻塞 I/O 机制。在 BIO 中,当一个客户端与服务器建立连接时,服务器会为每个客户端连接创建一个独立的线程 。这个线程负责处理该客户端的所有 I/O 操作,包括读取客户端发送的数据和向客户端发送响应数据。

当服务器线程调用ServerSocket的accept()方法监听客户端连接时,该方法会阻塞线程,直到有新的客户端连接请求到达 。一旦有新的连接请求,accept()方法会返回一个新的Socket对象,代表与客户端的连接。然后,服务器会为这个Socket创建一个新的线程,用于处理该客户端的 I/O 操作。

在处理客户端的 I/O 操作时,例如读取客户端发送的数据,服务器线程会调用Socket的InputStream的read()方法 。这个方法会阻塞线程,直到有数据可读。如果没有数据可读,线程会一直等待,直到有数据到达或者连接关闭。当有数据可读时,read()方法会将数据读取到缓冲区中,然后返回读取的字节数。同样,在向客户端发送数据时,调用Socket的OutputStream的write()方法也会阻塞线程,直到数据被完全写入。

这种一个客户端对应一个线程的处理方式,虽然简单直观,但在高并发场景下存在严重的性能问题。因为每个线程都需要占用一定的系统资源,包括栈空间、CPU 时间等。当并发连接数较多时,大量线程的创建和管理会消耗大量的系统资源,导致系统性能下降。此外,由于 I/O 操作的阻塞特性,当线程在进行 I/O 操作时,会被阻塞,无法执行其他任务,这就导致了线程资源的浪费。

3.2.2 NIO 的底层原理

NIO 的底层原理基于操作系统的多路复用(Multiplexing)和非阻塞 I/O 机制。在 NIO 中,核心组件包括 Selector(多路复用器)、Channel(通道)和 Buffer(缓冲区) 。

Selector 是 NIO 的关键组件之一,它允许一个线程同时监听多个 Channel 的事件 。Selector 通过内部的轮询机制,不断地检查注册在其上的 Channel 是否有感兴趣的事件发生,如连接建立、数据可读、数据可写等。当某个 Channel 上有事件发生时,Selector 会将该 Channel 对应的 SelectionKey 加入到已选择键集合中,程序可以通过遍历这个集合来获取发生事件的 Channel,并进行相应的处理。

Channel 是对操作系统底层 I/O 通道的抽象,它提供了一种与 I/O 设备进行交互的方式 。Channel 可以注册到 Selector 上,以便 Selector 能够监听其事件。与 BIO 中的流不同,Channel 是双向的,可以同时进行读和写操作。例如,SocketChannel用于 TCP 网络通信,它可以通过configureBlocking(false)方法设置为非阻塞模式,在这种模式下,调用read()和write()方法时,如果数据没有准备好,不会阻塞线程,而是立即返回。

Buffer 是一个用于存储数据的缓冲区,它在 NIO 中扮演着数据读写的中间角色 。所有的数据操作都需要通过 Buffer 来进行,Channel 从数据源读取数据到 Buffer 中,然后程序从 Buffer 中读取数据进行处理;程序将处理后的数据写入 Buffer,再由 Channel 将 Buffer 中的数据写入到目标数据源。例如,在使用SocketChannel读取数据时,首先创建一个ByteBuffer,然后调用SocketChannel的read(ByteBuffer buffer)方法将数据读取到ByteBuffer中,再从ByteBuffer中读取数据进行处理。

NIO 的多路复用机制通过 Selector 实现,它使得一个线程可以同时管理多个 Channel,大大减少了线程的数量和上下文切换开销,提高了系统的并发处理能力和 I/O 操作效率。在一个聊天服务器中,可以使用一个 Selector 来监听多个客户端的SocketChannel,当有新的客户端连接请求到达时,Selector 会通知服务器线程,服务器线程可以创建新的SocketChannel来处理该连接;当有客户端发送数据时,Selector 也会通知服务器线程,服务器线程可以从对应的SocketChannel中读取数据并进行处理。这样,一个服务器线程就可以同时处理多个客户端的请求,提高了服务器的并发处理能力。

3.3 适用业务场景

BIO 适用于连接数目少且固定的场景,因为它为每个连接创建一个线程,在连接数较少时,线程资源的消耗相对较小,且编程简单,易于理解和维护 。例如,一些小型的企业内部应用,可能只需要与少量的客户端进行通信,这种情况下使用 BIO 可以快速搭建起通信框架,并且由于连接数固定,不会出现线程资源耗尽的问题。另外,对于一些对服务器资源要求不高,但对程序的简单性和可读性要求较高的场景,BIO 也是一个不错的选择。比如,一些简单的命令行工具,需要与用户进行交互,读取用户输入并返回结果,使用 BIO 可以方便地实现这种简单的 I/O 操作。

NIO 适用于连接数目多且连接短(轻操作)的高并发场景 。由于 NIO 采用非阻塞 I/O 和多路复用技术,一个线程可以管理多个连接,大大减少了线程的数量和上下文切换开销,能够高效地处理大量并发连接 。例如,在聊天服务器中,会有大量的客户端同时连接到服务器,并且每个客户端的消息发送和接收操作通常都是轻量级的,使用 NIO 可以有效地处理这些并发连接,提高服务器的性能和响应速度。同样,弹幕系统也是一个典型的高并发场景,大量的用户同时发送弹幕消息,NIO 可以快速地处理这些消息,保证弹幕的实时性。此外,在一些服务器间的通信场景中,也经常会涉及到大量的连接,NIO 的高性能和高并发处理能力使其成为理想的选择。

四、Java 实现 BIO 与 NIO 的样例展示

4.1 BIO 示例代码

4.1.1 简单 BIO 实现

下面是一个简单的 BIO(Blocking I/O)实现示例,包括服务端和客户端代码。该示例展示了如何使用 Java 的ServerSocket和Socket进行基本的网络通信,并进行数据的读写操作。

BIO 服务端代码

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class BIOServer {public static void main(String[] args) {try (ServerSocket serverSocket = new ServerSocket(8080)) {System.out.println("BIO Server is listening on port 8080");while (true) {// 监听客户端连接,accept()方法会阻塞,直到有新的客户端连接Socket socket = serverSocket.accept();System.out.println("New client connected: " + socket.getInetAddress());// 为每个客户端连接创建一个新的线程来处理通信new Thread(() -> {try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));PrintWriter out = new PrintWriter(socket.getOutputStream(), true)) {String inputLine;while ((inputLine = in.readLine())!= null) {System.out.println("Received from client: " + inputLine);// 向客户端发送响应out.println("Server response: " + inputLine);if ("exit".equalsIgnoreCase(inputLine)) {break;}}} catch (IOException e) {e.printStackTrace();} finally {try {socket.close();} catch (IOException e) {e.printStackTrace();}}}).start();}} catch (IOException e) {e.printStackTrace();}}
}

BIO 客户端代码

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
public class BIOClient {public static void main(String[] args) {try (Socket socket = new Socket("localhost", 8080);BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));PrintWriter out = new PrintWriter(socket.getOutputStream(), true);BufferedReader stdIn = new BufferedReader(new InputStreamReader(System.in))) {String userInput;while ((userInput = stdIn.readLine())!= null) {out.println(userInput);System.out.println("Sent to server: " + userInput);String response = in.readLine();System.out.println("Received from server: " + response);if ("exit".equalsIgnoreCase(userInput)) {break;}}} catch (UnknownHostException e) {System.out.println("Don't know about host: localhost");e.printStackTrace();} catch (IOException e) {System.out.println("Couldn't get I/O for the connection to: localhost");e.printStackTrace();}}
}

在上述代码中,BIO 服务端通过ServerSocket监听 8080 端口,当有客户端连接时,创建一个新的线程来处理与该客户端的通信。在处理线程中,使用BufferedReader从客户端读取数据,使用PrintWriter向客户端发送数据。BIO 客户端通过Socket连接到服务端,同样使用BufferedReader和PrintWriter进行数据的读写操作。

4.1.2 线程池优化的 BIO

为了减少线程创建和销毁的开销,提高性能,可以使用线程池来优化 BIO。下面是一个使用线程池优化后的 BIO 服务端代码示例:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolOptimizedBIOServer {private static final int THREAD_POOL_SIZE = 10;public static void main(String[] args) {// 创建一个固定大小的线程池ExecutorService executorService = Executors.newFixedThreadPool(THREAD_POOL_SIZE);try (ServerSocket serverSocket = new ServerSocket(8080)) {System.out.println("ThreadPoolOptimizedBIO Server is listening on port 8080");while (true) {// 监听客户端连接,accept()方法会阻塞,直到有新的客户端连接Socket socket = serverSocket.accept();System.out.println("New client connected: " + socket.getInetAddress());// 将客户端连接的处理任务提交到线程池executorService.submit(() -> {try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));PrintWriter out = new PrintWriter(socket.getOutputStream(), true)) {String inputLine;while ((inputLine = in.readLine())!= null) {System.out.println("Received from client: " + inputLine);// 向客户端发送响应out.println("Server response: " + inputLine);if ("exit".equalsIgnoreCase(inputLine)) {break;}}} catch (IOException e) {e.printStackTrace();} finally {try {socket.close();} catch (IOException e) {e.printStackTrace();}}});}} catch (IOException e) {e.printStackTrace();} finally {executorService.shutdown();}}
}

在这个示例中,使用Executors.newFixedThreadPool(THREAD_POOL_SIZE)创建了一个固定大小为 10 的线程池。当有新的客户端连接时,不再为每个连接创建一个新的线程,而是将处理任务提交到线程池中,由线程池中的线程来处理。这样可以减少线程的创建和销毁开销,提高系统的性能和资源利用率 。同时,在程序结束时,调用executorService.shutdown()方法来关闭线程池,释放资源。

4.2 NIO 示例代码

4.2.1 NIO 基础示例

下面是一个 NIO(New I/O)的基础示例,展示了 NIO 中Selector、Channel和Buffer的协同工作。该示例包括 NIO 服务端和客户端代码,演示了如何使用 NIO 进行网络通信。

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;
import java.util.Set;
public class NIOServer {public static void main(String[] args) {try (Selector selector = Selector.open();ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()) {serverSocketChannel.configureBlocking(false);serverSocketChannel.socket().bind(new InetSocketAddress(8080));serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);System.out.println("NIO Server is listening on port 8080");while (true) {// 阻塞,直到有感兴趣的事件发生selector.select();Set<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> keyIterator = selectedKeys.iterator();while (keyIterator.hasNext()) {SelectionKey key = keyIterator.next();keyIterator.remove();if (key.isAcceptable()) {// 处理新的客户端连接ServerSocketChannel server = (ServerSocketChannel) key.channel();SocketChannel clientChannel = server.accept();clientChannel.configureBlocking(false);// 注册读事件clientChannel.register(selector, SelectionKey.OP_READ);System.out.println("Accepted new connection from client: " + clientChannel.getRemoteAddress());} else if (key.isReadable()) {// 处理客户端发送的数据SocketChannel clientChannel = (SocketChannel) key.channel();ByteBuffer buffer = ByteBuffer.allocate(1024);int bytesRead = clientChannel.read(buffer);if (bytesRead > 0) {buffer.flip();byte[] data = new byte[buffer.remaining()];buffer.get(data);String message = new String(data);System.out.println("Received from client: " + message);// 向客户端发送响应ByteBuffer responseBuffer = ByteBuffer.wrap(("Server response: " + message).getBytes());clientChannel.write(responseBuffer);} else if (bytesRead == -1) {// 客户端关闭连接clientChannel.close();}}}}} catch (IOException e) {e.printStackTrace();}}
}

NIO 客户端代码

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class NIOClient {public static void main(String[] args) {try (SocketChannel socketChannel = SocketChannel.open()) {socketChannel.configureBlocking(false);socketChannel.connect(new InetSocketAddress("localhost", 8080));while (!socketChannel.finishConnect()) {// 可以在此处执行其他任务}System.out.println("Connected to server");ByteBuffer buffer = ByteBuffer.allocate(1024);buffer.put("Hello, Server".getBytes());buffer.flip();socketChannel.write(buffer);buffer.clear();int bytesRead = socketChannel.read(buffer);if (bytesRead > 0) {buffer.flip();byte[] data = new byte[buffer.remaining()];buffer.get(data);String response = new String(data);System.out.println("Received from server: " + response);}} catch (IOException e) {e.printStackTrace();}}
}

在上述 NIO 服务端代码中,首先创建了一个Selector和一个非阻塞的ServerSocketChannel,并将ServerSocketChannel注册到Selector上,监听OP_ACCEPT事件。当有新的客户端连接时,接受连接并将新的SocketChannel注册到Selector上,监听OP_READ事件。当有可读事件发生时,从SocketChannel中读取数据,并向客户端发送响应。NIO 客户端代码中,创建一个非阻塞的SocketChannel,连接到服务端,发送数据并接收服务端的响应。

4.2.2 复杂 NIO 场景示例

下面是一个更复杂的 NIO 应用场景示例,实现了一个简单的文件服务器。该示例展示了 NIO 在实际应用中的强大功能,包括文件的读取和传输。

NIO 文件服务器服务端代码

import java.io.File;
import java.io.FileInputStream;
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 NIOFileServer {private static final int PORT = 8081;private static final String FILE_DIRECTORY = "files";public static void main(String[] args) {try (Selector selector = Selector.open();ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()) {serverSocketChannel.configureBlocking(false);serverSocketChannel.socket().bind(new InetSocketAddress(PORT));serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);System.out.println("NIO File Server is listening on port " + PORT);while (true) {selector.select();Set<SelectionKey> selectedKeys = selector.selectedKeys();Iterator<SelectionKey> keyIterator = selectedKeys.iterator();while (keyIterator.hasNext()) {SelectionKey key = keyIterator.next();keyIterator.remove();if (key.isAcceptable()) {ServerSocketChannel server = (ServerSocketChannel) key.channel();SocketChannel clientChannel = server.accept();clientChannel.configureBlocking(false);clientChannel.register(selector, SelectionKey.OP_READ);System.out.println("Accepted new connection from client: " + clientChannel.getRemoteAddress());} else if (key.isReadable()) {SocketChannel clientChannel = (SocketChannel) key.channel();ByteBuffer buffer = ByteBuffer.allocate(1024);int bytesRead = clientChannel.read(buffer);if (bytesRead > 0) {buffer.flip();byte[] data = new byte[buffer.remaining()];buffer.get(data);String fileName = new String(data).trim();System.out.println("Received file request for: " + fileName);File file = new File(FILE_DIRECTORY + File.separator + fileName);if (file.exists() && file.isFile()) {try (FileInputStream fileInputStream = new FileInputStream(file)) {buffer = ByteBuffer.allocate(1024);while ((bytesRead = fileInputStream.read(buffer.array()))!= -1) {buffer.flip();clientChannel.write(buffer);buffer.clear();}}} else {System.out.println("File not found: " + fileName);clientChannel.write(ByteBuffer.wrap("File not found".getBytes()));}} else if (bytesRead == -1) {clientChannel.close();}}}}} catch (IOException e) {e.printStackTrace();}}
}

NIO 文件服务器客户端代码

import java.io.FileOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class NIOFileClient {private static final String SERVER_ADDRESS = "localhost";private static final int SERVER_PORT = 8081;private static final String DOWNLOAD_DIRECTORY = "downloads";public static void main(String[] args) {if (args.length!= 1) {System.out.println("Usage: java NIOFileClient <fileName>");return;}String fileName = args[0];try (SocketChannel socketChannel = SocketChannel.open()) {socketChannel.configureBlocking(false);socketChannel.connect(new InetSocketAddress(SERVER_ADDRESS, SERVER_PORT));while (!socketChannel.finishConnect()) {// 可以在此处执行其他任务}System.out.println("Connected to file server");ByteBuffer buffer = ByteBuffer.wrap(fileName.getBytes());socketChannel.write(buffer);buffer = ByteBuffer.allocate(1024);FileOutputStream fileOutputStream = new FileOutputStream(DOWNLOAD_DIRECTORY + "/" + fileName);int bytesRead;while ((bytesRead = socketChannel.read(buffer))!= -1) {buffer.flip();byte[] data = new byte[buffer.remaining()];buffer.get(data);if (new String(data).equals("File not found")) {System.out.println("File not found on server");break;}fileOutputStream.write(data);buffer.clear();}fileOutputStream.close();} catch (IOException e) {e.printStackTrace();}}
}

在这个 NIO 文件服务器示例中,服务端监听指定端口,当有客户端连接并发送文件名请求时,服务端检查文件是否存在,如果存在则将文件内容读取并发送给客户端;如果文件不存在,则向客户端发送 “File not found”。客户端连接到服务端,发送文件名请求,接收文件内容并保存到本地指定目录。如果接收到 “File not found”,则提示用户文件在服务器上不存在。

五、总结与展望

从 BIO 到 NIO 的演变,是 Java I/O 领域顺应时代发展的重大变革。BIO 作为 Java 早期的 I/O 模型,以其简单直观的编程方式,在早期的应用开发中发挥了重要作用 ,但在高并发场景下,其阻塞式的 I/O 操作和线程资源的高消耗,限制了系统的性能和扩展性。

NIO 的出现,为解决 BIO 的困境提供了全新的思路和方法。通过引入 Selector、Channel 和 Buffer 等核心组件,NIO 构建了基于事件驱动的非阻塞 I/O 模型,实现了一个线程管理多个 I/O 通道,大大提高了系统的并发处理能力和 I/O 操作效率 。在高并发场景下,NIO 的优势尤为显著,如聊天服务器、弹幕系统、文件服务器等应用场景中,NIO 能够高效地处理大量并发连接,提升系统的性能和响应速度 。

展望 Java I/O 的未来发展趋势,随着硬件技术的不断进步和应用场景的日益复杂,对 I/O 性能的要求也将越来越高。一方面,NIO 有望在现有基础上进一步优化和完善,提升其在不同场景下的性能表现和稳定性 。例如,在缓冲区管理、通道操作等方面进行更高效的实现,减少资源消耗和性能开销。另一方面,随着异步编程、分布式系统等技术的发展,Java I/O 可能会与这些技术更紧密地结合,以满足分布式、高并发环境下的复杂 I/O 需求 。如在分布式文件系统中,NIO 可以为数据的高效传输和处理提供支持;在异步通信框架中,NIO 的非阻塞特性能够更好地实现异步操作,提高系统的响应能力。此外,随着人工智能、大数据等新兴领域的快速发展,Java I/O 也需要不断演进,以适应这些领域对大规模数据处理和高速数据传输的需求 。

对于 Java 开发者来说,深入理解 BIO 和 NIO 的原理、特性及适用场景,能够根据不同的业务需求选择合适的 I/O 模型,是提升 Java 应用性能和开发效率的关键 。同时,关注 Java I/O 的发展动态,不断学习和掌握新的 I/O 技术和应用方法,将有助于在未来的 Java 开发中,更好地应对各种挑战,开发出更高效、更可靠的应用程序。

相关文章:

从BIO到NIO:Java IO的进化之路

引言 在 Java 编程的世界里&#xff0c;输入输出&#xff08;I/O&#xff09;操作是基石般的存在&#xff0c;从文件的读取写入&#xff0c;到网络通信的数据传输&#xff0c;I/O 操作贯穿于各种应用程序的核心。BIO&#xff08;Blocking I/O&#xff0c;阻塞式 I/O&#xff0…...

Mysql:数据库

Mysql 一、数据库概念&#xff1f;二、MySQL架构三、SQL语句分类四、数据库操作4.1 数据库创建4.2 数据库字符集和校验规则4.3 数据库修改4.4 数据库删除4.4 数据库备份和恢复其他 五、表操作5.1 创建表5.2 修改表5.3 删除表 六、表的增删改查6.1 Create(创建):数据新增1&#…...

深度学习系列--01.入门

一.深度学习概念 深度学习&#xff08;Deep Learning&#xff09;是机器学习的分支&#xff0c;是指使用多层的神经网络进行机器学习的一种手法抖音百科。它学习样本数据的内在规律和表示层次&#xff0c;最终目标是让机器能够像人一样具有分析学习能力&#xff0c;能够识别文字…...

【Elasticsearch】`auto_date_histogram`聚合功能详解

1.功能概述 auto_date_histogram是 Elasticsearch 提供的一种时间分桶聚合功能&#xff0c;它可以根据数据分布自动调整分桶的间隔&#xff0c;以生成指定数量的分桶。与传统的date_histogram不同&#xff0c;auto_date_histogram不需要用户手动指定时间间隔&#xff0c;而是根…...

php7.3安装php7.3-gmp扩展踩坑总结

环境&#xff1a; 容器里面为php7.3.3版本 服务器也为php7.3.3-14版本&#xff0c;但是因为业务量太大需要在服务器里面跑脚本 容器里面为 alpine 系统&#xff0c;安装各种扩展 服务器里面开发服为 ubuntu 16.04.7 LTS (Xenial Xerus) 系统 服务器线上为 ubuntu 20.04.6 LTS (…...

7. k8s二进制集群之Kube ApiServer部署

创建kube工作目录(仅在主节点上创建即可)同样在我们的部署主机上创建apiserver证书请求文件根据证书文件生成apiserver证书仅接着创建TLS所需要的TOKEN创建apiserver服务的配置文件(仅在主节点上创建即可)创建apiserver服务管理配置文件对所有master节点分发证书 & TOK…...

QT笔记——多语言翻译

文章目录 1、概要2、多语言切换2.1、结果展示2.2、创建项目2.2、绘制UI2.2、生成“.st”文件2.4、生成“.qm”文件2.5、工程demo 1、概要 借助QT自带的翻译功能&#xff0c;实现实际应用用进行 “多语言切换” 2、多语言切换 2.1、结果展示 多语言切换 2.2、创建项目 1、文件…...

【2025】camunda API接口介绍以及REST接口使用(3)

前言 在前面的两篇文章我们介绍了Camunda的web端和camunda-modeler的使用。这篇文章主要介绍camunda结合springboot进行使用&#xff0c;以及相关api介绍。 该专栏主要为介绍camunda的学习和使用 &#x1f345;【2024】Camunda常用功能基本详细介绍和使用-下&#xff08;1&…...

js面试some和every的区别

1.基础使用 some和every 都是数组的一个方法let num [1,2,3,4,5,6] let flag1 num.some((item,index,array)> item > 2)let flag2 num.every((item,index, array)> item > 2)1.some 遍历判断中是符合条件的值 一旦找到则不会继续迭代下去 直接返回 2.every 遍历…...

Vue 中如何嵌入可浮动的第三方网页窗口(附Demo)

目录 前言1. 思路Demo2. 实战Demo 前言 &#x1f91f; 找工作&#xff0c;来万码优才&#xff1a;&#x1f449; #小程序://万码优才/r6rqmzDaXpYkJZF 1. 思路Demo 以下Demo提供思路参考&#xff0c;需要结合实际自身应用代码 下述URL的链接使用百度替代&#xff01; 方式 1…...

【大数据技术】词频统计样例(hadoop+mapreduce+yarn)

词频统计(hadoop+mapreduce+yarn) 搭建完全分布式高可用大数据集群(VMware+CentOS+FinalShell) 搭建完全分布式高可用大数据集群(Hadoop+MapReduce+Yarn) 在阅读本文前,请确保已经阅读过以上两篇文章,成功搭建了Hadoop+MapReduce+Yarn的大数据集群环境。 写在前面 Wo…...

java进阶知识点

java回收机制 浅谈java中的反射 依赖注入的简单理解 通过接口的引用和构造方法的表达&#xff0c;将一些事情整好了反过来传给需要用到的地方~ 这样做得好处&#xff1a;做到了单一职责&#xff0c;并且提高了复用性&#xff0c;解耦了之后&#xff0c;任你如何实现&#xf…...

深度学习系列--02.损失函数

一.定义 损失函数&#xff08;Loss Function&#xff09;是机器学习和深度学习中用于衡量模型预测结果与真实标签之间差异的函数&#xff0c;它在模型训练和评估过程中起着至关重要的作用 二.作用 1.指导模型训练 提供优化方向&#xff1a;在训练模型时&#xff0c;我们的目…...

构建一个数据分析Agent:提升分析效率的实践

在上一篇文章中,我们讨论了如何构建一个智能客服Agent。今天,我想分享另一个实际项目:如何构建一个数据分析Agent。这个项目源于我们一个金融客户的真实需求 - 提升数据分析效率,加快决策速度。 从分析师的痛点说起 记得和分析师团队交流时的场景&#xff1a; 小张&#xff…...

在K8S中,如何把某个worker节点设置为不可调度?

在Kubernetes中&#xff0c;如果你想要把一个worker节点设置为不可调度&#xff0c;意味着你不想让Kubernetes调度器在这个节点上调度新的Pod。这通常用于维护或升级节点&#xff0c;或者当节点遇到硬件故障或性能问题时&#xff0c;要将某个worker节点设置为不可调度。 方法1…...

硬件电路基础

目录 1. 电学基础 1.1 原子 1.2 电压 1.3 电流 1.电流方向&#xff1a; 正极->负极,正电荷定向移动方向为电流方向&#xff0c;与电子定向移动方向相反。 2.电荷&#xff08;这里表示负电荷&#xff09;运动方向&#xff1a; 与电流方向相反 1.4 测电压的时候 2. 地线…...

5 前端系统开发:Vue2、Vue3框架(上):Vue入门式开发和Ajax技术

文章目录 前言一、Vue框架&#xff08;简化DOM操作的一个前端框架&#xff09;&#xff1a;基础入门1 Vue基本概念2 快速入门&#xff1a;创建Vue实例&#xff0c;初始化渲染&#xff08;1&#xff09;创建一个入门Vue实例&#xff08;2&#xff09;插值表达式&#xff1a;{{表…...

阿里 Java 岗个人面经分享(技术三面 + 技术 HR 面):Java 基础 +Spring+JVM+ 并发编程 + 算法 + 缓存

技术一面 20 分钟 1、自我介绍 说了很多遍了&#xff0c;很流畅捡重点介绍完。 2、问我数据结构算法好不好 挺好的&#xff08;其实心还是有点虚&#xff0c;不过最近刷了很多题也只能壮着胆子充胖子了&#xff09; 3、找到单链表的三等分点&#xff0c;如果单链表是有环的…...

vue2-给data动态添加属性

vue2-给data动态添加属性 1. 问题的来源 在VUe2中&#xff08;VUE3中使用了proxy&#xff0c;及时动态添加也能实现响应式&#xff09;&#xff0c;如果我们动态给data添加一个属性&#xff0c;会发现视图没有同步更新举个例子我们通过v-for遍历data中的一个属性list&#xf…...

Linux 文件和目录

Linux 文件和目录 文章目录 Linux 文件和目录Linux 目录Linux 目录配置的依据 --FHS目录树文件属性文件的分类一般权限 UGO特殊权限 suid\sgid\sticky隐藏属性 ATTR文件访问控制列表 ACL文件相关的命令权限的修改 chmod chown chgrp umaskchmodchgrpumask相关文档 /etc/profile…...

【大数据技术】本机DataGrip远程连接虚拟机MySQL/Hive

本机DataGrip远程连接虚拟机MySQL/Hive datagrip-2024.3.4VMware Workstation Pro 16CentOS-Stream-10-latest-x86_64-dvd1.iso写在前面 本文主要介绍如何使用本机的DataGrip连接虚拟机的MySQL数据库和Hive数据库,提高编程效率。 安装DataGrip 请按照以下步骤安装DataGrip软…...

Leetcode 3440. Reschedule Meetings for Maximum Free Time II

Leetcode 3440. Reschedule Meetings for Maximum Free Time II 1. 解题思路2. 代码实现 题目链接&#xff1a;3440. Reschedule Meetings for Maximum Free Time II 1. 解题思路 这一题某种意义上来说甚至是上一题Leetcode 3439的简化版本&#xff08;关于这一题的解答可以…...

专门记录台式电脑常见问题

1、蓝屏死机&#xff0c;检查内存硬盘和cpu 2、拆内存条&#xff0c;用橡皮擦金手指 3、放主板静电&#xff0c;扣主板电池 4、系统时间不正确&#xff0c;主板电池没电 5、开机键坏了 6、电脑主机的风扇转&#xff0c;正常通电运行&#xff0c;但显示器没信号。看键盘的num键&…...

[操作系统] 进程终止

在计算机操作系统中&#xff0c;进程&#xff08;Process&#xff09;是程序在运行中的实例&#xff0c;而进程的生命周期始于创建&#xff0c;终于终止。进程终止不仅仅意味着程序执行结束&#xff0c;还涉及资源的回收、状态的传递、以及可能的错误处理。在 Linux 和 Unix 系…...

[x86 ubuntu22.04]进入S4失败

目录 1 问题描述 2 解决过程 2.1 查看内核日志 2.2 新建一个交换分区 2.3 指定交换分区的位置 1 问题描述 CPU&#xff1a;G6900E OS&#xff1a;ubuntu22.04 Kernel&#xff1a;6.8.0-49-generic 使用“echo disk > /sys/power/state”命令进入 S4&#xff0c;但是无法…...

12.外观模式(Facade Pattern)

定义 外观模式&#xff08;Facade Pattern&#xff09; 是一种结构型设计模式&#xff0c;它通过为复杂的子系统提供一个统一的接口&#xff0c;使得子系统的使用更加简化。外观模式通常隐藏了复杂的内部子系统&#xff0c;使得客户端可以通过一个简单的接口与这些子系统进行交…...

ES6 入门教程:箭头函数、解构赋值及其他新特性详解

ES6 入门教程&#xff1a;箭头函数、解构赋值及其他新特性详解 ES6 入门教程&#xff1a;箭头函数、解构赋值及其他新特性详解引言什么是 ES6&#xff1f;箭头函数&#xff08;Arrow Functions&#xff09;1. 基本语法2. 常见特点&#xff08;1&#xff09;没有自己的 this 上下…...

win编译openssl

一、perl执行脚本 1、安装perl脚本 perl安装 2、配置perl脚本 perl Configure VC-WIN32 no-asm no-shared --prefixE:\openssl-x.x.x\install二、编译openssl 1、使用vs工具编译nmake 如果使用命令行nmake编译会提示“无法打开包括文件: “limits.h”“ 等错误信息 所以…...

51单片机看门狗系统

在 STC89C52 单片机中&#xff0c;看门狗控制寄存器的固定地址为 0xE1。此地址由芯片厂商在硬件设计时确定&#xff0c;但是它在头文件中并未给出&#xff0c;因此在使用看门狗系统时需要声明下这个特殊功能寄存器 sfr WDT_CONTR 0xE1; 本案将用一个小灯的工作状况来展示看门…...

探索 paraphrase-MiniLM-L6-v2 模型在自然语言处理中的应用

在自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;将文本数据转换为机器学习模型可以处理的格式是至关重要的。近年来&#xff0c;sentence-transformers 库因其在文本嵌入方面的卓越表现而受到广泛关注。本文将深入探讨 paraphrase-MiniLM-L6-v2 模型&#xff0c;这…...