Java Socket 之 NIO

2023-11-04

对于 TCP 或 UDP 的服务器,如何实现并发处理客户端。

最直观的想法就是为每个到来的请求,创建一个单独的线程来处理,但是这种方式未免太浪费资源了,那可以使用线程池来管理线程,这样可以节约资源。以 TCP 服务器举例。

首先需要定义一个需要提交到线程池中的任务。

public class TCPRunnable implements Runnable {private Socket mSocket;public TCPRunnable(Socket socket) {mSocket = socket;}@Overridepublic void run() {try {System.out.println("Handling client: " + mSocket.getRemoteSocketAddress());InputStream in = mSocket.getInputStream();OutputStream out = mSocket.getOutputStream();BufferedReader br = new BufferedReader(new InputStreamReader(in));String line;System.out.println("Client said: ");while ((line = br.readLine()) != null) {System.out.println(line);}out.write("Welcome!".getBytes());} catch (IOException e) {e.printStackTrace();} finally {try {if (mSocket != null) {mSocket.close();}} catch (IOException e) {e.printStackTrace();}}}
} 

在构造函数中,需要传入一个 Socket 实例,当任务提交到线程池后,Socket 的读写操作就在异步线程中执行。

现在可以改进下服务器端,只需要在获取 Socket 实例后提交任务即可

public class TCPServer1 {public static void main(String[] args) {ExecutorService mThreadPool = Executors.newCachedThreadPool();ServerSocket serverSocket = null;try {serverSocket = new ServerSocket(8890);while (true) {Socket socket = serverSocket.accept();mThreadPool.execute(new TCPRunnable(socket));}} catch (IOException e) {e.printStackTrace();} finally {try {if (serverSocket != null) {serverSocket.close();}} catch (IOException e) {e.printStackTrace();}}}
} 

使用线程池好像很完美,但是现在再思考一个总是,假如客户端希望与服务器保持一个长连接,那么很显然线程池也限制了客户端并发访问的数量,因为核心线程就那么几个。 那么可不可以增大线程池中的核心线程数量呢? 可以是可以,但是要增大多少呢?面对数以百万计的客户端,你选择不了!而且增大线程数量,只会带来更大的线程开销,包括线程调度以及上下文切换。 同时,我们还要面对一个总是,那就是多线程临界资源的访问,我们需要同步或者加锁,这些隐藏的开销是开发者无法控制的。

Java NIO 的到来解决了这些问题,并且可以让服务器同时处理上千个客户端,而且还可以保持良好的性能。那么本文就探讨下 NIO 到底强在哪里。

Channel

NIO 使用信道 (Channel) 来发送和接收数据,而不使用传统的流 (InputStream/OutputStream)。

Channel 实例代表了打开一个实体的连接,这些实体包括硬件设备,文件,网络套接字等等。 Channel 有个特色,在 Channel 上的操作,例如读写,都是线程安全的。

SelectableChannel

SelectableChannel 是一个抽象类,它实现了 Channel 接口,这个类比较特殊。

首先 SelectableChannel 可以是阻塞或者非阻塞模式。如果是阻塞模式,在这个信道上的任何 I/O 操作都是阻塞的直到 I/O 完成。 而如果是非阻塞模式,任何在这个信道上的 I/O 都不会阻塞,但是传输的字节数可能比原本请求的字节数要少,甚至一个也没有。

其次呢 SelectableChannel 可以被 Selector 用来多路复用,不过首先需要调用 selectableChannel.configureBlocking(false) 调整为非阻塞模式(nonblocking mode),这一点很重要。然后进行注册

SelectionKey register(Selector sel, int ops)
SelectionKey register(Selector sel, int ops, Object att) 

第一个参数代表要注册的 Selector 实例。关于 Selector 后面再讲。

第二个参数代表本通道感兴趣的操作,这些都定义在 SelectionKey 类中,如下

public static final int OP_READ = 1 << 0;
public static final int OP_WRITE = 1 << 2;
public static final int OP_CONNECT = 1 << 3;
public static final int OP_ACCEPT = 1 << 4; 

对于 SocketChannel ,它感兴趣的操作只有 OP_READ, OP_WIRTEOP_CONNECT,然而它并不包括 OP_ACCEPT。 而 ServerSocketChannel可以对这四个操作都感兴趣。为何?因为只有 ServerSocketChannelaccpet() 方法。

SocketChannelServerSocketChannel 都是 SelectableChannel 的子类。

第三个参数 Object att 是注册时的附件,也就是可以在注册的时候带点什么东西过去。

register() 方法会返回一个 SelectionKey 实例。SelectionKey 相当于一个 Java Bean,其实就是 register() 的三个参数的容器,它可以返回和设置这些参数

Selector selector();
int interestOps();
Object attachment() 

SocketChannel

SocketChannel 代表套接字通道(socket channel)。

SocketChannel 实例是通过它的静态的方法 open() 创建的

 public static SocketChannel open() throws IOException {return SelectorProvider.provider().openSocketChannel();}public static SocketChannel open(SocketAddress remote)throws IOException{// 1. ceate socket channelSocketChannel sc = open();try {// 2. connect channel's socket, blocking until connected or errorsc.connect(remote);} catch (Throwable x) {try {sc.close();} catch (Throwable suppressed) {x.addSuppressed(suppressed);}throw x;}assert sc.isConnected();return sc;} 

open() 方法仅仅是创建一个 SocketChannel 对象,而 open(SocketAddress remote) 就更进一步,它还调用了 connect(addr) 来连接服务器。

SocketChannelSelectableChannel 的子类,还记得前面 SelectableChannel 的特性吗?如果不配置阻塞模式,那么 SocketChannel 对象默认就是阻塞模式,那么 open(SocketAddress remote) 方法其实就是阻塞式打开服务器连接。而且在 SocketChannel 上任何 I/O 操作都是阻塞式的。

那么既然 SelectableChannel 可以在非阻塞模式下的任何 I/O 操作都不阻塞,那么我们可以先调用无参的 open() 方法,然后再配置为非阻塞模式,再进行连接,而这个连接就是非阻塞式连接,伪代码如下

// 创建 SocketChannel 实例
SocketChannel sc = SocketChannel.open();
// 调整为非阻塞模式
sr.configureBlocking(false);
// 连接服务器
sr.connect(remoteAddr); 

此时的 connect() 方法是非阻塞式的,我们可以通过 isConnectionPending() 方法来查询是否还在连接中,如果还在连接中我们可以做点其它事,而不用像创建 Socket 一样一起阻塞走到连接建立,在这里我们可以看到使用 NIO 的好处了。

如果 isConnectionPending() 返回了 false,那就代表已经建立连接了,但是我们还要调用 finishConnect() 来完成连接,这点需要注意。

用 SocketChannel 实现客户端

public class NonBlockingTCPClient {public static void main(String[] args) {byte[] data = "hello".getBytes();SocketChannel channel = null;try {// 1. open a socket channelchannel = SocketChannel.open();// adjust to be nonblockingchannel.configureBlocking(false);// 2. init connection to server and repeatedly poll with complete// connect() and finishConnect() are nonblocking operation, both return immediatelyif (!channel.connect(new InetSocketAddress(InetAddress.getLocalHost(), 8899))) {while (!channel.finishConnect()) {System.out.print(".");}}System.out.println("Connected to server...");ByteBuffer writeBuffer = ByteBuffer.wrap(data);ByteBuffer readBuffer = ByteBuffer.allocate(data.length);int totalBytesReceived = 0;int bytesReceived;// 3. read and write byteswhile (totalBytesReceived < data.length) {if (writeBuffer.hasRemaining()) {channel.write(writeBuffer);}if ((bytesReceived = channel.read(readBuffer)) == -1) {throw new SocketException("Connection closed prematurely");}totalBytesReceived += bytesReceived;System.out.print(".");}System.out.println("Server said: " + new String(readBuffer.array()));} catch (IOException e) {e.printStackTrace();} finally {// 4 .close socket channeltry {if (channel != null) {channel.close();}} catch (IOException e) {e.printStackTrace();}}}
} 

第一步,创建 SocketChannel 实例,并配置为非阻塞模式,只有在非阻塞模式下,任何在 SocketChannel 实例上的 I/O 操作才是非阻塞的。这样我们的客户端就是一个非阻塞式客户端,也就可以提升客户端性能。

第二步,用 connect() 方法连接服务器,同时用 while 循环不断检测并完全连接。 其实我们可以不用这样盲等,这里只是为了演示连接的过程。 当你在需要马上进行 I/O 操作前,必须要用 finishConnect() 完成连接过程。

第三步,用 ByteBuffer 读写字节,这里我们为何和一个 while 循环不断地读写呢? 还记得前面讲 SelectableChannel 非阻塞时的特性吗? 如果一个 SelectableChannel 为非阻塞模式,它的 I/O 操作读写的字节数可能比实际的要少,甚至没有。 所以我们这里用循环不断的读写,保证读写完成。

官方对 SocketChannel.write() 有一段话是这样说的: A socket channel in non-blocking mode, for example, cannot write any more bytes than are free in the socket’s output buffer.

ServerSocketChannel

ServerSocketChannel 类代表服务器端套接字通道(server-socket channel)。

ServerSocketChannelSocktChannel 一样,需要通过静态方法 open() 来创建一个实例,创建后,还需要通过 bind() 方法来绑定到本地的 IP 地址和端口

ServerSocketChannel bind(SocketAddress local)
ServerSocketChannel bind(SocketAddress local, int limitQueue) 

参数 SocketAddress local 代表本地 IP 地址和端口号,参数 int limitQueue 限制了连接的数量。

Selector

SelectorSelectableChannel 的多路复用器,可以用一个 Selector 管理多个 SelectableChannel。例如,可以用 Selector 在一个线程中管理多个 ServerSocketChannel,那么我们就可以在单线程中同时监听多个端口的请求,这简直是美不可言。 从这里我们也可以看出使用 NIO 的好处。

创建 Selector 实例

Selector 实例也需要通过静态方法 open() 创建。

注册 SelectableChannel

前面说过,我们需要调用 SelectableChannelregister() 来向 Selector 注册,它会返回一个 SelctionKey 来代表这次注册。

选择通道

前面说过,可以通过 Selector 管理多个 SelectableChannel,它的 select() 方法可以监测哪些信道已经准备好进行 I/O 操作了,返回值代表了这些 I/O 的数量。

int select()
int select(long timeout)
int selectNow() 

当调用 select() 方法后,它会把代表已经准备好 I/O 操作的信道的 SelectionKey 保存在一个集合中,可以通过 selectedKeys() 返回。

Set<SelectionKey> selectedKeys() 

select() 的三个方法,从命名就可以看出这几个方法的不同之处,第一个方法是阻塞式调用,第三个方法设置了一个超时时间,第三个方法是立即返回。

wakeUp()

如果调用 selcet() 方法会导致线程阻塞,甚至无限阻塞,wakeUp() 方法是唤醒那些调用 select() 方法而处于阻塞状态的线程。

使用 Selector 和 ServerSocketChannel 实现服务器

package com.ckt.sockettest;

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 TCPChannelServer {public static void main(String[] args) {Selector selector = null;try {// 1. open a selectorselector = Selector.open();// 2. listen for server socket channelServerSocketChannel ssc = ServerSocketChannel.open();// must to be nonblocking mode before registerssc.configureBlocking(false);// bind server socket channel to port 8899ssc.bind(new InetSocketAddress(8899));// 3. register it with selectorssc.register(selector, SelectionKey.OP_ACCEPT);while (true) { // run forever// 4. select ready SelectionKey for I/O operationif (selector.select(3000) == 0) {continue;}// 5. get selected keysSet<SelectionKey> selectionKeys = selector.selectedKeys();Iterator<SelectionKey> iterator = selectionKeys.iterator();// 6. handle selected key's interest operationswhile (iterator.hasNext()) {SelectionKey key = iterator.next();if (key.isAcceptable()) {ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();// get socket channel from server socket channelSocketChannel clientChannel = serverSocketChannel.accept();// must to be nonblocking before register with selectorclientChannel.configureBlocking(false);// register socket channel to selector with OP_READclientChannel.register(key.selector(), SelectionKey.OP_READ);}if (key.isReadable()) {// read bytes from socket channel to byte bufferSocketChannel clientChannel = (SocketChannel) key.channel();ByteBuffer readBuffer = ByteBuffer.allocate(10);int readBytes = clientChannel.read(readBuffer);if (readBytes == -1) {System.out.println("closed.......");clientChannel.close();} else if (readBytes > 0) {String s = new String(readBuffer.array());System.out.println("Client said: " + s);if (s.trim().equals("Hello")) {// attachment is content used to writekey.interestOps(SelectionKey.OP_WRITE);key.attach("Welcome!!!");}}}if (key.isValid() && key.isWritable()) {SocketChannel clientChannel = (SocketChannel) key.channel();// get content from attachmentString content = (String) key.attachment();// write content to socket channelclientChannel.write(ByteBuffer.wrap(content.getBytes()));key.interestOps(SelectionKey.OP_READ);}// remove handled key from selected keysiterator.remove();}}} catch (IOException e) {e.printStackTrace();} finally {// close selectorif (selector != null) {try {selector.close();} catch (IOException e) {e.printStackTrace();}}}}
} 

第一步,创建 Selector 实例。

第二步,创建 ServerSocketChannel 实例,配置为非阻塞模式,绑定本地端口。

第三步,把 ServerSocketChannel实例 注册到 Selector 实例中。

第四步,选择一些准备好 I/O 操作的信道,这里设置了3秒超时时间,也就是阻塞3秒。

第五步,获取选中的 SelectionKey 的集合。

第六步,处理 SelectionKey 的感兴趣的操作。注册到 selector 中的 serverSocketChannel 只能是 isAcceptable() ,因此通过它的 accept() 方法,我们可以获取到客户端的请求 SocketChannel 实例,然后再把这个 socketChannel 注册到 selector 中,设置为可读的操作。那么下次遍历 selectionKeys 的时候,就可以处理那么可读的操作。

总结

通过这篇文章,概要性的描述了 Java Socket 的轮廓。 然而我在实际的工作中并没有接触这方面内容,因此这篇文章只是肤浅的入门,如果日后有机会深入学习,再来改善这些文章内容。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java Socket 之 NIO 的相关文章

  • Android PhoneGap 插件,UI 选项卡栏,调整 WebView 大小

    我正在创建一个美味的 PhoneGap 插件 希望一旦它能被打开 准备好了 插件基本完成了 我只需要一个漂亮的用户界面 相互作用 简而言之 我想创建一个 本机 android 工具栏组件 如果您实现 PhoneGap UIControls
  • Java - 如何将特殊字符放入字符串中

    Java 似乎有很好的字符串处理能力 尽管如此 我还是遇到了最简单的问题 我需要动态字符串 它们在运行时更改 因此字符串类型不是一个好的选择 因为它们是不可变的 所以我使用字符数组 设置起来有点痛苦 但至少它们是可以修改的 我想创建一个字符
  • 位图内存不足错误

    我对这个错误有疑问 我从 URL 制作网站图标解析器 我这样做是这样的 public class GrabIconsFromWebPage public static String replaceUrl String url StringB
  • URL.setURLStreamHandlerFactory

    我正在使用带有嵌入式 Jetty 的可执行 jar 开发一个 Web 应用程序 我的jar包含一个依赖jar jar in jar 我参考了JarRsrcLoader and RsrcURLStreamHandlerFactory由 Ecl
  • 使用 ChannelExec 的命令未执行 - Jsch

    我正在使用 Jsch 在服务器中创建一个文件并执行一些命令 对于文件创建 它工作正常 但是对于命令执行 则不然 它保持状态 1 仍在处理它 并永远保持该状态 这种情况发生在 shell 执行或我尝试成为 root 时 请按照以下方法操作 p
  • Spring3/Hibernate3/TestNG:有些测试给出 LazyInitializationException,有些则没有

    前言 我在单元测试中遇到了 LazyInitializationException 的问题 而且我很难理解它 正如你从我的问题中看到的那样Spring 中的数据库会话 https stackoverflow com questions 13
  • 将过滤器添加到 Eclipse 中的 Project Explorer

    我想向 Project Explorer 添加一个新的过滤器 以向用户隐藏一些在 Eclipse RCP 应用程序中自动创建的项目 到目前为止我已经找到了两个扩展点 org eclipse ui ide resourceFilters 允许
  • 尝试使用 JRI 将 R 与我的 Java 应用程序集成,但出现错误。谁能解释一下原因和解决办法吗?

    我需要将 Java 与 R 集成来运行一些数学命令并使用 R 的功能进行绘图 以下部分代码给出了错误 public static void main String args HelloRWorld r new HelloRWorld r h
  • 是否有任何API可以将Microsoft Exchange服务器与Java应用程序集成以进行任务同步?

    我正在尝试将 Java Web 应用程序与 Microsoft Exchange 服务器集成以实现双向日历 即任务 同步 是否有用于此集成的 Java 开源 商业 API 谢谢 文卡特 看一眼j 交易所 http sourceforge n
  • 有多少种方法可以将位图转换为字符串,反之亦然?

    在我的应用程序中 我想以字符串的形式将位图图像发送到服务器 我想知道有多少种方法可以将位图转换为字符串 现在我使用 Base64 格式进行编码和解码 它需要更多的内存 是否有其他可能性以不同的方式做同样的事情 从而消耗更少的内存 现在我正在
  • 如何在 Eclipse 中使用其他外部 jar 依赖项创建不可运行/不可执行的 jar

    我无法通过 Eclipse 导出向导创建普通的 jar 不可运行 不可执行 它仅创建 jar 文件 但不会导出依赖的 jar 从而在从其他类调用导出的 jar 的方法时出现错误 请帮助 非常感谢 kurellajunior的建议 它是通过使
  • Java-如何将黑白图像加载到二进制中?

    我在 FSE 模式下使用 Java 和 swing 我想将完全黑白图像加载为二进制格式 最好是二维数组 并将其用于基于掩码的每像素碰撞检测 我什至不知道从哪里开始 过去一个小时我一直在研究 但没有找到任何相关的东西 只需将其读入Buffer
  • 在 Java 中将弯音发送到 MIDI 音序器

    我了解启动和运行 MIDI 音序器的基础知识 并且希望能够在播放过程中增加 减小序列的音高 但弯音是发送到合成器而不是音序器的消息 我尝试将音序器的接收器设置为合成器的发射器 当我发送弯音短消息时 音序器保持相同的音调 但随后合成器以新的弯
  • 如何将 arraylist 从 servlet 传递到 javascript?

    我通过在属性中设置数组列表并将其转发到 jsp 来从 servlet 传递数组列表 Servlet ArrayList
  • 改变for循环的顺序?

    我遇到一种情况 我需要根据用户输入以不同的顺序循环遍历 xyz 坐标 所以我是 3D 空间中的一个区域 然后是一组像这样的 for 循环 for int x 0 x lt build getWidth x for int y 0 y lt
  • 了解 Spark 中的 DAG

    问题是我有以下 DAG 我认为当需要洗牌时 火花将工作划分为不同的阶段 考虑阶段 0 和阶段 1 有些操作不需要洗牌 那么为什么 Spark 将它们分成不同的阶段呢 我认为跨分区的实际数据移动应该发生在第 2 阶段 因为这里我们需要cogr
  • Java:由 HTTP 连接创建的等待连接线程存活时间很长

    我有一个服务器端代码 用于检查 SOAP 服务是否已启动 代码如下 String response while response length 0 try final URL url new URL DummySoapServiceURL
  • 失败时石英重试

    假设我有一个这样配置的触发器
  • 摩尔斯电码 至 英语

    我现在的问题是让 摩尔斯电码转英语 正常工作 将英语转换为莫尔斯电码的第一部分工作正常 我知道以前已经有人问过这个问题 但我不知道我做错了什么 我知道我需要在某个地方进行拆分 但我只是不确定将其放在代码中的何处 现在 莫尔斯电码到英语的部分
  • 如何捕获 try-with-resource 语句中 close 方法抛出的异常

    我正在读关于try with resourceJava 中的语句可用于指定任意数量的资源 try Resource1 res1 initialize code Resource1 res2 initialize code statement

随机推荐

  • sql注入基础

    0x01sql语句基础 1 select语句 格式 select from table 从table表中获取字段信息 select from table where 在满足where后的条件下查询字段信息 2 insert语句 格式 ins
  • STM32通过串口2使用ESP8266WIFI模块连接新大陆云平台

    目录 使用硬件 分步骤 配置TCP连接 连接WIFI 1 使用ESP8266的复位引脚进行复位 2 发送基本AT指令 3 连接新大陆 4 新大陆云平台显示在线及上传数据测试成功 5 串口显示 5 涉及的函数 发生AT检测WIFI模块错误 如
  • JetBrains设置inline hint的背景色、前景色

    如题 修改IDE的hint前景色和背景色 修改后结果
  • 元宇宙的价值究竟在哪 有哪些方向值得重点关注?关于元宇宙进行详细介绍

    1992年 第一次经济危机持续至此给全球带来了极大的打击 而在互联网行业 网络不再是政府和军队专属的使用品 Dephi开始为他们的客户提供在线网络服务 并逐渐的从电子邮件服务发展到了全方位的网络服务 这一年对于整个互联网而言具有里程碑的历史
  • 【Vue3】vite配置css 的sourceMap ,以及文件引用配置别名

    Vite 2 9配置浏览器加载 CSS 源映射 vite config ts 有一个devSourcemap属性css可以设置为true 这是vite config ts我目前正在使用的文件 export default defineCon
  • [论文阅读] (17)CCS2019 针对PowerShell脚本的轻量级去混淆和语义感知攻击检测(经典)

    娜璋带你读论文 系列主要是督促自己阅读优秀论文及听取学术讲座 并分享给大家 希望您喜欢 由于作者的英文水平和学术能力不高 需要不断提升 所以还请大家批评指正 非常欢迎大家给我留言评论 学术路上期待与您前行 加油 前一篇总结了Powershe
  • 仿真软件都在这里了!20+国内外自动驾驶仿真软件大盘点

    编辑 智车科技 原文链接 https mp weixin qq com s nG48GndQVb7rFtMdjYUU3Q 点击下方卡片 关注 自动驾驶之心 公众号 ADAS巨卷干货 即可获取 点击进入 自动驾驶之心 仿真测试 技术交流群 导
  • 生态伙伴

    法律人的日常工作中 离不开案例文书 法律法规的检索 而如何高效 便利的进行内容检索 一直困扰着法律人 本期飞书生态伙伴 觅律搜索 是一款专门为法律人量身定制的智能法律信息检索工具 收录超过5000万份裁判文书 权威案例 法律法规 律师律所等
  • Thymeleaf 提示: Cannot perform conversion to XML from legacy HTML:

    SpringBoot 1 5 x 集成Thymeleaf 2 1 x 提示如下错误信息 http nio 9096 exec 1 ERROR org thymeleaf TemplateEngine THYMELEAF http nio 9
  • 加密货币:我们为何而战?

    在 加密货币 领域中肆虐的冲突是无止境的 这些激烈的争论冲突涉及到各个方面 参与各方也几乎不去尝试达成双方都能接受的妥协让步 有趣的是 站在这些争议焦点的对立面往往是同一群人 从加密货币极繁主义和财富的分配 到治理和共识算法等等 这些争议的
  • Ubuntu/linux 下安装jdk和eclipse,超详细教程

    1 首先下载jdk和eclipse jdk官方下载网址 http www oracle com technetwork java javase downloads index html 官方有时候下的很慢很慢 百度网盘现成的jdk8 htt
  • 保研之路——中山大学数据科学与计算机学院直硕夏令营

    中山大学数据科学与计算机学院直硕夏令营 个人情况 高校复试参与情况 中山大学数据科学与计算机学院直硕 7 14 7 20 结语 嗯 抱着不白花这么多路费住宿费的初衷准备写一个保研经验贴 希望学弟学妹少花点钱吧orz 我的战术大概是只要学校给
  • stm32实用篇4: stm32数据类型长度

    由于经常会忘记stm32的数据类型长度 测试一下 DEBUG INFO stm32数据类型长度 DEBUG INFO char d byte sizeof char DEBUG INFO short d byte sizeof short
  • 算法:归并排序和快排的区别

    一 二者比较 归并排序和快排的相同点 1 利用分治思想 2 具体实现都用递归 归并排序和快排的不同点 1 先分解再合并 归并排序先递归分解到最小粒度 然后从小粒度开始合并排序 自下而上的合并排序 2 边分解边排序 快速排序每次分解都实现整体
  • Educoder_web实训作业——写在最后

    今天终于把最后一章的web发了出来 这也是我第一次完整的将一个实训作业写成一个专栏推送 其实 这不是我第一次做关卡答案的文章了 上个学期Java实训的时候 由于当时自身也有很多题不是特别清楚 就上网搜了一下 没想到发现网上面已经有人开始再发
  • MFC中OnTimer定时器用法

    一 单个定时器用法 定时器工作主要流程 设置定时器SetTimer 时间到后调用OnTimer函数 关闭定时器KillTimer 可以在程序初始化用SetTimer函数弄成多个线程类似 并行进行多个函数功能 1 1 SetTimer H n
  • Python实现排队论——多坑位仿真(未使用仿真库,纯手写仿真)

    Python实现排队论 多坑位厕所 在一次偶然机会 接触到运筹学的排队论问题 于是简单尝试了一下硬撸代码 纯手打仿真 没有使用仿真库 建议大家可以学习Simpy库 用以仿真 一 案例 主要是基于 蒙特卡罗思想 求解 单坑位 排队等待时间问题
  • 动手做一个简单的智能小车

    动手做一个简单的智能小车 来到CNDN一年了 看到了许多大佬的杰出作品 也该写点什么来回馈给大家了前不久接触了单片机 想提前进行实践一下所以有想法做一个实体出来 想来想去难的怕自己搞不定 但是还好找到了志同道合的王同学 一起搞一个智能小车
  • 数据结构与算法课程笔记(十)

    实验十 图的存储结构与遍历 一 实验目的 二 实验环境 三 实验内容 一 实验目的 掌握图的邻接矩阵表示方法 理解基于邻接矩阵的深度优先 广度优先遍历方法的实现 掌握图的邻接表表示方法 理解基于邻接表的深度优先 广度优先遍历方法的实现 二
  • Java Socket 之 NIO

    对于 TCP 或 UDP 的服务器 如何实现并发处理客户端 最直观的想法就是为每个到来的请求 创建一个单独的线程来处理 但是这种方式未免太浪费资源了 那可以使用线程池来管理线程 这样可以节约资源 以 TCP 服务器举例 首先需要定义一个需要