基于C语言实现的KV存储引擎

  • 项目简介
  • 整体架构
  • 网络模块的实现
    • recator
    • proactor
    • Ntyco

项目简介

本文主要是基于 C 语言来实现一个简单的 KV 存储架构,目的就是将网络模块跟实际开发结合起来。

首先我们知道对于数据的存储可以分为两种方式,一种是在内存中进行存储,像 Redis 这种,是一种内存型数据库,主要也是以 KV 的形式进行存储,提升了对应的数据的访问效率;另一种就是在磁盘中进行存储,像 MySQL 这种,是一种关系型数据库,更多的是以一种表的形式去组织的数据库,也是一种主流的数据库,但是访问的速度就稍微慢一点儿。

那么当前已经有众多的数据库的存在了,我们又为什么需要去实现一个自己的 KV 存储引擎呢?

对于 Redis,MySQL 这些数据库来说,他们可以适用于多种数据类型,我们可以理解为是一个非常大类别的实现,如果我们当前只是需要存储某些类别的数据,比如我们就单纯的进行一些短链接映射存储,用户的一些信息存储,在某些时候需要快速进行读取,我们就没必要使用 Redis 和 MySQL 这种的数据库存储的方式,也就避免了使用它们需要去考虑的一些问题,同样,我们只是单纯的去进行某些数据的存储,我们就可以自己进行优化,将对应的性能提升到最优。

我们自己实现的 KV 存储其实跟 Redis 中的 KV 存储的思想很相像,都是以键值对的方式去进行存储,比如说我们需要访问某一张图片,知道链接,输入对应的链接,就可以访问这张图片,其实这就是一种 KV 存储的方式。

整体架构

基于上面的了解,我们就可以来梳理一下我们的 KV 存储的一个整体架构,其实整个流程也可以去理解为一个请求响应的流程,我么需要进行存储,首先就要发送需要存储的数据,然后服务端进行存储,返回给我们对应的信息(成功或者失败),我们后续访问就可以直接进行看到对应的信息,我们就可以简单的理解为下面这种架构:

在这里插入图片描述

网络模块的实现

在前面的文章当中我们介绍了几种网络高并发的实现方式,reactorio_uringNtyco(协程),在我们的实现当中这几种实现方式都会被使用到。

首先我们需要明白的一点就是,对于网络模块与 KV 引擎模块我们是要进行分开的,网络模块只进行网络模块的工作就可以了,而 KV 引擎模块在进行协议的解析工作。

recator

在前面的章节当中,我们已经实现过 reactor 了,reacor 本质上就是将对应的 IO 管理转化为对事件的管理,其实它的思想就是使用了 IO 多路复用模型,对读写事件进行监听,通过回调函数的调用,异步的去处理读写事件,不去过分的占用核心线程的工作。

首先就是对应的封装工作,对于一个事件来说,肯定有对应的 fd,回调函数,对应的接收和发送缓冲区,我们将其封装在一个 struct 中,方便后续进行调用。

#ifndef __SERVER_H__
#define __SERVER_H__#define BUFFER_LENGTH		1024#define ENABLE_KVS 1typedef int (*RCALLBACK)(int fd);struct conn {int fd;char rbuffer[BUFFER_LENGTH];int rlength;char wbuffer[BUFFER_LENGTH];int wlength;RCALLBACK send_callback;union {RCALLBACK recv_callback;RCALLBACK accept_callback;} r_action;int status;
};#if ENABLE_KVS
int kvs_request(struct conn *c);
int kvs_response(struct conn *c);
#endif#endif

我们在这一块儿也提供了两个接口kvs_requestkvs_response,从命名就可以可以看出,他其实就是接受服务端发送过来的请以及响应,那么我们接收到对应的请求以后,就需要进行协议的制定,如何对数据进行解析,很明显kvs_request接口中就需要与我们的 KV 引擎模块相连接,接收到请求以后就需要调用到 KV 引擎模块进行协议的解析工作。

我们来看具体的实现代码:

reactor.c

#include <errno.h>
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <poll.h>
#include <sys/epoll.h>
#include <errno.h>
#include <sys/time.h>
#include "server.h"#define CONNECTION_SIZE			1024#define MAX_PORTS			20#define TIME_SUB_MS(tv1, tv2)  ((tv1.tv_sec - tv2.tv_sec) * 1000 + (tv1.tv_usec - tv2.tv_usec) / 1000)typedef int (*msg_handler)(char* msg, int length, char* response);
static msg_handler kvs_handler;// 请求处理
int kvs_request(struct conn *c) {// printf("recv: %d, %s\n", c->rlength, c->rbuffer);c->wlength = kvs_handler(c->rbuffer, c->rlength, c->wbuffer);return c->wlength;
}// 响应处理
int kvs_response(struct conn *c) {}int accept_cb(int fd);
int recv_cb(int fd);
int send_cb(int fd);int epfd = 0;
struct timeval begin;struct conn conn_list[CONNECTION_SIZE] = {0};
// fdint set_event(int fd, int event, int flag) {if (flag) {  // non-zero addstruct epoll_event ev;ev.events = event;ev.data.fd = fd;epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);} else {  // zero modstruct epoll_event ev;ev.events = event;ev.data.fd = fd;epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);}
}int event_register(int fd, int event) {if (fd < 0) return -1;conn_list[fd].fd = fd;conn_list[fd].r_action.recv_callback = recv_cb;conn_list[fd].send_callback = send_cb;memset(conn_list[fd].rbuffer, 0, BUFFER_LENGTH);conn_list[fd].rlength = 0;memset(conn_list[fd].wbuffer, 0, BUFFER_LENGTH);conn_list[fd].wlength = 0;set_event(fd, event, 1);
}// listenfd(sockfd) --> EPOLLIN --> accept_cb
int accept_cb(int fd) {struct sockaddr_in  clientaddr;socklen_t len = sizeof(clientaddr);int clientfd = accept(fd, (struct sockaddr*)&clientaddr, &len);//printf("accept finshed: %d\n", clientfd);if (clientfd < 0) {printf("accept errno: %d --> %s\n", errno, strerror(errno));return -1;}event_register(clientfd, EPOLLIN);  // | EPOLLETif ((clientfd % 1000) == 0) {struct timeval current;gettimeofday(&current, NULL);int time_used = TIME_SUB_MS(current, begin);memcpy(&begin, &current, sizeof(struct timeval));printf("accept finshed: %d, time_used: %d\n", clientfd, time_used);}return 0;
}int recv_cb(int fd) {memset(conn_list[fd].rbuffer, 0, BUFFER_LENGTH );int count = recv(fd, conn_list[fd].rbuffer, BUFFER_LENGTH, 0);if (count == 0) { // disconnectprintf("client disconnect: %d\n", fd);close(fd);epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL); // unfinishedreturn 0;} else if (count < 0) { // printf("count: %d, errno: %d, %s\n", count, errno, strerror(errno));close(fd);epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);return 0;}conn_list[fd].rlength = count;// printf("RECV: %s\n", conn_list[fd].rbuffer);#if ENABLE_KVSkvs_request(&conn_list[fd]);#endif set_event(fd, EPOLLOUT, 0);return count;
}int send_cb(int fd) {
#if ENABLE_KVSkvs_response(&conn_list[fd]);#endifint count = 0;if (conn_list[fd].wlength != 0) {count = send(fd, conn_list[fd].wbuffer, conn_list[fd].wlength, 0);}set_event(fd, EPOLLIN, 0);//set_event(fd, EPOLLOUT, 0);return count;
}int init_reactor_server(unsigned short port) {int sockfd = socket(AF_INET, SOCK_STREAM, 0);struct sockaddr_in servaddr;servaddr.sin_family = AF_INET;servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0servaddr.sin_port = htons(port); // 0-1023, if (-1 == bind(sockfd, (struct sockaddr*)&servaddr, sizeof(struct sockaddr))) {printf("bind failed: %s\n", strerror(errno));}listen(sockfd, 10);//printf("listen finshed: %d\n", sockfd); // 3 return sockfd;
}int recator_entry(unsigned short port, msg_handler handler) {kvs_handler = handler;epfd = epoll_create(1);int i = 0;for (i = 0;i < MAX_PORTS; i++) {int sockfd = init_reactor_server(port + i);conn_list[sockfd].fd = sockfd;conn_list[sockfd].r_action.recv_callback = accept_cb;set_event(sockfd, EPOLLIN, 1);}gettimeofday(&begin, NULL);while (1) { // mainloopstruct epoll_event events[1024] = {0};int nready = epoll_wait(epfd, events, 1024, -1);int i = 0;for (i = 0;i < nready;i ++) {int connfd = events[i].data.fd;if (events[i].events & EPOLLIN) {conn_list[connfd].r_action.recv_callback(connfd);} if (events[i].events & EPOLLOUT) {conn_list[connfd].send_callback(connfd);}}}
}

kvstore.h

#ifndef __KV_STORE__
#define __KV_STORE__#define NETWORK_RECATOR 0
#define NETWORK_PROACTOR 1
#define NETWORK_NTYCO 2#define NETWORK_TYPE NETWORK_RECATORtypedef int (*msg_handler)(char* msg, int length, char* response);const char* command[] = {"SET", "GET", "DEL", "MOD", "EXIST"
};const char* response[] = {};#endif

kvstore.c

#include <stdio.h>
#include <stdlib.h>
#include "kvstore.h"extern int recator_entry(unsigned short port, msg_handler handler);
extern int ntyco_start(unsigned short port, msg_handler handler);
extern int proactor_entry(unsigned short port, msg_handler handler);
/*
* @brief 协议解析
* @param msg 消息体
* @param length 消息体长度
* @param response 响应体
* @return 0 成功 -1 失败
*/
// 协议解析
int kvs_protocal(char* msg, int length, char* response)
{printf("recv: %d, %s\n", length, msg);}int main(int argc, char* argv[])
{if (argc != 2) {printf("Usage: %s <port>\n", argv[0]);return -1;}if (NETWORK_TYPE == NETWORK_RECATOR) {recator_entry(atoi(argv[1]), kvs_protocal);} else if (NETWORK_TYPE == NETWORK_PROACTOR) {proactor_entry(atoi(argv[1]), kvs_protocal);} else if (NETWORK_TYPE == NETWORK_NTYCO) {ntyco_start(atoi(argv[1]), kvs_protocal);}return 0;
}
  • kvstore.c文件当中主要就是对于 KV 引擎的一个实现,当然 main 函数也是用在这当中的,kvs_protocal是我们的具体协议解析的函数,我们要实现其与网络模块的互联,就可以采用函数指针的方式,无论是reactorproactor或者是协程,其实都采用这种方式,C 语言并不想 C++ 那样有包装器,所以在这儿我们就使用函数指针的方法来操作。
  • kvstore.h其实就是定义与实现的分离,我们当前是仿照 Redis 协议进行制定的;
  • recator.c主要就是kvs_request模块,实现了与 KV 引擎的互联,但是网络模块又是与 KV 引擎模块是解耦的。
    在这里插入图片描述

proactor

procator.c

#include <stdio.h>
#include <liburing.h>
#include <netinet/in.h>
#include <string.h>
#include <unistd.h>#define EVENT_ACCEPT   	0
#define EVENT_READ		1
#define EVENT_WRITE		2#define ENTRIES_LENGTH		1024
#define BUFFER_LENGTH		1024typedef int (*msg_handler)(char* msg, int length, char* response);
static msg_handler kvs_handler;struct conn_info {int fd;int event;
};int init_proactor_server(unsigned short port) {	int sockfd = socket(AF_INET, SOCK_STREAM, 0);	struct sockaddr_in serveraddr;	memset(&serveraddr, 0, sizeof(struct sockaddr_in));	serveraddr.sin_family = AF_INET;	serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);	serveraddr.sin_port = htons(port);	if (-1 == bind(sockfd, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr))) {		perror("bind");		return -1;	}	listen(sockfd, 10);return sockfd;
}int set_event_recv(struct io_uring *ring, int sockfd,void *buf, size_t len, int flags) {struct io_uring_sqe *sqe = io_uring_get_sqe(ring);struct conn_info accept_info = {.fd = sockfd,.event = EVENT_READ,};io_uring_prep_recv(sqe, sockfd, buf, len, flags);memcpy(&sqe->user_data, &accept_info, sizeof(struct conn_info));}int set_event_send(struct io_uring *ring, int sockfd,void *buf, size_t len, int flags) {struct io_uring_sqe *sqe = io_uring_get_sqe(ring);struct conn_info accept_info = {.fd = sockfd,.event = EVENT_WRITE,};io_uring_prep_send(sqe, sockfd, buf, len, flags);memcpy(&sqe->user_data, &accept_info, sizeof(struct conn_info));
}int set_event_accept(struct io_uring *ring, int sockfd, struct sockaddr *addr,socklen_t *addrlen, int flags) {struct io_uring_sqe *sqe = io_uring_get_sqe(ring);struct conn_info accept_info = {.fd = sockfd,.event = EVENT_ACCEPT,};io_uring_prep_accept(sqe, sockfd, (struct sockaddr*)addr, addrlen, flags);memcpy(&sqe->user_data, &accept_info, sizeof(struct conn_info));}int proactor_entry(unsigned short port, msg_handler handler) {kvs_handler = handler;int sockfd = init_proactor_server(port);struct io_uring_params params;memset(&params, 0, sizeof(params));struct io_uring ring;io_uring_queue_init_params(ENTRIES_LENGTH, &ring, &params);struct sockaddr_in clientaddr;	socklen_t len = sizeof(clientaddr);set_event_accept(&ring, sockfd, (struct sockaddr*)&clientaddr, &len, 0);char buffer[BUFFER_LENGTH] = {0};while (1) {io_uring_submit(&ring);struct io_uring_cqe *cqe;io_uring_wait_cqe(&ring, &cqe);struct io_uring_cqe *cqes[128];int nready = io_uring_peek_batch_cqe(&ring, cqes, 128);  // epoll_waitint i = 0;for (i = 0;i < nready;i ++) {struct io_uring_cqe *entries = cqes[i];struct conn_info result;memcpy(&result, &entries->user_data, sizeof(struct conn_info));if (result.event == EVENT_ACCEPT) {set_event_accept(&ring, sockfd, (struct sockaddr*)&clientaddr, &len, 0);//printf("set_event_accept\n"); //int connfd = entries->res;set_event_recv(&ring, connfd, buffer, BUFFER_LENGTH, 0);} else if (result.event == EVENT_READ) {  //int ret = entries->res;if (ret == 0) {close(result.fd);} else if (ret > 0) {// printf("set_event_recv ret: %d, %s\n", ret, buffer); // 协议解析char response[BUFFER_LENGTH] = {0};ret = kvs_handler(buffer, ret, response);set_event_send(&ring, result.fd, response, ret, 0);}}  else if (result.event == EVENT_WRITE) {int ret = entries->res;//printf("set_event_send ret: %d, %s\n", ret, buffer);set_event_recv(&ring, result.fd, buffer, BUFFER_LENGTH, 0);}}io_uring_cq_advance(&ring, nready);}
}

有了前面recator模块的理解,我们现在就只需要在已经实现过的io_uring代码中进行一些修改即可,将协议解析的内容添加进去。

Ntyco

当前协程实现是采用的一个 github 上开源的网络协程库组件来进行实现的,也是将对应的协议解析的内容添加进去即可:https://github.com/wangbojing/NtyCo,感兴趣的可以将对应的代码下载下来进行使用。

ntyco.c

#include "nty_coroutine.h"
#include <arpa/inet.h>typedef int (*msg_handler)(char* msg, int length, char* response);
static msg_handler kvs_handler;void server_reader(void *arg) {int fd = *(int *)arg;int ret = 0;while (1) {char buf[1024] = {0};ret = recv(fd, buf, 1024, 0);if (ret > 0) {// printf("read from server: %.*s\n", ret, buf);// 协议解析位置char response[1024] = {0};int slength = kvs_handler(buf, ret, response);ret = send(fd, response, slength, 0);if (ret == -1) {close(fd);break;}} else if (ret == 0) {	close(fd);break;}}
}void server(void *arg) {unsigned short port = *(unsigned short *)arg;int fd = socket(AF_INET, SOCK_STREAM, 0);if (fd < 0) return ;struct sockaddr_in local, remote;local.sin_family = AF_INET;local.sin_port = htons(port);local.sin_addr.s_addr = INADDR_ANY;bind(fd, (struct sockaddr*)&local, sizeof(struct sockaddr_in));listen(fd, 20);printf("listen port : %d\n", port);while (1) {socklen_t len = sizeof(struct sockaddr_in);int cli_fd = accept(fd, (struct sockaddr*)&remote, &len);nty_coroutine *read_co;nty_coroutine_create(&read_co, server_reader, &cli_fd);}
}int ntyco_start(unsigned short port, msg_handler handler) {kvs_handler = handler;nty_coroutine *co = NULL;nty_coroutine_create(&co, server, &port);nty_schedule_run();
}

接下来可以看一下对应的实践成果:
在这里插入图片描述
当前与网络模块的关联已经实现完毕,后续更新请看下一篇文章。

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

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

相关文章

c++和python联合编程示例

安装 C与 Python 绑定工具 pip install pybind11这其实相当于使用 python 安装了一个 c的库 pybind11,这个库只由头文件构成&#xff0c; 支持基础数据类型传递以及 python 的 numpy 和 c的 eigen 库之间的自动转换。 编写 CMakeList.txt cmake_minimum_required(VERSION 3.14)…

【OD机试题解法笔记】贪心歌手

题目描述 一个歌手准备从A城去B城参加演出。 按照合同&#xff0c;他必须在 T 天内赶到歌手途经 N 座城市歌手不能往回走每两座城市之间需要的天数都可以提前获知。歌手在每座城市都可以在路边卖唱赚钱。 经过调研&#xff0c;歌手提前获知了每座城市卖唱的收入预期&#xff1a…

AI: 告别过时信息, 用RAG和一份PDF 为LLM打造一个随需更新的“外脑”

嘿&#xff0c;各位技术同学&#xff01;今天&#xff0c;我们来聊一个大家在使用大语言模型&#xff08;LLM&#xff09;时都会遇到的痛点&#xff1a;知识过时。 无论是像我一样&#xff0c;用 Gemini Pro 学习日新月异的以太坊&#xff0c;还是希望它能精确掌握某个特定工具…

深度学习(鱼书)day08--误差反向传播(后三节)

深度学习&#xff08;鱼书&#xff09;day08–误差反向传播&#xff08;后三节&#xff09;一、激活函数层的实现 这里&#xff0c;我们把构成神经网络的层实现为一个类。先来实现激活函数的ReLU层和Sigmoid层。ReLU层 激活函数ReLU&#xff08;Rectified Linear Unit&#xff…

C# 中生成随机数的常用方法

1. 使用 Random 类&#xff08;简单场景&#xff09; 2. 使用 RandomNumberGenerator 类&#xff08;安全场景&#xff09; 3. 生成指定精度的随机小数 C# 中生成随机数的常用方法&#xff1a; 随机数类型实现方式示例代码特点与适用场景随机整数&#xff08;无范围&#xf…

Flink 算子链设计和源代码实现

1、JobGraph &#xff08;JobManager&#xff09; JobGraph 生成时&#xff0c;通过 ChainingStrategy 连接算子&#xff0c;最终在 Task 中生成 ChainedDriver 链表。StreamingJobGraphGeneratorcreateJobGraph() 构建jobGrapch 包含 JobVertex setChaining() 构建算子链isCha…

对接八大应用渠道

背景最近公司想把游戏包上到各个渠道上&#xff0c;因此需要对接各种渠道&#xff0c;渠道如下&#xff0c;oppo、vivo、华为、小米、应用宝、taptap、荣耀、三星等应用渠道 主要就是对接登录、支付接口&#xff08;后续不知道会不会有其他的&#xff09;&#x…

学习:入门uniapp Vue3组合式API版本(17)

42.打包发行微信小程序的上线全流程 域名 配置 发行 绑定手机号 上传 提交后等待&#xff0c;上传 43.打包H5并发布上线到unicloud的前端页面托管 完善配置 unicloud 手机号实名信息不一致&#xff1a;请确保手机号的实名信息与开发者姓名、身份证号一致&#xff0c;请前往开…

SOLIDWORKS材料明细表设置,属于自己的BOM表模板

上一期我们了解了如何在SOLIDWORKS工程图中添加材料明细表?接下来&#xff0c;我们将进行对SOLIDWORKS材料明细表的设置、查看缩略图、模板保存的深度讲解。01 材料明细表设置菜单栏生成表格后左侧菜单栏会显示关于材料明细表的相关设置信息。我们先了解一下菜单栏设置详情&am…

全栈:Maven的作用是什么?本地仓库,私服还有中央仓库的区别?Maven和pom.xml配置文件的关系是什么?

Maven和pom.xml配置文件的关系是什么&#xff1a; Maven是一个构建工具和依赖管理工具&#xff0c;而pom.xml&#xff08;Project Object Model&#xff09;是Maven的核心配置文件。 SSM 框架的项目不一定是 Maven 项目&#xff0c;但推荐使用 Maven进行管理。 SSM 框架的项目可…

超越 ChatGPT:智能体崛起,开启全自主 AI 时代

引言 短短三年,生成式 AI 已从对话助手跨越到能自主规划并完成任务的“智能体(Agentic AI)”时代。这场演进不仅体现在模型规模的提升,更在于系统架构、交互范式与安全治理的全面革新。本文按时间线梳理关键阶段与核心技术,为您呈现 AI 智能体革命的脉络与未来趋势。 1. …

一杯就够:让大脑瞬间在线、让肌肉满电的 “Kick-out Drink” 全解析

一杯就够&#xff1a;让大脑瞬间在线、让肌肉满电的 “Kick-out Drink” 全解析“每天清晨&#xff0c;当闹钟还在哀嚎&#xff0c;你举杯一饮&#xff0c;睡意像被扔出擂台——这&#xff0c;就是 Kick-out Drink 的全部浪漫。”清晨 30 分钟后&#xff0c;250 mL 常温水里溶解…

系统开机时自动执行指令

使用 systemd 创建一个服务单元可以让系统开机时自动执行指令&#xff0c;假设需要执行的指令如下&#xff0c;运行可执行文件&#xff08;/home/demo/可执行文件&#xff09;&#xff0c;并输入参数&#xff08;–input/home/config/demo.yaml&#xff09;&#xff1a; /home/…

Docker 初学者需要了解的几个知识点 (七):php.ini

这段配置是 php.ini 文件中针对 PHP 扩展和 Xdebug 调试工具的设置&#xff0c;主要用于让 PHP 支持数据库连接和代码调试&#xff08;尤其在 Docker 环境中&#xff09;&#xff0c;具体解释如下&#xff1a;[PHP] extensionpdo_mysql extensionmysqli xdebug.modedebug xdebu…

【高阶版】R语言空间分析、模拟预测与可视化高级应用

随着地理信息系统&#xff08;GIS&#xff09;和大尺度研究的发展&#xff0c;空间数据的管理、统计与制图变得越来越重要。R语言在数据分析、挖掘和可视化中发挥着重要的作用&#xff0c;其中在空间分析方面扮演着重要角色&#xff0c;与空间相关的包的数量也达到130多个。在本…

dolphinscheduler中一个脚本用于从列定义中提取列名列表

dolphinscheduler中&#xff0c;我们从一个mysql表导出数据&#xff0c;上传到hdfs, 再创建一个临时表&#xff0c;所以需要用到列名定义和列名列表。 原来定义两个变量&#xff0c;不仅繁锁&#xff0c;还容易出现差错&#xff0c;比如两者列序不对。 所以考虑只定义列定义变量…

JavaWeb(苍穹外卖)--学习笔记16(定时任务工具Spring Task,Cron表达式)

前言 本篇文章是学习B站黑马程序员苍穹外卖的学习笔记&#x1f4d1;。我的学习路线是Java基础语法-JavaWeb-做项目&#xff0c;管理端的功能学习完之后&#xff0c;就进入到了用户端微信小程序的开发&#xff0c;用户端开发的流程大致为用户登录—商品浏览&#xff08;其中涉及…

灵敏度,精度,精确度,精密度,精准度,准确度,分辨率,分辨力——概念

文章目录前提总结前提 我最近在整理一份数据指标要求的时候&#xff0c;总是混淆这几个概念&#xff1a;灵敏度&#xff0c;精度&#xff0c;精确度&#xff0c;精密度&#xff0c;精准度&#xff0c;准确度&#xff0c;分辨率&#xff0c;分辨力&#xff0c;搜了一些文章&…

python-异常(笔记)

#后续代码可以正常运行 try:f open("xxx.txt","r",encodingutf-8)except:print("except error")#捕获指定异常&#xff0c;其他异常报错程序中止&#xff0c;管不到 try:print(name) except NameError as you_call:print("name error"…

[lvgl_player] 用户界面(LVGL) | 播放器核心设计

docs&#xff1a;基于LVGL的音乐播放器 本项目是为嵌入式设备设计的音乐播放系统&#xff0c;采用LVGL图形库构建用户界面。 系统支持播放WAV格式音频文件&#xff0c;具备播放列表管理功能&#xff0c;可实现播放/暂停控制、曲目切换等核心操作。 用户可通过交互界面实时调…