epoll的LT模式(水平触发)和ET模式(边沿触发)
前言
epoll的触发模式是个引发讨论非常多的话题,网络上这方面总结的文章也很多,首先从名字上就不是很统一,LT模式常被称为水平触发、电平触发、条件触发,而ET模式常被称为边缘触发、边沿触发等,这些都是从英文翻译过来的,只不过翻译的时候有些差异,LT全称 level-triggered,ET全称 edge-triggered。
虽然这个知识点热度很高,但很多人对于它的理解总是差那么一点,特别是在面试的时候,很多面试者总是处于一种回忆和背诵的状态,其实这两种模式真的不需要去死记硬背,下面说说我个人对这两种模式的理解和记忆方法。
名称的记忆
每次提到ET(边沿触发)首先映入我脑海的是大学里《数字逻辑电路》这门课程,里面会提到低电平、高电平,当电平从低到高时会有一个上升沿,而电平从高到低时会有一个下降沿,这个“沿”就是边沿触发时提到的“边沿”,跟马路边的马路牙子是同一种概念,也就是指状态变化的时候。提起上升沿和下降沿我还是印象很深的,当时我可是占用了好几节课的时间用Verilog语言写了一个显示“HELLO WORLD”的仿真波形,依靠的就是电平变化中的“沿”。
状态变化
LT模式和ET模式可以类比电平变化来学习,但是在实际应用中概念却不是完全一样的,在epoll的应用中涉及到关于IO的读写,而读写的状态变化有哪些呢?可读、不可读、可写、不可写,其实就是这四种状态而已,以socket为例。
可读:socket上有数据
不可读:socket上没有数据了
可写:socket上有空间可写
不可写:socket上无空间可写
对于水平触发模式,一个事件只要有,就会一直触发。
对于边缘触发模式,只有一个事件从无到有才会触发。
LT模式
对于读事件 EPOLLIN,只要socket上有未读完的数据,EPOLLIN 就会一直触发;对于写事件 EPOLLOUT,只要socket可写(一说指的是 TCP 窗口一直不饱和,我觉得是TCP缓冲区未满时,这一点还需验证),EPOLLOUT 就会一直触发。
在这种模式下,大家会认为读数据会简单一些,因为即使数据没有读完,那么下次调用epoll_wait()时,它还会通知你在上没读完的文件描述符上继续读,也就是人们常说的这种模式不用担心会丢失数据。
而写数据时,因为使用 LT 模式会一直触发 EPOLLOUT 事件,那么如果代码实现依赖于可写事件触发去发送数据,一定要在数据发送完之后移除检测可写事件,避免没有数据发送时无意义的触发。
ET模式
对于读事件 EPOLLIN,只有socket上的数据从无到有,EPOLLIN 才会触发;对于写事件 EPOLLOUT,只有在socket写缓冲区从不可写变为可写,EPOLLOUT 才会触发(刚刚添加事件完成调用epoll_wait时或者缓冲区从满到不满)
这种模式听起来清爽了很多,只有状态变化时才会通知,通知的次数少了自然也会引发一些问题,比如触发读事件后必须把数据收取干净,因为你不一定有下一次机会再收取数据了,即使不采用一次读取干净的方式,也要把这个激活状态记下来,后续接着处理,否则如果数据残留到下一次消息来到时就会造成延迟现象。
这种模式下写事件触发后,后续就不会再触发了,如果还需要下一次的写事件触发来驱动发送数据,就需要再次注册一次检测可写事件。
数据的读取和发送
关于数据的读比较好理解,无论是LT模式还是ET模式,监听到读事件从socket开始读数据就好了,只不过读的逻辑有些差异,LT模式下,读事件触发后,可以按需收取想要的字节数,不用把本次接收到的数据收取干净,ET模式下,读事件触发后通常需要数据一次性收取干净。
而数据的写不太容易理解,因为数据的读是对端发来数据导致的,而数据的写其实是自己的逻辑层触发的,所以在通过网络发数据时通常都不会去注册监可写事件,一般都是调用 send 或者 write 函数直接发送,如果发送过程中, 函数返回 -1,并且错误码是 EWOULDBLOCK 表明发送失败,此时才会注册监听可写事件,并将剩余的服务存入自定义的发送缓冲区中,等可写事件触发后再接着将发送缓冲区中剩余的数据发送出去。
相关视频推荐
全网最详细epoll讲解,6种epoll的设计,让你吊打面试官
epoll实战揭秘-支撑亿级IO的底层基石
epoll的具体实现与epoll线程安全,互斥锁、自旋锁、原子操作、CAS
免费学习地址:c/c++ linux服务器开发/后台架构师
需要C/C++ Linux服务器架构师学习资料加qun812855908获取(资料包括C/C++,Linux,golang技术,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK,ffmpeg等),免费分享

代码实践
基础代码
以下为一个epoll触发模式测试的基础代码,也不算太长,直接拿来就可以测试:
#include <sys/socket.h> //for socket
#include <arpa/inet.h> //for htonl htons
#include <sys/epoll.h> //for epoll_ctl
#include <unistd.h> //for close
#include <fcntl.h> //for fcntl
#include <errno.h> //for errno
#include <iostream> //for coutclass fd_object
{
public:fd_object(int fd) { listen_fd = fd; }~fd_object() { close(listen_fd); }
private:int listen_fd;
};/*
./epoll for lt mode
and
./epoll 1 for et mode
*/
int main(int argc, char* argv[])
{//create a socket fdint listen_fd = socket(AF_INET, SOCK_STREAM, 0);if (listen_fd == -1){std::cout << "create listen socket fd error." << std::endl;return -1;}fd_object obj(listen_fd);//set socket to non-blockint socket_flag = fcntl(listen_fd, F_GETFL, 0);socket_flag |= O_NONBLOCK;if (fcntl(listen_fd, F_SETFL, socket_flag) == -1){std::cout << "set listen fd to nonblock error." << std::endl;return -1;}//init server bind infoint port = 51741;struct sockaddr_in bind_addr;bind_addr.sin_family = AF_INET;bind_addr.sin_addr.s_addr = htonl(INADDR_ANY);bind_addr.sin_port = htons(port);if (bind(listen_fd, (struct sockaddr *)&bind_addr, sizeof(bind_addr)) == -1){std::cout << "bind listen socket fd error." << std::endl;return -1;}//start listenif (listen(listen_fd, SOMAXCONN) == -1){std::cout << "listen error." << std::endl;return -1;}elsestd::cout << "start server at port [" << port << "] with [" << (argc <= 1 ? "LT" : "ET") << "] mode." << std::endl;//create a epoll fdint epoll_fd = epoll_create(88);if (epoll_fd == -1){std::cout << "create a epoll fd error." << std::endl;return -1;}epoll_event listen_fd_event;listen_fd_event.data.fd = listen_fd;listen_fd_event.events = EPOLLIN;if (argc > 1) listen_fd_event.events |= EPOLLET;//add epoll event for listen fdif (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &listen_fd_event) == -1){std::cout << "epoll ctl error." << std::endl;return -1;}while (true){epoll_event epoll_events[1024];int n = epoll_wait(epoll_fd, epoll_events, 1024, 1000);if (n < 0)break;else if (n == 0) //timeoutcontinue;for (int i = 0; i < n; ++i){if (epoll_events[i].events & EPOLLIN)//trigger read event{if (epoll_events[i].data.fd == listen_fd){//accept a new connectionstruct sockaddr_in client_addr;socklen_t client_addr_len = sizeof(client_addr);int client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_addr_len);if (client_fd == -1)continue;socket_flag = fcntl(client_fd, F_GETFL, 0);socket_flag |= O_NONBLOCK;if (fcntl(client_fd, F_SETFL, socket_flag) == -1){close(client_fd);std::cout << "set client fd to non-block error." << std::endl;continue;}epoll_event client_fd_event;client_fd_event.data.fd = client_fd;client_fd_event.events = EPOLLIN | EPOLLOUT;if (argc > 1) client_fd_event.events |= EPOLLET;if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &client_fd_event) == -1){std::cout << "add client fd to epoll fd error." << std::endl;close(client_fd);continue;}std::cout << "accept a new client fd [" << client_fd << "]." << std::endl;}else{std::cout << "EPOLLIN event triggered for client fd [" << epoll_events[i].data.fd << "]." << std::endl;char recvbuf[1024] = { 0 };int m = recv(epoll_events[i].data.fd, recvbuf, 1, 0); // only read 1 bytes when read event triggeredif (m == 0 || (m < 0 && errno != EWOULDBLOCK && errno != EINTR)){if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, epoll_events[i].data.fd, NULL) != -1)std::cout << "the client fd [" << epoll_events[i].data.fd << "] disconnected." << std::endl;close(epoll_events[i].data.fd);}std::cout << "recv data from client fd [" << epoll_events[i].data.fd << "] and data is [" << recvbuf << "]." << std::endl;}}else if (epoll_events[i].events & EPOLLOUT){if (epoll_events[i].data.fd == listen_fd) //trigger write eventcontinue;std::cout << "EPOLLOUT event triggered for client fd [" << epoll_events[i].data.fd << "]." << std::endl;}}}return 0;
}
简单说下这段代码的测试方法,可以使用 g++ testepoll.cpp -o epoll 进行编译,编译后通过 ./epoll 运行为LT模式,通过 ./epoll et模式运行为ET模式,我们用编译好的epoll程序作为服务器,使用nc命令来模拟一个客户端。
测试分类
1.编译后直接./epoll,然后在另一个命令行窗口用 nc -v 127.0.0.1 51741 命令模拟一次连接,此时 ./epoll 会产生大量的 EPOLLOUT event triggered for client fd ...,那是因为在LT模式下,EPOLLOUT会被一直触发。
albert@home-pc:/mnt/d/data/cpp/testepoll$ ./epoll
start server at port [51741] with [LT] mode.
accept a new client fd [5].
EPOLLOUT event triggered for client fd [5].
EPOLLOUT event triggered for client fd [5].
EPOLLOUT event triggered for client fd [5].
EPOLLOUT event triggered for client fd [5].
EPOLLOUT event triggered for client fd [5].
EPOLLOUT event triggered for client fd [5].
EPOLLOUT event triggered for client fd [5].
EPOLLOUT event triggered for client fd [5].
...
2.注释包含 EPOLLOUT event triggered for client fd 输出内容的第152行代码,编译后 ./epoll运行,然后在另一个命令行窗口用 nc -v 127.0.0.1 51741 模拟一次连接后,输入abcd回车,可以看到服务器./epoll输出内容,EPOLLIN被触发多次,每次读取一个字节。
albert@home-pc:/mnt/d/data/cpp/testepoll$ ./epoll
start server at port [51741] with [LT] mode.
accept a new client fd [5].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [a].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [b].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [c].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [d].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [
].
3.还原刚才注释的那行代码,编译后执行 ./epoll et 启动服务器,然后在另一个命令行窗口用 nc -v 127.0.0.1 51741 模拟一次连接后,然后在另一个命令行窗口用 nc -v 127.0.0.1 51741 模拟一次连接,服务器窗口显示触发了EPOLLOUT事件
albert@home-pc:/mnt/d/data/cpp/testepoll$ ./epoll et
start server at port [51741] with [ET] mode.
accept a new client fd [5].
EPOLLOUT event triggered for client fd [5].
在此基础上,从刚刚运行nc命令的窗口中输入回车、输入回车、输出回车,那么epoll服务器窗口看到的是触发了三次EPOLLIN事件,每次收到一个回车:
albert@home-pc:/mnt/d/data/cpp/testepoll$ ./epoll et
start server at port [51741] with [ET] mode.
accept a new client fd [5].
EPOLLOUT event triggered for client fd [5].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [
].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [
].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [
].
但是如果在nc模拟的客户端里输出abcd回车,那么在epoll服务器窗口触发一次EPOLLIN事件接收到一个a之后便再也不会触发EPOLLIN了,即使你在nc客户端在此输入也没有用,那是因为在接受的缓冲区中一直还有数据,新数据来时没有出现缓冲区从空到有数据的情况,所以在ET模式下也注意这种情况。
albert@home-pc:/mnt/d/data/cpp/testepoll$ ./epoll et
start server at port [51741] with [ET] mode.
accept a new client fd [5].
EPOLLOUT event triggered for client fd [5].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [
].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [
].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [
].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [a].
怎么解决ET触发了一次就不再触发了
改代码呗,ET模式在连接后触发一次EPOLLOUT,接收到数据时触发一次EPOLLIN,如果数据没收完,以后这两个事件就再也不会被触发了,要想改变这种情况可以再次注册一下这两个事件,时机可以选择接收到数据的时候,所以可以修改这部分代码:
else
{std::cout << "EPOLLIN event triggered for client fd [" << epoll_events[i].data.fd << "]." << std::endl;char recvbuf[1024] = { 0 };int m = recv(epoll_events[i].data.fd, recvbuf, 1, 0); // only read 1 bytes when read event triggeredif (m == 0 || (m < 0 && errno != EWOULDBLOCK && errno != EINTR)){if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, epoll_events[i].data.fd, NULL) != -1)std::cout << "the client fd [" << epoll_events[i].data.fd << "] disconnected." << std::endl;close(epoll_events[i].data.fd);}std::cout << "recv data from client fd [" << epoll_events[i].data.fd << "] and data is [" << recvbuf << "]." << std::endl;
}
添加再次注册的逻辑:
else
{std::cout << "EPOLLIN event triggered for client fd [" << epoll_events[i].data.fd << "]." << std::endl;char recvbuf[1024] = { 0 };int m = recv(epoll_events[i].data.fd, recvbuf, 1, 0); // only read 1 bytes when read event triggeredif (m == 0 || (m < 0 && errno != EWOULDBLOCK && errno != EINTR)){if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, epoll_events[i].data.fd, NULL) != -1)std::cout << "the client fd [" << epoll_events[i].data.fd << "] disconnected." << std::endl;close(epoll_events[i].data.fd);}epoll_event client_fd_event;client_fd_event.data.fd = epoll_events[i].data.fd;client_fd_event.events = EPOLLIN | EPOLLOUT;if (argc > 1) client_fd_event.events |= EPOLLET;epoll_ctl(epoll_fd, EPOLL_CTL_MOD, epoll_events[i].data.fd, &client_fd_event);std::cout << "recv data from client fd [" << epoll_events[i].data.fd << "] and data is [" << recvbuf << "]." << std::endl;
}
这次以./epoll et方式启动服务器,使用nc -v 127.0.0.1 51741模拟客户端,输入abc回车发现,epoll服务器输出显示触发的事件变了:
albert@home-pc:/mnt/d/data/cpp/testepoll$ ./epoll et
start server at port [51741] with [ET] mode.
accept a new client fd [5].
EPOLLOUT event triggered for client fd [5].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [a].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [b].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [c].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [
].
EPOLLOUT event triggered for client fd [5].
总结
- LT模式会一直触发EPOLLOUT,当缓冲区有数据时会一直触发EPOLLIN
- ET模式会在连接建立后触发一次EPOLLOUT,当收到数据时会触发一次EPOLLIN
- LT模式触发EPOLLIN时可以按需读取数据,残留了数据还会再次通知读取
- ET模式触发EPOLLIN时必须把数据读取完,否则即使来了新的数据也不会再次通知了
- LT模式的EPOLLOUT会一直触发,所以发送完数据记得删除,否则会产生大量不必要的通知
- ET模式的EPOLLOUT事件若数据未发送完需再次注册,否则不会再有发送的机会
- 通常发送网络数据时不会依赖EPOLLOUT事件,只有在缓冲区满发送失败时会注册这个事件,期待被通知后再次发送
相关文章:
epoll的LT模式(水平触发)和ET模式(边沿触发)
前言 epoll的触发模式是个引发讨论非常多的话题,网络上这方面总结的文章也很多,首先从名字上就不是很统一,LT模式常被称为水平触发、电平触发、条件触发,而ET模式常被称为边缘触发、边沿触发等,这些都是从英文翻译过来…...
Java基础面试20题
Java的八大基本数据类型 答:可以分4种类型:布尔类型(boolean),字符类型(char),浮点类型(double,float),整型(byte,short,int, long)。 String,StringBuffer与StringBuilder的区别? …...
Java面向对象封装
目录 封装的概念 封装具体实现步骤 示例代码如下 封装具体语法 封装的概念 Java封装是一种面向对象的编程方法,它的主要思想是将类的内部细节(包括数据和方法)隐藏起来,对外只提供公共的访问接口,从而保证了程序的安全性和稳定性。 封装具体实现步骤 1、声明类的成…...
队列实现图书信息管理(C语言)
文章目录Queue.hmain.cQueue.c用队列实现一个图书信息管理,这里放一下有哪些文件。(ps:我之前写的是学生信息管理,但是有人说我们的作业是写图书,就该了下内容,没有改文件名)队列是用链表实现的…...
Java开发 - 读写分离初体验
前言 上一篇中,我们介绍了主从复制,相信学过的小伙伴已经能够很好的掌握主从复制的技术,实际上也并没有那么难,虽然没有讲一主多从,多主多从的配置,但是从一主一从的配置中也很容易联想到该怎么配置&#…...
图文详解CAN Log文件 - ASC文件格式
目录 1 CAN Log文件 -- ASC文件格式 1.1 Header 1.2 版本编号 1.3 经典CAN网络中的描述 1.3.1 经典CAN Standard标准帧的描述 1.3.2 经典CAN Extended扩展帧的描述 1.3.3 CAN Remote远程帧的描述 1.3.4 CAN Error错误帧的描述 1.4 CANFD网络中的描述 1.4.1 经典CAN S…...
网络编程套接字(一)
学习任务: 我们先来认识端口号,区分好主机IP和端口号的区别,以及涉及到进程PID和端口号的区别。 然后简单认识一下TCP协议和UDP协议,这两个协议都是传输层的。接着了解什么是网络字节序,它有什么作用。然后是网络编程的…...
Mysql数据库存储过程
1、参数分类 存储过程的参数类型可以是IN、OUT和INOUT。根据这点分类如下: 1、没有参数(无参数无返回) 2、仅仅带 IN 类型(有参数无返回) 3、仅仅带 OUT 类型(无参数有返回) 4、既带 IN 又带 O…...
当我开始学习人工智能:人工智能的学派及研究目标
上课真是不认真啊,现在都写不来了作业了,真的会谢 一、人工智能的学派及其争论 1.1 对人工智能方法的争论 三个学派 符号主义 认为人的认知基元是符号,认知过程即符号操作过程。 认为人是一个物理符号系统,计算机也是一个物理符…...
Html5钢琴块游戏制作与分享(音游可玩)
当年一款手机节奏音游,相信不少人都玩过或见过。最近也是将其做了出来分享给大家。 游戏的基本玩法:点击下落的黑色方块,弹奏音乐。(下落的速度会越来越快) 可以进行试玩,手机玩起来效果会更好些。 点击…...
MySQL数据库——数据库设计概念和数据库设计步骤
数据库设计就是根据业务系统的具体需求,结合我们所选用的数据库,建立好表结构及表与表之间的管理关系,为这个业务系统构造出最优秀的数据存储模型的过程。使之能有效的对应用的数据进行存储,并高效的对已经存储的数据进行访问。 …...
【云原生】Kubernetes(k8s)之Pod概念和使用
k8s之Pod概念和使用一、Pod简介1.1、Pod的阶段(状态)1.2、容器状态二、Pod的定义2.1、restartPolicy2.2、imagePullPolicy2.3、command2.4、args2.5、resources三、Pod的使用3.1、创建并访问Pod3.2、多个应用容器3.3、Init容器3.3.1、Init容器与普通容器…...
数组(九)-- LC[316][321][402] 去除重复字母
1 移掉 K 位数字 1.1 题目描述 题目链接:https://leetcode.cn/problems/remove-k-digits/ 1.2 思路分析 这道题让我们从一个字符串数字中删除 k 个数字,使得剩下的数最小。也就说,我们要保持原来的数字的相对位置不变。 以题目中的 num1432…...
ubuntu下Thrift安装
thrift是一种常用rpc框架,工作中经常会用到,本文记录一下其安装过程。 目录 1.下载软件包 1.1thrift下载 1.2libevent下载 1.3boost下载 2.安装(注意步骤) 2.1安装libevent 2.2安装boost 2.3安装与Python2.7版本对应的py…...
读懂AUTOSAR :DiagnosticLogAndTrace DLT(四)-- API解析
一、周期调用的函数:Dlt_TxFunction 根据参数DltGeneralTrafficShapingSupport,决定如何去发送DLT消息。如果为TRUE,那需要参考参数DltLogChannelTrafficShapingBandwidth为每个Log通道设置发送带宽;如果为FALSE,那么…...
【LeetCode】剑指 Offer 56. 数组中数字出现的次数 p275 -- Java Version
1. 题目介绍(56. 数组中数字出现的次数) 面试题56.:数组中数字出现的次数, 一共分为两小题: 题目一:数组中只出现一次的两个数字题目二:数组中唯一只出现一次的数字 2. 题目1:数组中…...
Zookeeper集群 + Fafka集群
目录 第一章Zookeeper 概述 1.1.Zookeeper 定义 1.2.Zookeeper 工作机制 1.3.Zookeeper 特点 1.4.Zookeeper 数据结构 1.5.Zookeeper 应用场景 1.6.Zookeeper 原理之选举机制 1.7.部署 Zookeeper 集群 总结 第二章消息队列概述 2.1消息队列需求原因 2.2消息队列的优…...
全国青少年电子信息智能创新大赛(复赛)python·模拟四卷
目录 一、编程题 答案解析如下: 下载文档打印做题: 全国青少年电子信息智能创新大赛(复赛)python模拟四卷 一、编程题 第一题:描述 班上有学生若干名,给出每名学生的年龄《整数),求班上所有学生的平均年龄,保留到小数点后两企 输入 第一行有一个整数n (1<= n...
Redis - 介绍与使用场景
简介 Redis 的全称是 Remote Dictionary Server,是一个使用 C 语言编写的、开源的(BSD 许可)高性能非关系型(NoSQL)的键值对数据库。 Redis 的数据是存储在内存中的,所以读写速度非常快,被广泛…...
Spark SQL实战(07)-Data Sources
1 概述 Spark SQL通过DataFrame接口支持对多种数据源进行操作。 DataFrame可使用关系型变换进行操作,也可用于创建临时视图。将DataFrame注册为临时视图可以让你对其数据运行SQL查询。 本节介绍使用Spark数据源加载和保存数据的一般方法,并进一步介绍…...
谷歌浏览器插件
项目中有时候会用到插件 sync-cookie-extension1.0.0:开发环境同步测试 cookie 至 localhost,便于本地请求服务携带 cookie 参考地址:https://juejin.cn/post/7139354571712757767 里面有源码下载下来,加在到扩展即可使用FeHelp…...
TRS收益互换:跨境资本流动的金融创新工具与系统化解决方案
一、TRS收益互换的本质与业务逻辑 (一)概念解析 TRS(Total Return Swap)收益互换是一种金融衍生工具,指交易双方约定在未来一定期限内,基于特定资产或指数的表现进行现金流交换的协议。其核心特征包括&am…...
MySQL中【正则表达式】用法
MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现(两者等价),用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例: 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...
ArcGIS Pro制作水平横向图例+多级标注
今天介绍下载ArcGIS Pro中如何设置水平横向图例。 之前我们介绍了ArcGIS的横向图例制作:ArcGIS横向、多列图例、顺序重排、符号居中、批量更改图例符号等等(ArcGIS出图图例8大技巧),那这次我们看看ArcGIS Pro如何更加快捷的操作。…...
Vite中定义@软链接
在webpack中可以直接通过符号表示src路径,但是vite中默认不可以。 如何实现: vite中提供了resolve.alias:通过别名在指向一个具体的路径 在vite.config.js中 import { join } from pathexport default defineConfig({plugins: [vue()],//…...
在 Spring Boot 项目里,MYSQL中json类型字段使用
前言: 因为程序特殊需求导致,需要mysql数据库存储json类型数据,因此记录一下使用流程 1.java实体中新增字段 private List<User> users 2.增加mybatis-plus注解 TableField(typeHandler FastjsonTypeHandler.class) private Lis…...
Visual Studio Code 扩展
Visual Studio Code 扩展 change-case 大小写转换EmmyLua for VSCode 调试插件Bookmarks 书签 change-case 大小写转换 https://marketplace.visualstudio.com/items?itemNamewmaurer.change-case 选中单词后,命令 changeCase.commands 可预览转换效果 EmmyLua…...
xmind转换为markdown
文章目录 解锁思维导图新姿势:将XMind转为结构化Markdown 一、认识Xmind结构二、核心转换流程详解1.解压XMind文件(ZIP处理)2.解析JSON数据结构3:递归转换树形结构4:Markdown层级生成逻辑 三、完整代码 解锁思维导图新…...
uni-app学习笔记三十五--扩展组件的安装和使用
由于内置组件不能满足日常开发需要,uniapp官方也提供了众多的扩展组件供我们使用。由于不是内置组件,需要安装才能使用。 一、安装扩展插件 安装方法: 1.访问uniapp官方文档组件部分:组件使用的入门教程 | uni-app官网 点击左侧…...
向量几何的二元性:叉乘模长与内积投影的深层联系
在数学与物理的空间世界中,向量运算构成了理解几何结构的基石。叉乘(外积)与点积(内积)作为向量代数的两大支柱,表面上呈现出截然不同的几何意义与代数形式,却在深层次上揭示了向量间相互作用的…...
