Netty学习(NIO基础)

NIO基础

三大组件

Channel and Buffer

在这里插入图片描述
常用的只有ByteBuffer
在这里插入图片描述

Selector(选择器)

结合服务器的设计演化来理解Selector

多线程版设计

最早在nio设计出现前服务端程序的设计是多线程版设计,即一个客户端对应一个socket连接,一个连接用一个线程处理,每个线程专管一个连接
在这里插入图片描述
由此造成的缺点有

  1. 内存占用高, 如果一个线程默认1m大小,1000个链接就是1g
  2. CPU在线程之间的上下文切换成本高
  3. 只适合连接数少的场景
线程池版设计

socketAPI工作在阻塞模式下,同一时刻内一个线程只能处理一个客户端的socket连接,切必须等待线程处理完成当前socket连接,且必须等断开旧的连接后才能处理新的socket连接,即使旧的连接没有任何读写请求,线程没有得到充分的利用

早期的Tomcat就采用的线程池版设计阻塞式io,比较适合http请求

在这里插入图片描述

Selector设计

Channel代表服务器和客户端连接,数据读写的通道,

将客户端连接服务器的各种事件操作通过Channel细致化,Selector是负责监听Channel请求的工具,将监听到的请求交给线程,如果Channel的流量太高,其他Channel会被搁置,所以只适合流量低连接数多的场景

在这里插入图片描述

ByteBuffer

基本使用

内存有限,缓存区不能跟文件一样大小增大,所以分多次读取

@Slf4j
public class AppTest {public static void main(String[] args) {// 获取fileChannel的方法 1.输入输出流  2.RandomAccessFiletry (FileChannel channel = new FileInputStream("data.txt").getChannel()) {// 准备缓冲区  获取一块内存,大小由allocate决定ByteBuffer buffer = ByteBuffer.allocate(10);while (true){//从Channel读取数据向buffer写入int len = channel.read(buffer);log.debug("读取到的字节数{}", len);if (len == -1){ //没有内容了break;}//打印buffer的内容buffer.flip();// 切换至读模式while (buffer.hasRemaining()) {// 是否还有未读数据byte b = buffer.get();log.debug("实际字节{}",(char) b);}buffer.clear();// 切换为写模式}} catch (IOException e) {}}
}

ByteBuffer结构

ByteBuffer有以下重要属性

  • capacity(容量)
  • position(读写指针)
  • limit(读写限制)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

常见方法

分配空间

在这里插入图片描述

在这里插入图片描述

获取数据

byte b = buffer.get();
byte b = buffer.get(1);// 不会改变position值

字符串转ByteBuffer方法

在这里插入图片描述

后两种会直接切换到读模式

Scattering Reads 分散读集中写

在这里插入图片描述
在这里插入图片描述

黏包、半包


public class TestByteBufferExam {public static void main(String[] args) {/*网络上有多条数据发送给服务端,数据之间使用 \n 进行分隔但由于某种原因这些数据在接收时,被进行了重新组合,例如原始数据有3条为Hello,world\nI'm zhangsan\nHow are you?\n变成了下面的两个 byteBuffer (黏包,半包)Hello,world\nI'm zhangsan\nHow are you?\n现在要求你编写程序,将错乱的数据恢复成原始的按 \n 分隔的数据*/ByteBuffer source = ByteBuffer.allocate(32);source.put("Hello,world\nI'm zhangsan\nHo".getBytes());split2(source);source.put("w are you?\n".getBytes());//追加值split2(source);}private static void split(ByteBuffer source) {source.flip();for (int i = 0; i < source.limit(); i++) {// 找到一条完整消息if (source.get(i) == '\n') {//不改变position值int length = i + 1 - source.position();// 把这条完整消息存入新的 ByteBufferByteBuffer target = ByteBuffer.allocate(length);// 从 source 读,向 target 写for (int j = 0; j < length; j++) {target.put(source.get());//改变了position值}debugAll(target);}}source.compact();//未读完部分向前压缩并切换为写模式}
}

文件编程

重点是网络编程所以文件编程了解就行

FileChannel

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

传输数据

在这里插入图片描述

Path

在这里插入图片描述
在这里插入图片描述

Files

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

FilesWalkFileTree

更便捷的遍历文件的API

private static void m1() throws IOException {AtomicInteger dirCount = new AtomicInteger();AtomicInteger fileCount = new AtomicInteger();Files.walkFileTree(Paths.get("C:\\Program Files\\Java\\jdk1.8.0_91"), new SimpleFileVisitor<Path>(){//进入目录@Overridepublic FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {System.out.println("进入目录====>"+dir);dirCount.incrementAndGet();return super.preVisitDirectory(dir, attrs);}//操作文件@Overridepublic FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {Files.delete(file);return super.visitFile(file, attrs);}//退出目录@Overridepublic FileVisitResult postVisitDirectory(Path file, BasicFileAttributes attrs) throws IOException {System.out.println(file);fileCount.incrementAndGet();return super.visitFile(file, attrs);}});System.out.println("dir count:" +dirCount);System.out.println("file count:" +fileCount);
}

网络编程

阻塞模式

阻塞模式下,服务器每次只能执行一个连接,单线程模式运行,必须等待客户端连接和客户端信息,否则阻塞,线程停止运行等待客户端,资源效率利用低

@Slf4j
public class Server {public static void main(String[] args) throws IOException {//使用nio来理解阻塞模式,单线程// 0. ByteBufferByteBuffer buffer = ByteBuffer.allocate(16);// 1. 创建的了服务器ServerSocketChannel ssc = ServerSocketChannel.open();// 2. 绑定监听端口ssc.bind(new InetSocketAddress(8080));// 3. 连接集合List<SocketChannel> channels = new ArrayList<>();while (true) {// 4. accept建立与客户端连接,socketChannel用来与客户端之间通信log.debug("connecting...");SocketChannel sc = ssc.accept(); // 如果客户端没启动,将会阻塞在这里等待客户端启动连接的请求log.debug("connected... {}", sc);channels.add(sc);for (SocketChannel channel : channels) {// 5. 接受客户端发生的数据log.debug("before read... {}",channel);channel.read(buffer);//如果客户端没有数据返回,将会阻塞等待客户端返回数据buffer.flip();debugRead(buffer);buffer.clear();log.debug("after read... {}",channel);}}}
}-------public class Client {public static void main(String[] args) throws IOException {SocketChannel sc = SocketChannel.open();sc.connect(new InetSocketAddress("localhost",8080));System.out.println("waiting...");}
}

非阻塞模式

非阻塞模式下,不管客户端是否有链接或返回数据,线程都不会停止,但会一直轮训尝试获取连接和数据,资源浪费

@Slf4j
public class Server {public static void main(String[] args) throws IOException {//使用nio来理解阻塞模式,单线程// 0. ByteBufferByteBuffer buffer = ByteBuffer.allocate(16);// 1. 创建的了服务器ServerSocketChannel ssc = ServerSocketChannel.open();ssc.configureBlocking(false);//非阻塞模式// 2. 绑定监听端口ssc.bind(new InetSocketAddress(8080));// 3. 连接集合List<SocketChannel> channels = new ArrayList<>();//非阻塞模式下  循环一直进行,轮训等待连接,还是有资源浪费while (true) {SocketChannel sc = ssc.accept(); //  非阻塞模式,如果没有客户端连接,sc返回null 线程继续运行if (sc != null){// 4. accept建立与客户端连接,socketChannel用来与客户端之间通信log.debug("connected... {}", sc);channels.add(sc);}for (SocketChannel channel : channels) {// 5. 接受客户端发生的数据log.debug("before read... {}",channel);int read = channel.read(buffer);// 非阻塞,线程继续运行,如果没有返回数据read返回0if (read > 0){buffer.flip();debugRead(buffer);buffer.clear();log.debug("after read... {}",channel);}}}}
}

Selector

Selector底层有两个keys的集合,keys表示注册在selector中的Channel集合,selectedKeys表示上述Channel参数的事件集合,selectedKeys集合中的事件必须及时消费处理,并及时移除,否则selector会认为该事件未处理造成重复处理死循环
在这里插入图片描述

  • accept serverSocket独有事件,客户端发起连接请求就会触发此事件
  • connect 是客户端连接建立后触发的事件
  • read 数据可读事件
  • write 可写事件

第三步,把Channel注册在selector中时,在内部注册SelectionKey的set集合,它存储了各个事件key,当事件被处理时应当从SelectionKey集合中移除它,否则有key而没有待处理的事件会报错空指针,所以使用Iterator迭代器循环,能移除循环到的key


@Slf4j
public class Server {public static void main(String[] args) throws IOException {// 1. 创建 Selector,管理多个 ChannelSelector selector = Selector.open();ServerSocketChannel ssc = ServerSocketChannel.open();ssc.configureBlocking(false);// 2. 建立Selector和Channel的联系(注册)// selectionKey 绑定一个事件SelectionKey sscKey = ssc.register(selector, 0, null);// 这个key绑定accept事件,只关注accept事件sscKey.interestOps(SelectionKey.OP_ACCEPT);log.debug("register key:{}", sscKey);ssc.bind(new InetSocketAddress(8080));while (true) {// 3. select方法,没有事件发生则线程阻塞,有事件发生则线程恢复运行// select 在事件未处理时不会阻塞,事件发生后要么取消要么处理,不能置之不理selector.select();// 4. 处理事件,selectKeys 内部包含了所有发生的事件Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();//处理key时,要从selectKeys集合中删除,否则有key而没有事件则会报错空指针iterator.remove();log.debug("key: {}", key);// 5. 区分事件类型if (key.isAcceptable()) {//如果是链接ServerSocketChannel channel = (ServerSocketChannel) key.channel();//若果不处理链接,则第三步 selector 会认为事件未处理,未完成连接的消费,不会阻塞SocketChannel sc = channel.accept();sc.configureBlocking(false);SelectionKey scKey = sc.register(selector, 0, null);scKey.interestOps(SelectionKey.OP_READ);log.debug("{}", sc);log.debug("scKey:{}", scKey);} else if (key.isReadable()) {//如果是 readSocketChannel channel = (SocketChannel) key.channel();ByteBuffer buffer = ByteBuffer.allocate(16);channel.read(buffer);buffer.flip();debugRead(buffer);}//key.cancel();  取消事件}}}
}

在这里插入图片描述

处理客户端断开

如果客户端异常断开,在catch块中删除key,因为无论是否有读数据,都会产生read返回值,正常断开的返回值是-1
在这里插入图片描述

消息边界问题

当Buffer小于消息长度时,消息接受不完整会造成消息乱码或失效
在这里插入图片描述
可能会出现的情况,10字节大小的Buffer在接受13字节大小的消息时,只打印了后3个字节的的消息,因为当Buffer一次读没有接受完,服务器会自动触发两次读事件,将没有读完的内容再次读取,第一次读到Buffer就被覆盖了。

Buffer应该重新设计成,可自动扩容和非局部变量
在这里插入图片描述

在这里插入图片描述

附件与扩容

在向selector中注册keys时,可以为此key添加att参数,即附件参数,附件参数在双方交互时都可以获取修改
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

处理内容过多问题

如果发生数据大于Buffer 缓冲区,会触发多次读事件,这时如果有其他的客户端事件是处理不了的,相当于被一个客户端连接阻塞了,一直在处理那个客户端的内容发送,因为发送缓冲区被占满暂时无法发送,这时要充分利用可以选择去读

在这里插入图片描述

服务端的写操作会尝试写入尽可能多的字节,但是写入缓冲区满了是无法写的,这时候write返回0,我们优化让他关注可写事件,不要做无谓的尝试,可写的时候再写入。

最终改造

服务器在向客户端第一次写操作没写完时,给当前只关注写操作SelectionKey事件追加加上 读操作事件的值,这样大数据量一次没写完,服务器自动触发的写操作可以被捕获,能写操作再写操作,不会因为缓冲区满产生不必要的写操作造成阻塞


public class WriteServer {public static void main(String[] args) throws IOException {ServerSocketChannel ssc = ServerSocketChannel.open();ssc.configureBlocking(false);Selector selector = Selector.open();ssc.register(selector, SelectionKey.OP_ACCEPT);ssc.bind(new InetSocketAddress(8088));while (true) {selector.select();Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();iterator.remove();if (key.isAcceptable()) {SocketChannel sc = ssc.accept();sc.configureBlocking(false);SelectionKey scKey = sc.register(selector, 0, null);scKey.interestOps(SelectionKey.OP_READ);// 1. 向客户端发送大量数据StringBuilder sb = new StringBuilder();for (int i = 0; i < 5000000; i++) {sb.append("a");}ByteBuffer buffer = Charset.defaultCharset().encode(sb.toString());// 2. 返回代表实际写入的字节数int write = sc.write(buffer);System.out.println(write);// 3. 判断是否有剩余字节未写完if (buffer.hasRemaining()) {// 4. 关键改造,让客户端关注可写事件,能写入时再写入  在原有关注数值上加上对应可写事件的数值常量scKey.interestOps(scKey.interestOps() + SelectionKey.OP_WRITE);// 5. 把未写完的数据挂到scKey的附件上scKey.attach(buffer);}} else if (key.isWritable()) {//关注写事件ByteBuffer buffer = (ByteBuffer) key.attachment();SocketChannel sc = (SocketChannel) key.channel();int write = sc.write(buffer);System.out.println(write);// 6. 清理操作if (!buffer.hasRemaining()) {// 清理Buffer  将附件设为空key.attach(null);// 写完毕 不再需要关注写事件key.interestOps(key.interestOps() - SelectionKey.OP_WRITE);}}}}}
}
总结

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

多线程优化

在这里插入图片描述
boss线程的selector只处理连接事件,worker线程的selector则专门处理读写事件
在这里插入图片描述

线程队列优化

boss线程内调用 worker 的 register方法,其还是由boss线程调用,只有run方法中的方法才回另开线程,实现了 boss线程的selector只处理连接事件,worker线程的selector则专门处理读写事件。
其中利用线程队列的方法延迟select的注册,因为队列中的sc注册只消费一次,所以完成注册后select只监听read事件

@Slf4j
public class MultiThreadServer {public static void main(String[] args) throws IOException {Thread.currentThread().setName("boss");ServerSocketChannel ssc = ServerSocketChannel.open();ssc.configureBlocking(false);// 非阻塞模式Selector bossSelect = Selector.open();SelectionKey bossKey = ssc.register(bossSelect, 0, null);bossKey.interestOps(SelectionKey.OP_ACCEPT);ssc.bind(new InetSocketAddress(8008));// 1. 创建固定数量的workerWorker worker = new Worker("worker-0");while (true) {log.debug("1  bossSelect before ...,{}", bossSelect);bossSelect.select(); // select 方法 没有事件则阻塞log.debug("2  bossSelect after ...,{}", bossSelect);Iterator<SelectionKey> iterator = bossSelect.selectedKeys().iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();iterator.remove();if (key.isAcceptable()) {SocketChannel sc = ssc.accept();sc.configureBlocking(false);log.debug("3  connected ...,{}", sc.getRemoteAddress());// 2. 关联selectorlog.debug("4  before ...,{}", sc.getRemoteAddress());worker.register(sc);// 初始 selector 启动 worker 线程log.debug("5  after ...,{}", sc.getRemoteAddress());}}}}static class Worker implements Runnable {private Thread thread;private Selector workerSelect;private String name;private volatile boolean start = false;//还未初始化private ConcurrentLinkedQueue<Runnable> queue = new ConcurrentLinkedQueue<>();public Worker(String name) {this.name = name;}public void register(SocketChannel sc) throws IOException {if (!start) {thread = new Thread(this, name);thread.start();workerSelect = Selector.open();start = true;}// 向队列添加任务,但这个任务没有立刻执行 bossqueue.add(()->{log.debug("6  sc register ...,{}", workerSelect);try {sc.register(workerSelect, SelectionKey.OP_READ, null);// 在select阻塞时会注册失败} catch (ClosedChannelException e) {throw new RuntimeException(e);}});workerSelect.wakeup();// wakeup方法是一次性方法,无论在select阻塞前后执行 都会唤醒一次select让select不被阻塞}@Overridepublic void run() {while (true) {try {log.debug("7  workerSelect before ...,{}", workerSelect);workerSelect.select();Runnable task = queue.poll();if (task != null){log.debug("8  queue run ...,{}", workerSelect);task.run();}log.debug("9  workerSelect after...,{}", workerSelect);Iterator<SelectionKey> iterator = workerSelect.selectedKeys().iterator();while (iterator.hasNext()) {SelectionKey key = iterator.next();iterator.remove();if (key.isReadable()) {ByteBuffer buffer = ByteBuffer.allocate(16);SocketChannel channel = (SocketChannel) key.channel();log.debug("10  read ...,{}", channel.getRemoteAddress());channel.read(buffer);buffer.flip();debugAll(buffer);}}} catch (IOException e) {e.printStackTrace();}}}}
}

还可以去掉队列的操作,只调用select的唤醒
因为wakeup方法是一次性方法,无论在select阻塞前后执行 都会唤醒一次select让select不被阻塞
而客户端连接事件只在boss线程执行一次所以调用了一次register方法,而start方法在唤醒前执行了,此时可能阻塞了,而wakeup方法的特殊性,能保证select完成注册,所以只需一次唤醒一次select完成注册,即可保证客户端被正常注册,消息被正常接受而不会被先运行的workerSelect.select();方法阻塞
在这里插入图片描述

多worker改造

在这里插入图片描述
worker开几个线程比较合适,若要充分发挥服务器的多核优势,可以设置至少物理核的数量

在这里插入图片描述

Worker[] workers = new Worker[Runtime.getRuntime().availableProcessors()];   

NIO vs BIO

stream vs channel

在这里插入图片描述

IO模型

同步阻塞、同步非阻塞、同步多路复用、异步阻塞(不存在)、异步非阻塞
在这里插入图片描述

零拷贝

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

切换了三次,数据复制了4次

直接内存第一次优化

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

DMA是一个独立的硬件。

零拷贝指不在Java JVM内存中进行拷贝动作

AIO

Async异步IO
在这里插入图片描述

由主线程处产生的异步线程叫守护线程,如果主线程结束了,那么守护线程也会结束,哪怕守护线程还在工作中
所以我们使主线程阻塞,等待异步返回结果

@Slf4j
public class AioFileChannel {public static void main(String[] args) throws IOException {try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(Paths.get("data.txt"), StandardOpenOption.READ)) {// 参数1 ByteBuffer// 参数2 读取的起始位置// 参数3 附件// 参数4 回调对象 CompletionHandlerByteBuffer buffer = ByteBuffer.allocate(16);log.debug("read begin...");channel.read(buffer, 0, buffer, new CompletionHandler<Integer, ByteBuffer>() {@Override // read 成功public void completed(Integer result, ByteBuffer attachment) {log.debug("read completed...{}", result);attachment.flip();debugAll(attachment);}@Override // read 失败public void failed(Throwable exc, ByteBuffer attachment) {exc.printStackTrace();}});log.debug("read end...");} catch (IOException e) {e.printStackTrace();}System.in.read();//使主线程阻塞不结束}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/369333.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于docker环境及Harbor部署{很简短一点了,耐心看吧}

用到的环境&#xff1a; docker 、nacos、compose、harbor&#xff08;自行安装 ,以下连接作为参考&#xff09; nacos&#xff1a;史上最全整合nacos单机模式整合哈哈哈哈哈_nacos 源码启动 单机模式-CSDN博客 docker、compose、harbor:史上最全的整合Harbor安装教程&#…

一览 Anoma 上的有趣应用概念

撰文&#xff1a;Tia&#xff0c;Techub News 本文来源香港Web3媒体&#xff1a;Techub News Anoma 的目标是为应用提供通用的意图机器接口&#xff0c;这意味着使用 Anoma&#xff0c;开发人员可以根据意图和分布式意图机编写应用&#xff0c;而不是根据事务和特定状态机进行…

技术成神之路:设计模式(二)建造者模式

1.定义 建造者模式&#xff08;Builder Pattern&#xff09;是一种创建型设计模式&#xff0c;它允许你分步骤创建复杂对象&#xff0c;而不必直接调用构造函数。建造者模式特别适合那些包含多个组成部分并且构造过程复杂的对象。 2. 结构 建造者模式的主要组成部分包括&#…

UE5 02-给物体一个扭矩力

需要注意的是: 1.弹簧臂 可以使用绝对旋转 这样就可以不跟随父物体Player的旋转 2.弹簧臂 进行碰撞测试勾选,当这个弹簧线被遮挡,摄像机会切换到碰撞点位置 进行碰撞测试勾选,当这个弹簧线被遮挡,摄像机不会切换到碰撞点位置

RAG 为什么需要文本分割(Chunking)

Picone上的一个博客&#xff0c;翻译过来学习一下&#xff0c;其中加入了一些个人的理解和调整&#xff0c;有兴趣更深入研究的可以看一下文章的原文。 为什么需要文本分割&#xff08;Chunking&#xff09; 在构建与LLM相关的应用程序时&#xff0c;Chunking是将大量文本分解…

Python酷库之旅-第三方库Pandas(004)

目录 一、用法精讲 5、pandas.DataFrame.to_csv函数 5-1、语法 5-2、参数 5-3、功能 5-4、返回值 5-5、说明 5-6、用法 5-6-1、代码示例 5-6-2、结果输出 6、pandas.read_fwf函数 6-1、语法 6-2、参数 6-3、功能 6-4、返回值 6-5、说明 6-6、用法 6-6-1、代码…

微信小程序渲染层与逻辑层交互原理

1. 网页开发与小程序开发有何不同&#xff1f; 2. 小程序运行环境 3. 页面渲染技术选型 1. 纯客户端技术&#xff1b; 2. 纯Web技术&#xff1b; 3. 用客户端原生技术与Web技术结合的混合技术&#xff08;Hybrid&#xff09;&#xff0c;小程序就是使用的这种技术&#xff1…

C++11 shared_ptr---面试常考

shared_ptr简介 共享对其所指堆内存空间的所有权&#xff0c;当最后⼀个指涉到该对象的shared_ptr不再指向他时&#xff0c;shared_ptr会⾃动析构所指对象如何判断⾃⼰是否指涉到该资源的最后⼀个&#xff1f;《引⽤计数》 shared_ptr构造函数&#xff0c;使引⽤计数析构函数&…

【YOLOv5/v7改进系列】改进池化层为ASPP

一、导言 Atrous Spatial Pyramid Pooling (ASPP)模块是一种用于多尺度特征提取的创新技术&#xff0c;旨在提升深度学习模型在语义图像分割任务中的表现。ASPP模块通过在不同的采样率下应用空洞卷积&#xff0c;可以捕获不同大小的对象以及图像的上下文信息&#xff0c;从而增…

Astro新前端框架首次体验

Astro新前端框架首次体验 1、什么是Astro Astro是一个静态网站生成器的前端框架&#xff0c;它提供了一种新的开发方式和更好的性能体验&#xff0c;帮助开发者更快速地构建现代化的网站和应用程序。 简单来说就是&#xff1a;Astro这个是一个网站生成器&#xff0c;可以直接…

Hyper-V克隆虚拟机教程分享!

方法1. 使用导出导入功能克隆Hyper-V虚拟机 导出和导入是Hyper-V服务器备份和克隆的一种比较有效的方法。使用此功能&#xff0c;您可以创建Hyper-V虚拟机模板&#xff0c;其中包括软件、VM CPU、RAM和其他设备的配置&#xff0c;这有助于在Hyper-V中快速部署多个虚拟机。 在…

前端引用vue/element/echarts资源等引用方法Blob下载HTML

前端引用下载vue/element/echarts资源等引用方法 功能需求 需求是在HTML页面中集成Vue.js、Element Plus&#xff08;Element UI的Vue 3版本&#xff09;、ECharts等前端资源&#xff0c;使用Blob下载HTML。 解决方案概述 直接访问线上CDN地址&#xff1a;简单直接&#xff0c…

计算机网络(2

计算机网络续 一. 网络编程 网络编程, 指网络上的主机, 通过不同的进程, 以编程的方式实现网络通信(或网络数据传输). 即便是同一个主机, 只要不同进程, 基于网络来传输数据, 也属于网络编程. 二. 网络编程套接字(socket) socket: 操作系统提供的网络编程的 API 称作 “soc…

【数据结构与算法】堆排序算法原理与实现:基于堆实现的高效排序算法

&#x1f493; 博客主页&#xff1a;倔强的石头的CSDN主页 &#x1f4dd;Gitee主页&#xff1a;倔强的石头的gitee主页 ⏩ 文章专栏&#xff1a;《数据结构与算法》 期待您的关注 ​ 目录 一、引言 堆排序的简介 堆排序的特点 二、堆的概念 三、堆排序算法的原理 四、堆…

软件测试面试1000问(含答案)

1、自动化代码中,用到了哪些设计模式? 单例设计模式工厂模式PO设计模式数据驱动模式面向接口编程设计模式 2、什么是断言( Assert) ? 断言Assert用于在代码中验证实际结果是不是符合预期结果&#xff0c;如果测试用例执行失败会抛出异常并提供断言日志 3、什么是web自动化…

数据结构预科

在堆区申请两个长度为32的空间&#xff0c;实现两个字符串的比较【非库函数实现】 要求&#xff1a; 1> 定义函数&#xff0c;在对区申请空间&#xff0c;两个申请&#xff0c;主函数需要调用2次 2> 定义函数&#xff0c;实现字符串的输入&#xff0c;void input(char …

Jenkins容器的部署

本文主要是记录如何在Centos7上安装docker,以及在docker里面配置tomcat、mysql、jenkins等环境。 一、安装docker 1.1 准备工作 centos7、VMware17Pro 1.2 通过yum在线安装dokcer yum -y install docker1.3 启动docker服务 systemctl start docker.service1.4 查看docke…

Java传引用问题

本文将介绍 Java 中的引用传递&#xff0c;包括其定义、实现方式、通过引用修改原来指向的内容和通过引用修改当前引用的指向的区别 目录 1、引用传递的概念 2、引用传递的实现方式 3、传引用会发生的两种情况&#xff1a; 通过引用修改当前引用的指向 通过引用修改原来指…

《数据仓库与数据挖掘》 总复习

试卷组成 第一章图 第二章图 第三章图 第四章图 第五章图 第六章图 第九章图 第一章 DW与DM概述 &#xff08;特点、特性&#xff09; DB到DW 主要特征 &#xff08;1&#xff09;数据太多&#xff0c;信息贫乏&#xff08;Data Rich&#xff0c; Information Poor)。 &a…