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

Live555源码阅读笔记:哈希表的实现

😁博客主页😁:🚀https://blog.csdn.net/wkd_007🚀
🤑博客内容🤑:🍭嵌入式开发、Linux、C语言、C++、数据结构、音视频🍭
🤣本文内容🤣:🍭介绍哈希表的实现 🍭
😎金句分享😎:🍭你不能选择最好的,但最好的会来选择你——泰戈尔🍭
⏰发布时间⏰: 2024-07-24 23:44:05

本文未经允许,不得转发!!!

目录

  • 🎄一、概述
  • 🎄二、HashTable 类
  • 🎄三、BasicHashTable 类
    • ✨3.1 插入操作
    • ✨3.2 删除操作
    • ✨3.3 查询操作
    • ✨3.4 查询哈希表元素个数
  • 🎄四、哈希表的使用
  • 🎄五、总结


在这里插入图片描述

在这里插入图片描述

🎄一、概述

本文介绍Live555源码的哈希表实现,如果对哈希表不了解的,可以看这篇文章:https://blog.csdn.net/wkd_007/article/details/140655297

哈希表是存储 键值对 的一个数据结构。一个哈希表应该要提供插入元素、删除元素、查询元素的操作,而具体的哈希表实现还会出现哈希函数、处理哈希冲突、键值对相关结构体、比对键值等,我们看哈希表代码时需要格外注意这些实现。

下图是Live555哈希表相关类的关系图:
1、HashTable 是个基类,规范哈希表的接口;
2、BasicHashTable 是哈希表的具体实现;
3、TableEntry 是存储键值对的,同时它还有个指针指向自己,说明这里应该是使用 链地址法 来处理哈希冲突的。

在这里插入图片描述
Live555哈希表相关代码主要在这4个文件:HashTable.hh、HashTable.cpp、BasicHashTable.hh、BasicHashTable.cpp。主要涉及两个类,下面介绍具体实现。


在这里插入图片描述

🎄二、HashTable 类

HashTable 类声明:

class HashTable {
public:virtual ~HashTable();// The following must be implemented by a particular// implementation (subclass):static HashTable* create(int keyType);virtual void* Add(char const* key, void* value) = 0;  // Returns the old value if different, otherwise 0virtual Boolean Remove(char const* key) = 0;virtual void* Lookup(char const* key) const = 0;virtual unsigned numEntries() const = 0;	// Returns 0 if not foundBoolean IsEmpty() const { return numEntries() == 0; }// Used to iterate through the members of the table:class Iterator {public:// The following must be implemented by a particular// implementation (subclass):static Iterator* create(HashTable const& hashTable);virtual ~Iterator();virtual void* next(char const*& key) = 0; // returns 0 if noneprotected:Iterator(); // abstract base class};// A shortcut that can be used to successively remove each of// the entries in the table (e.g., so that their values can be// deleted, if they happen to be pointers to allocated memory).void* RemoveNext();// Returns the first entry in the table.// (This is useful for deleting each entry in the table, if the entry's destructor also removes itself from the table.)void* getFirst(); protected:HashTable(); // abstract base class
};

首先,我们看到 HashTable 类声明了4个纯虚函数:

virtual void* Add(char const* key, void* value) = 0;// Returns the old value if different, otherwise 0
virtual Boolean Remove(char const* key) = 0;
virtual void* Lookup(char const* key) const = 0;
virtual unsigned numEntries() const = 0;

这说明 HashTable 类是个抽象基类,所以它不会实例化对象,同时很可能会使用 HashTable 类的指针或引用去管理其派生类对象。这四个函数对应了哈希表的插入、删除、查询、哈希值总个数 4个操作,这里也规定了子类必须按照这样的声明去实现这几个操作。


其次,注意到static HashTable* create(int keyType);,说明很大可能使用这个函数来创建哈希表,并且这里的返回值是 HashTable*,但HashTable是抽象类,不会有对象,这里的指针应该指向派生类 BasicHashTable 的对象。函数参数 keyType 说明创建哈希表需要指定键值类型,HashTable.hh给出了两个键值类型:

int const STRING_HASH_KEYS = 0;
int const ONE_WORD_HASH_KEYS = 1;

再者,HashTable 类声明了一个迭代器类 Iterator ,并且要求传入哈希表引用,应该是用来遍历哈希表的。

最后就是 RemoveNext 操作删除一个哈希表元素,getFirst 操作获取第一个哈希表元素。


在这里插入图片描述

🎄三、BasicHashTable 类

BasicHashTable 类是哈希表的具体实现,它实现了哈希表的基本操作:插入、删除、查询。也实现了哈希函数、键值比对、哈希表扩容等函数,不过这些都是隐藏(private)的。

BasicHashTable 类声明如下:

class BasicHashTable: public HashTable {
private:class TableEntry; // forwardpublic:BasicHashTable(int keyType);virtual ~BasicHashTable();// Used to iterate through the members of the table:class Iterator; friend class Iterator; // to make Sun's C++ compiler happyclass Iterator: public HashTable::Iterator {// 迭代器,用于迭代传入的哈希表 2024-07-22 23:43:05public:Iterator(BasicHashTable const& table);private: // implementation of inherited pure virtual functionsvoid* next(char const*& key); // returns 0 if noneprivate:BasicHashTable const& fTable;unsigned fNextIndex; // index of next bucket to be enumerated after thisTableEntry* fNextEntry; // next entry in the current bucket};private: // implementation of inherited pure virtual functions// 实现继承的纯虚函数,这里是 private 的,也就是说不能通过 BasicHashTable 对象直接调用,但可以通过父类指针或引用来多态调用 2024-07-24virtual void* Add(char const* key, void* value);// Returns the old value if different, otherwise 0virtual Boolean Remove(char const* key);virtual void* Lookup(char const* key) const;// Returns 0 if not foundvirtual unsigned numEntries() const;private:class TableEntry {public:TableEntry* fNext;char const* key;void* value;};TableEntry* lookupKey(char const* key, unsigned& index) const;// returns entry matching "key", or NULL if noneBoolean keyMatches(char const* key1, char const* key2) const;// used to implement "lookupKey()"TableEntry* insertNewEntry(unsigned index, char const* key);// creates a new entry, and inserts it in the tablevoid assignKey(TableEntry* entry, char const* key);// used to implement "insertNewEntry()"void deleteEntry(unsigned index, TableEntry* entry);void deleteKey(TableEntry* entry);// used to implement "deleteEntry()"void rebuild(); // rebuilds the table as its size increasesunsigned hashIndexFromKey(char const* key) const;// used to implement many of the routines aboveunsigned randomIndex(uintptr_t i) const {return (unsigned)(((i*1103515245) >> fDownShift) & fMask);}private:TableEntry** fBuckets; // pointer to bucket arrayTableEntry* fStaticBuckets[SMALL_HASH_TABLE_SIZE];// used for small tablesunsigned fNumBuckets, fNumEntries, fRebuildSize, fDownShift, fMask;int fKeyType;
};

首先,BasicHashTable类继承自HashTable类,并且实现了继承的全部纯虚函数,所以BasicHashTable类不是抽象类,可以实例化对象。BasicHashTable类有点特别,只提供了创建对象接口(BasicHashTable)、销毁对象接口(~BasicHashTable),其他函数都是私有的,即使构造了对象,也没有公有接口可以使用,让用户只能通过基类(HashTable)指针或引用去使用基类声明为公有的几个纯虚函数,然后通过多态调用到 BasicHashTable 类的相应接口。

构造函数:

#define REBUILD_MULTIPLIER 3
BasicHashTable::BasicHashTable(int keyType): fBuckets(fStaticBuckets), fNumBuckets(SMALL_HASH_TABLE_SIZE),fNumEntries(0), fRebuildSize(SMALL_HASH_TABLE_SIZE*REBUILD_MULTIPLIER),fDownShift(28), fMask(0x3), fKeyType(keyType) {for (unsigned i = 0; i < SMALL_HASH_TABLE_SIZE; ++i) {fStaticBuckets[i] = NULL;}
}

哈希表最初创建时,只是用个包含3个元素的数组。

在阅读下面小节之前,先了解 BasicHashTable 存储键值对的结构体,除了key、value,还包含了一个 fNext 指针:

class TableEntry {
public:TableEntry* fNext;char const* key;void* value;
};

✨3.1 插入操作

BasicHashTable类插入操作通过 Add 函数实现,代码如下:

// 添加一个键值对到哈希表,2024-07-22 23:49:05
void* BasicHashTable::Add(char const* key, void* value) {void* oldValue;unsigned index;TableEntry* entry = lookupKey(key, index);if (entry != NULL) {// There's already an item with this keyoldValue = entry->value;} else {// There's no existing entry; create a new one:entry = insertNewEntry(index, key);oldValue = NULL;}entry->value = value;// If the table has become too large, rebuild it with more buckets:if (fNumEntries >= fRebuildSize) rebuild();return oldValue;
}

先查找 key 值是否已存在了,是的话就替换并返回旧的value值,不存在就插入新的键值对(insertNewEntry)。下面看看Add依赖的几个函数:lookupKey、hashIndexFromKey、keyMatches、insertNewEntry、assignKey、rebuild。

lookupKey 函数:根据key找到一个键值对(entry),下标index

// 根据key找到一个键值对(entry),下标index,2024-07-22 23:36:55
BasicHashTable::TableEntry* BasicHashTable
::lookupKey(char const* key, unsigned& index) const {TableEntry* entry;index = hashIndexFromKey(key);for (entry = fBuckets[index]; entry != NULL; entry = entry->fNext) {if (keyMatches(key, entry->key)) break;}return entry;
}

先计算出哈希地址(hashIndexFromKey),再比对键值,找到键值对 entry。

hashIndexFromKey函数:哈希函数,根据key生成哈希地址index

// 哈希函数,根据key生成一个下标 2024-07-22 23:07:25
unsigned BasicHashTable::hashIndexFromKey(char const* key) const {unsigned result = 0;if (fKeyType == STRING_HASH_KEYS) {while (1) {char c = *key++;if (c == 0) break;result += (result<<3) + (unsigned)c;}result &= fMask;printf("result = %d\n",result);} else if (fKeyType == ONE_WORD_HASH_KEYS) {result = randomIndex((uintptr_t)key);	} else {unsigned* k = (unsigned*)key;uintptr_t sum = 0;for (int i = 0; i < fKeyType; ++i) {sum += k[i];}result = randomIndex(sum);}return result;
}

按照不同键值类型去生成哈希地址。

keyMatches 函数:比对两个键值是否相等,相等返回true。

// 比较两个key是否一致 2024-07-22 23:35:24
Boolean BasicHashTable
::keyMatches(char const* key1, char const* key2) const {// The way we check the keys for a match depends upon their type:if (fKeyType == STRING_HASH_KEYS) {return (strcmp(key1, key2) == 0);} else if (fKeyType == ONE_WORD_HASH_KEYS) {return (key1 == key2);} else {unsigned* k1 = (unsigned*)key1;unsigned* k2 = (unsigned*)key2;for (int i = 0; i < fKeyType; ++i) {if (k1[i] != k2[i]) return False; // keys differ}return True;}
}

insertNewEntry函数:生成一个entry, 插入到链表,并给entry->key赋值

// 生成一个entry, 插入到链表,并给entry->key赋值,2024-07-22 23:34:42
BasicHashTable::TableEntry* BasicHashTable
::insertNewEntry(unsigned index, char const* key) {TableEntry* entry = new TableEntry();entry->fNext = fBuckets[index];fBuckets[index] = entry;++fNumEntries;assignKey(entry, key);return entry;
}

这里使用头插法,将新的entry插入到 fBuckets[index] 的位置。

assignKey函数: 根据键值类型、key值,创建一个 Key,并赋值到entry的key字段

// 根据键值类型、key值,创建一个 Key,并赋值到entry的key字段 2024-07-22 21:16:20
void BasicHashTable::assignKey(TableEntry* entry, char const* key) {// The way we assign the key depends upon its type:if (fKeyType == STRING_HASH_KEYS) {entry->key = strDup(key);} else if (fKeyType == ONE_WORD_HASH_KEYS) {entry->key = key;} else if (fKeyType > 0) {unsigned* keyFrom = (unsigned*)key;unsigned* keyTo = new unsigned[fKeyType];for (int i = 0; i < fKeyType; ++i) keyTo[i] = keyFrom[i];entry->key = (char const*)keyTo;}
}

rebuild 函数:重新建立一个哈希表

// 重新建立一个哈希表,2024-07-22 21:22:08
void BasicHashTable::rebuild() {// Remember the existing table size:unsigned oldSize = fNumBuckets;TableEntry** oldBuckets = fBuckets;// Create the new sized table:fNumBuckets *= 4;fBuckets = new TableEntry*[fNumBuckets];for (unsigned i = 0; i < fNumBuckets; ++i) {fBuckets[i] = NULL;}fRebuildSize *= 4;fDownShift -= 2;fMask = (fMask<<2)|0x3;// Rehash the existing entries into the new table:for (TableEntry** oldChainPtr = oldBuckets; oldSize > 0;--oldSize, ++oldChainPtr) {for (TableEntry* hPtr = *oldChainPtr; hPtr != NULL;hPtr = *oldChainPtr) {*oldChainPtr = hPtr->fNext;unsigned index = hashIndexFromKey(hPtr->key);hPtr->fNext = fBuckets[index];fBuckets[index] = hPtr;}}// Free the old bucket array, if it was dynamically allocated:if (oldBuckets != fStaticBuckets) delete[] oldBuckets;
}

当哈希表键值对个数太多时,就会重建哈希表,避免因个数太多降低查询效率。


✨3.2 删除操作

BasicHashTable 的删除操作通过 Remove 函数实现,代码如下:

// 删除一个键值对(entry),2024-07-22 23:49:08
Boolean BasicHashTable::Remove(char const* key) {unsigned index;TableEntry* entry = lookupKey(key, index);if (entry == NULL) return False; // no such entrydeleteEntry(index, entry);return True;
}

1、先根据key找到键值对entry、index;
2、再删除键值对 (deleteEntry)。

deleteEntry 函数:删除指定下标的一个键值对(Entry)

// 删除指定下标的一个键值对(Entry),2024-07-22 21:20:18
void BasicHashTable::deleteEntry(unsigned index, TableEntry* entry) {TableEntry** ep = &fBuckets[index];	// 找到存在数组的位置Boolean foundIt = False;while (*ep != NULL) {  // 遍历链表if (*ep == entry) {foundIt = True;*ep = entry->fNext;break;}ep = &((*ep)->fNext);}if (!foundIt) { // shouldn't happen
#ifdef DEBUGfprintf(stderr, "BasicHashTable[%p]::deleteEntry(%d,%p): internal error - not found (first entry %p", this, index, entry, fBuckets[index]);if (fBuckets[index] != NULL) fprintf(stderr, ", next entry %p", fBuckets[index]->fNext);fprintf(stderr, ")\n");
#endif}--fNumEntries;deleteKey(entry);delete entry;
}

1、先找到存在数组的位置;
2、遍历链表,找到键值对entry;
3、删除键值(deleteKey);

deleteKey函数:删除一个键值对(Entry)的Key

// 删除一个键值对(Entry)的Key,2024-07-22 21:21:12
void BasicHashTable::deleteKey(TableEntry* entry) {// The way we delete the key depends upon its type:if (fKeyType == ONE_WORD_HASH_KEYS) {entry->key = NULL;} else {delete[] (char*)entry->key;entry->key = NULL;}
}

✨3.3 查询操作

BasicHashTable 的查询操作通过 Lookup 函数实现,代码如下:

// 查找,根据key找到value,2024-07-22 23:48:20
void* BasicHashTable::Lookup(char const* key) const {unsigned index;TableEntry* entry = lookupKey(key, index);if (entry == NULL) return NULL; // no such entryreturn entry->value;
}

通过key找到键值对entry,然后将value返回。


✨3.4 查询哈希表元素个数

BasicHashTable 的查询元素个数通过 numEntries 函数实现,代码如下:

// 哈希表总的键值对(entry)个数,2024-07-22 23:47:35
unsigned BasicHashTable::numEntries() const {return fNumEntries;
}

在这里插入图片描述

🎄四、哈希表的使用

这个小节,写个示例来使用上面介绍的哈希表,完整源码也上传了。下载地址:

// g++ *.cpp -o HashTest#include <iostream>
#include "BasicHashTable.hh"
using namespace std;void printHashTable(HashTable const& hashTable)
{HashTable::Iterator *itor = HashTable::Iterator::create(hashTable);void* value = NULL;const char* pKey = NULL;while((value = itor->next(pKey)) != NULL){const char* value_str = (const char*)value;cout << "Key=" << pKey << ", Value=" << value_str << endl;}delete itor;
}int main()
{HashTable *pHashTable = new BasicHashTable(STRING_HASH_KEYS);pHashTable->Add("testA",(void*)"testA");pHashTable->Add("testB",(void*)"testB");pHashTable->Add("testC",(void*)"testC");pHashTable->Add("testD",(void*)"testD");pHashTable->Add("testE",(void*)"testE");pHashTable->Add("",(void*)"testF");		// 空字符串也可以const char *pTestA = (const char*)pHashTable->Lookup("testA");cout << "testA=" << pTestA << endl;cout << endl;printHashTable(*pHashTable);cout << endl;pHashTable->Add("",(void*)"testG");		// 替换空字符串的值printHashTable(*pHashTable);cout << endl;delete pHashTable;return 0;
}

在这里插入图片描述

🎄五、总结

👉本文介绍了Live555的哈希表实现,最后给出了使用例子,对于想了解哈希表实现或Live555源码的同学有一定的帮助。

在这里插入图片描述
如果文章有帮助的话,点赞👍、收藏⭐,支持一波,谢谢 😁😁😁

相关文章:

Live555源码阅读笔记:哈希表的实现

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…...

vue3创建vite项目

一、创建vue3 vite项目&#xff1a; 命令行创建&#xff1a;npm create vitelatest vue3-tdly-demo -- --template vue (1)先进入项目文件夹&#xff0c;cd vue3-tdly-demo (2)之后执行&#xff0c; npm install (3)最后运行&#xff0c;npm run dev 将main.js文件内容改成…...

Maven概述

目录 1.Maven简介 2.Maven开发环境搭建 2.1下载Maven服务器 2.2安装&#xff0c;配置Maven 1.配置本地仓库地址 2.配置阿里云镜像地址 2.3在idea中配置maven 2.4在idea中创建maven项目 3.pom.xml配置 1.项目基本信息 2.依赖信息 3.构建信息 4.Maven命令 5.打包Jav…...

Easyu中datagrid点击时获取所在行的数据

问题 双击单元格时&#xff0c;获取该行的记录内容 $(#list).datagrid({method: post,url: user/list,queryParams:{sex : "f",age : "18~25"},fitColumns: true,pageList: [ 5, 10, 15, 20 ],singleSelect: false,pagination: true,fit: true,rownumber…...

java项目中添加SDK项目作为依赖使用(无需上传Maven)

需求&#xff1a; 当需要多次调用某个函数或算法时&#xff0c;不想每次调用接口都自己编写&#xff0c;可以将该项目打包&#xff0c;以添加依赖的方式实现调用 适用于&#xff1a; 无需上线的项目&#xff0c;仅公司或团队内部使用的项目 操作步骤&#xff1a; 以下面这…...

区块链和数据要素融合的价值及应用

一、数据要素面临的关键障碍 在构建数据要素基石的过程中&#xff0c;首要任务是明确并解决产权架构的难题&#xff0c;特别是使用权的确立与流转机制的顺畅&#xff0c;此乃数字经济蓬勃发展的命脉所在。一个高效的数据流转体系对于激发数据潜能、加速经济发展及优化数据资源…...

以太坊的可扩展性危机:探索执行层的瓶颈

导读&#xff1a;以太坊执行层承担着交易处理、智能合约执行以及保持一致和安全状态的维护等工作。Fuel Labs 撰文解析了以太坊执行层的工作原理&#xff0c;及其在可扩展性方面的发展瓶颈和影响。 Fuel Labs: 执行是指在区块链上执行交易和执行状态更改所需的计算。此计算通常…...

静态解析activiti文本,不入库操作流程

说明&#xff1a; activiti本身状态存库&#xff0c;导致效率太低&#xff0c;把中间状态封装成一个载荷类&#xff0c;返回给上游&#xff0c;下次请求时给带着载荷类即可。 1.pom依赖 <dependency><groupId>net.sf.json-lib</groupId><artifactId>js…...

100个python的基本语法知识【上】

0. 变量和赋值&#xff1a; x 5 name “John” 1. 数据类型&#xff1a; 整数&#xff08;int&#xff09; 浮点数&#xff08;float&#xff09; 字符串&#xff08;str&#xff09; 布尔值&#xff08;bool&#xff09; 2. 注释&#xff1a; # 这是单行注释 ""…...

Python从0到100(四十四):读取数据库数据

前言&#xff1a; 零基础学Python&#xff1a;Python从0到100最新最全教程。 想做这件事情很久了&#xff0c;这次我更新了自己所写过的所有博客&#xff0c;汇集成了Python从0到100&#xff0c;共一百节课&#xff0c;帮助大家一个月时间里从零基础到学习Python基础语法、Pyth…...

ZLMRTCClient配置说明与用法(含示例)

webRTC播放视频 后面在项目中会用到通过推拉播放视频流的技术&#xff0c;所以最近预研了一下webRTC 首先需要引入封装好的webRTC客户端的js文件ZLMRTCClient.js 下面是地址需要的自行下载 http://my.zsyou.top/2024/ZLMRTCClient.js 配置说明 new ZLMRTCClient.Endpoint…...

nginx代理服务配置,基于http协议-Linux(CentOS)

基于http协议的nginx代理服务 1. 打开 Nginx 虚拟机80端口配置文件2. 添加代理配置3. 重启nginx服务 nginx代理缓存配置 1. 打开 Nginx 虚拟机80端口配置文件 Nginx 的默认80端口虚拟机配置文件通常位于/etc/nginx/conf.d/default.conf。 vim /etc/nginx/conf.d/default.con…...

Photos框架 - 自定义媒体资源选择器(数据部分)

引言 在iOS开发中&#xff0c;系统已经为我们提供了多种便捷的媒体资源选择方式&#xff0c;如UIImagePickerController和PHPickerViewController。这些方式不仅使用方便、界面友好&#xff0c;而且我们完全不需要担心性能和稳定性问题&#xff0c;因为它们是由系统提供的&…...

Spring Boot + Spring Cloud 入门

运行配置 java -jar spring-boot-config-0.0.1-SNAPSHOT.jar --spring.profiles.activetest --my1.age32 --debugtrue "D:\Program Files\Redis\redis-server.exe" D:\Program Files\Redis\redis.windows.conf "D:\Program Files\Redis\redis-cli.exe" &q…...

怎么使用动态IP地址上网

如何设置动态IP地址上网&#xff1f; 设置动态IP地址上网的步骤如下&#xff1a; 一、了解动态IP地址 动态IP地址是由网络服务提供商&#xff08;ISP&#xff09;动态分配给用户的IP地址&#xff0c;它会根据用户的需求和网络情况实时改变。相比于静态IP地址&#xff0c;动态…...

【源码+文档+调试讲解】智慧物流小程序的设计与实现

摘 要 互联网发展至今&#xff0c;无论是其理论还是技术都已经成熟&#xff0c;而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播&#xff0c;搭配信息管理工具可以很好地为人们提供服务。针对高校教师成果信息管理混乱&#xff0c;出错率高&#xff0c;信息安全…...

QT:控件圆角设置、固定窗口大小

实现控件圆角度设置//使用的是setStyleSheet方法 //改变的控件是QTextEdit&#xff0c;如果你想改变其他控件&#xff0c;将QTextEdit进行更换 this->setStyleSheet("QTextEdit{background-color:#FFFFFF;border-top-left-radius:15px;border-top-right-radius:15px;bo…...

【JavaScript】深入理解 `let`、`var` 和 `const`

文章目录 一、var 的声明与特点二、let 的声明与特点三、const 的声明与特点四、let、var 和 const 的对比五、实战示例六、最佳实践 在 JavaScript 中&#xff0c;变量声明是编程的基础&#xff0c;而 let、var 和 const 是三种常用的变量声明方式。本文将详细介绍这三种变量声…...

云监控(华为) | 实训学习day7(10)

水一篇。。。。。。。。。。。。。 强迫症打卡必须要满 企拓 今天没有将东西 2024/7/22 规划学习路线对于进入AI行业至关重要。以下是一个详细的学习路线规划&#xff0c;旨在帮助你从零基础到成为一名合格的AI或大数据分析师&#xff1a; 第一阶段&#xff1a;基础知识建设…...

JS_plus.key.addEventListener监听键盘按键

官方文档&#xff1a;https://www.html5plus.org/doc/zh_cn/key.html 监听事件 plus.key.addEventListener(keydown, e > {console.log("keydown: "e.keyCode) }) plus.key.addEventListener(keyup, e > {console.log("keyup: "e.keyCode) })移除事…...

Python爬虫实战:研究MechanicalSoup库相关技术

一、MechanicalSoup 库概述 1.1 库简介 MechanicalSoup 是一个 Python 库,专为自动化交互网站而设计。它结合了 requests 的 HTTP 请求能力和 BeautifulSoup 的 HTML 解析能力,提供了直观的 API,让我们可以像人类用户一样浏览网页、填写表单和提交请求。 1.2 主要功能特点…...

React hook之useRef

React useRef 详解 useRef 是 React 提供的一个 Hook&#xff0c;用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途&#xff0c;下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...

盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来

一、破局&#xff1a;PCB行业的时代之问 在数字经济蓬勃发展的浪潮中&#xff0c;PCB&#xff08;印制电路板&#xff09;作为 “电子产品之母”&#xff0c;其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透&#xff0c;PCB行业面临着前所未有的挑战与机遇。产品迭代…...

Python:操作 Excel 折叠

💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...

Day131 | 灵神 | 回溯算法 | 子集型 子集

Day131 | 灵神 | 回溯算法 | 子集型 子集 78.子集 78. 子集 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a; 笔者写过很多次这道题了&#xff0c;不想写题解了&#xff0c;大家看灵神讲解吧 回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili 完…...

【Redis技术进阶之路】「原理分析系列开篇」分析客户端和服务端网络诵信交互实现(服务端执行命令请求的过程 - 初始化服务器)

服务端执行命令请求的过程 【专栏简介】【技术大纲】【专栏目标】【目标人群】1. Redis爱好者与社区成员2. 后端开发和系统架构师3. 计算机专业的本科生及研究生 初始化服务器1. 初始化服务器状态结构初始化RedisServer变量 2. 加载相关系统配置和用户配置参数定制化配置参数案…...

【RockeMQ】第2节|RocketMQ快速实战以及核⼼概念详解(二)

升级Dledger高可用集群 一、主从架构的不足与Dledger的定位 主从架构缺陷 数据备份依赖Slave节点&#xff0c;但无自动故障转移能力&#xff0c;Master宕机后需人工切换&#xff0c;期间消息可能无法读取。Slave仅存储数据&#xff0c;无法主动升级为Master响应请求&#xff…...

图表类系列各种样式PPT模版分享

图标图表系列PPT模版&#xff0c;柱状图PPT模版&#xff0c;线状图PPT模版&#xff0c;折线图PPT模版&#xff0c;饼状图PPT模版&#xff0c;雷达图PPT模版&#xff0c;树状图PPT模版 图表类系列各种样式PPT模版分享&#xff1a;图表系列PPT模板https://pan.quark.cn/s/20d40aa…...

Linux --进程控制

本文从以下五个方面来初步认识进程控制&#xff1a; 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程&#xff0c;创建出来的进程就是子进程&#xff0c;原来的进程为父进程。…...

【Java学习笔记】BigInteger 和 BigDecimal 类

BigInteger 和 BigDecimal 类 二者共有的常见方法 方法功能add加subtract减multiply乘divide除 注意点&#xff1a;传参类型必须是类对象 一、BigInteger 1. 作用&#xff1a;适合保存比较大的整型数 2. 使用说明 创建BigInteger对象 传入字符串 3. 代码示例 import j…...