【C++進階篇】初識哈希

哈希表深度剖析:原理、沖突解決與C++容器實戰

  • 一. 哈希
    • 1.1 哈希概念
    • 1.2 哈希思想
    • 1.3 常見的哈希函數
      • 1.3.1 直接定址法
      • 1.3.2 除留余數法
      • 1.3.3 乘法散列法(了解)
      • 1.3.4 平方取中法(了解)
    • 1.4 哈希沖突
      • 1.4.1 沖突原因
      • 1.4.2 解決辦法
  • 二. unordered_set / unordered_map詳細介紹
    • 2.1 unordered_set使用
    • 2.2 unordered_map使用
    • 2.3 unordered_set vs set 對比
  • 三. 最后

在當今數據處理的浪潮中,我們常常面臨海量信息的存儲與檢索挑戰。如何在海量數據中快速定位所需信息,成為提升效率的關鍵。此時,哈希技術應運而生,它如同一把神奇的鑰匙,能夠將復雜多樣的數據通過特定算法映射為簡潔的哈希值,從而實現高效的數據存儲與查詢。哈希技術不僅在計算機科學中占據重要地位,更廣泛應用于數據庫、網絡安全、分布式系統等諸多領域。今天,就讓我們深入探索哈希的奧秘,從它的基本概念、核心思想,到實際應用中的種種細節,一窺究竟。

一. 哈希

1.1 哈希概念

哈希(Hash),又稱散列,是一種將任意長度輸入數據通過特定算法映射為固定長度輸出值(哈希值)的核心技術。

1.2 哈希思想

  • 核心思想:

哈希的核心思想是通過哈希函數將數據轉換為固定長度的哈希值,實現高效的數據處理與存儲。其設計遵循以下原則:

  1. 確定性:相同輸入必產生相同哈希值,確保結果可預測。
  2. 高效性:哈希函數計算速度快,適用于大規模數據處理。
  3. 均勻性:哈希值需均勻分布,減少不同輸入映射到同一值(哈希沖突)的概率。

個人理解:哈希使用vector容器存儲數據,就是將鍵值模上數組的大小,從而建立鍵值與位置的一一映射,不可避免的存在沖突,什么沖突?換句話說,難免會出現某些值通過上述的算法,會統一的映射到同一位置。
例如假設數組的大小為8,某些數據(如16,24等)進行映射,16%8=0,24%8=0,上述兩個數據都映射到了0這個下標,這就是沖突的表現。所以哈希函數就需要設計的合理,一個優秀的哈希函數需要盡可能減少這種沖突。

1.3 常見的哈希函數

哈希函數的種類特別繁多,常見的哈希函數如下:

1.3.1 直接定址法

函數原型:Hashi = a * key +b

  • 優點:數據較為集中,均勻
  • 缺點:當數據很散亂的時候,空間利用不充分,浪費空間資源
  • 適應場景:當數據較為集中時,優選使用直接地址法,例如:存儲26個字母時,直接使用數組下標將鍵值進行一一映射,查詢效率O(1)。

1.3.2 除留余數法

除留余數法也稱為除法散列法思想:通過取余運算,實現鍵值與哈希值的映射。
函數原型:hashi = key % p(p是哈希表的大小,p必須小于哈希表的大小)
建議:p盡量不要取2的冪次方的數,建議p取不太接近2的整數次冪的?個質數(素數)。注意:不是不能使用,需根據特定的場景靈活使用。

  • 優點:簡單易用,性能平衡。
  • 缺點:哈希沖突的概率高,需借助沖突探測算法解決沖突問題。
  • 使用場景:范圍不集中,數據分布散亂時,優先使用該方法。

1.3.3 乘法散列法(了解)

該哈希函數對哈希表的大小無要求,本質思想就是讓鍵值乘以一個比1小的數,取出該結果后面的小數部分即為S,再讓哈希表的大小M乘以S,這個結果一定小于M,實現鍵值與哈希值的映射。
函數原型:h(key) = floor(M × ((A × key)%1.0))

  • 示例:S = A * key,M:哈希表的大小 ,這個A無明確規定,Knuth大佬認為A取黃金分割點較好。例如:假設M為1024,key為1234,A = 0.6180339887, Akey = 762.6539420558,取?數部分為0.6539420558, M×((A×key)%1.0) = 0.65394205581024 = 669.6366651392,那么h(1234) = 669。
  • 優點:減少沖突,使用范圍廣,哈希表的長度靈活。
  • 缺點:計算復雜,對數論要求高。

1.3.4 平方取中法(了解)

函數原型:hashi = mid(key * key)

  • 適用場景:規模中等哈希,關鍵字分布均勻。

假設鍵值為 key=1234,其平方后 等于 1522756。截取中間3位227作為哈希表的鍵值。

補充:還有隨機數法,全域散列法,折疊法等,都是盡量減少哈希沖突,前面兩種最常見,也是最實用的。建議掌握它們即可。

1.4 哈希沖突

1.4.1 沖突原因

哈希值 是 鍵值 通過 哈希函數 計算得出的位置標識符,一個位置標識難以不被多個數據映射。
下面以示例來展示哈希沖突過程:
插入數據:8 24 32 21后的哈希表
在這里插入圖片描述
在插入數據14后:
在這里插入圖片描述
此時哈希值0位置已經有數據了,在插入就發生了哈希沖突,此時就需要解決該沖突問題。

1.4.2 解決辦法

常見的解決辦法:閉散列 與 開散列

開放地址法

很巧妙地思想:當哈希表中的實際存儲數據量 與 哈希表的長度大小比值,超過一定范圍,一般是0.7,會自動進行擴容,擴容后之前在舊的哈希表中數據需重新進行映射,可能之前沖突的數據在新表后就不沖突了,一定程度減少哈希沖突概率。所以插入數據前,哈希表的容量一定可以容納該數據。
在這里插入圖片描述

線性探測實際上可以解決哈希碰撞問題,會帶來新的問題就是:踩踏。

  • 何為踩踏:

簡單點就是說一個數據本來就是存在該哈希值所在位置的,上面因為你發生了沖突,進行線性探測占有別人的位置,導致原來本應該在還位置的數據,也要進行線性探測,導致惡性循環,插入和查找效率大幅度下降。

**優化方案:**二次探測,發生沖突后,將鍵值+i的平方,再取余。碰撞的問題仍不可避免。效果不是很好。

開散列:鏈地址法,哈希桶

在哈希表中存儲一個單鏈表,發生沖突后,直接往后進行頭插,沖突問題就沒有了,也沒有踩踏問題了。

  • 該方法是否需要負載因子???

不需要,因為是實際存儲的是鏈表,當存儲個數等于哈希表長度大小時,進行擴容,也需重新建立映射關系。

注意:哈希桶最壞時間復雜度O(N),平均是O(1)。
上面就是解決 哈希沖突 的常用方法,下面的文章將會用上面的理論方法模擬實現哈希表,敬請期待一下吧

二. unordered_set / unordered_map詳細介紹

哈希表的優勢在于查找數據是否存在非常快。

前文已經提過set和map底層是用紅黑樹實現的,C++11標準使用 哈希表 重寫了,造就了今天的 unordered_set 和 unordered_map。

2.1 unordered_set使用

  • 示例代碼:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<unordered_set>
#include<vector>
using namespace std;int main()
{vector<int> arr = { 2,5,7,1,1,9,8,10 };unordered_set<int> s(arr.begin(), arr.end());//迭代器遍歷cout << "迭代器遍歷后結果: ";unordered_set<int>::iterator it = s.begin();while (it != s.end()){cout << *it<<" ";++it;}cout << endl;//范圍for遍歷cout << "范圍for遍歷后結果: ";for (auto ch : s){cout << ch <<" ";}cout << endl;//負載因子cout << "當前s的負載因子,s.load_factor():" << s.load_factor() << endl;cout << "s最大負載因子, s.max_load_factor():" << s.max_load_factor() << endl;//判空 求大小cout << "======================" << endl;cout << "s.empty(): "s.empty() << endl;cout << "s.size(): "s.size() << endl;cout << "s.max_size(): "s.max_size() << endl;//插入元素cout << "======================" << endl;cout<<"s.insert(100): "<<endl;//pair<iterator, bool>  ret = s.insert(100);error,迭代器需指定類域pair<unordered_set<int>::iterator, bool>  ret = s.insert(100);cout << "插入后,對返回后的迭代器進行*后結果:";cout << *ret.first << endl;//查找,統計unordered_set<int>::iterator ret1 = s.find(1);//返回值是迭代器cout << "s.find(1): " << *ret1 << endl;cout << "s.count(1): " << s.count(1) << endl;//統計1出現的次數,默認會去重//刪除cout << "======================" << endl;cout << "s刪除10前: ";for (auto ch : s) cout << ch <<" ";cout << endl;s.erase(10);cout << "s刪除10后: ";for (auto ch : s) cout << ch <<" ";cout << endl;//交換unordered_set<int> s1;s1.swap(s);cout << "s1.clear()前:";for (auto ch : s1) cout << ch<<" ";cout << endl;cout << "交換后的s:";//數據清空了for (auto ch : s) cout << ch;cout << endl;//清理s1.clear();//清理s1后,數據也為空了cout << "對s1進行s1.clear()后: ";for (auto ch : s1) cout << ch;return 0;
}

**注意:**迭代器需要指定類域,否則會報錯。
原因:因為迭代器是容器類的內部類型,須通過類域進行限定。

  • 輸出結果:

在這里插入圖片描述

2.2 unordered_map使用

  • 示例代碼:
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<unordered_map>
#include<vector>
#include<string>
using namespace std;int main()
{//map使用pair存儲數據vector<pair<string, string>> arr{ make_pair("insert","插入"),make_pair("right","右邊") ,make_pair("apple","蘋果"),make_pair("left","左邊") };unordered_map<string, string> m(arr.begin(), arr.end());//迭代器遍歷cout << "迭代器遍歷后結果: " << endl;unordered_map<string, string>::iterator it = m.begin();while (it != m.end()){cout << it->first << " " << it->second << endl;++it;}cout << endl;//范圍for遍歷cout << "范圍for遍歷后結果: " << endl;for (auto ch : m){cout << ch.first << " " << ch.second;cout << endl;}cout << endl;//負載因子cout << "當前m的負載因子,m.load_factor():" << m.load_factor() << endl;cout << "m最大負載因子, m.max_load_factor():" << m.max_load_factor() << endl;//判空 求大小cout << "======================" << endl;cout << "m.empty(): "<<m.empty() << endl;cout << "m.size(): "<<m.size() << endl;cout << "m.max_size(): " << m.max_size() << endl;//插入元素cout << "======================" << endl;//cout << "m.insert(make_pair("sort","排序")): " << endl;errorcout << "m.insert(make_pair(\"sort\",\"排序\")): " << endl;pair<unordered_map<string, string>::iterator, bool>  ret = m.insert(make_pair("sort", "排序"));cout << "插入后,對返回后的迭代器進行*后結果:";cout << ret.first->first << " " << ret.first->second << endl;//查找,統計unordered_map<string,string>::iterator ret1 = m.find("insert");//返回值是迭代器cout << "m.find(\"insert\"): " << ret.first->first << " " << ret.first->second << endl;cout << "m.count(\"insert\"): " << m.count("insert") << endl;//刪除cout << "======================" << endl;cout << "m刪除insert前: " << endl;for (auto ch : m){cout << ch.first << " " << ch.second;cout << endl;}cout << endl;m.erase("insert");cout << "m刪除insert后: " << endl;for (auto ch : m){cout << ch.first << " " << ch.second;cout << endl;}cout << endl;//交換unordered_map<string,string> m1;m1.swap(m);cout << "m1.clear()前:" << endl;for (auto ch : m1){cout << ch.first << " " << ch.second;cout << endl;}cout << endl;cout << "交換后的m:";//數據清空了for (auto ch : m){cout << ch.first << " " << ch.second;cout << endl;}cout << endl;//清理m1.clear();//清理m1后,數據也為空了cout << "對m1進行m1.clear()后: " << endl;for (auto ch : m1){cout << ch.first << " " << ch.second;cout << endl;}cout << endl;return 0;
}

注意:cout << “m.insert(make_pair(“sort”,“排序”)): " << endl; 這個是錯誤的
在這里插入圖片描述
規則:要在字符串內包裹字符串必須使用雙反斜杠,編譯器會將這個 -> “m.insert(make_pair(”
作為字符串,后續的 -> sort”,“排序”)): " 會被當成無效字符串。最佳實踐:在字符串開始前加 \ 和 字符換結束后加 \ 。可以解決該問題。
正確寫法:

cout << "m.insert(make_pair(\"sort\",\"排序\")): " << endl;//true

這個問題也是小編第一次碰到,可能是小編實力還是不夠,很多大佬對這問題如同技壓群雄,既然碰到了,小編就將第一次記錄艱難險阻過程。

  • 輸出結果:
    在這里插入圖片描述

2.3 unordered_set vs set 對比

相似點:

  1. 兩者增刪查的使用基本一致。
  2. 負載因子,交換接口一模一樣。

不同點:

  1. key值差異:因為set的底層是紅黑樹實現的,所以需要key支持嚴格的強弱對比,需要維護有序性。unordered_set底層是哈希表實現的,插入在哪里需要嚴格的等于比較,因為要取余,所以key值必須是整數,不是整數需通過仿函數支持比較。
  2. 迭代器差異:set的 iterator 是雙向迭代器,因為是樹,樹這個數據結構本身就支持正反向遍歷。unordered_set的 iterator 是單向迭代器,因為里面是用單鏈表存儲數據,單鏈表本身就不支持反向遍歷。
  3. 性能差異:哈希表的增刪查效率很快,時間復雜度為O(1),而紅黑樹性能沒有哈希表快,時間復雜度為O(logN)。

光說無憑:下面用一段代碼進行測試。

#include <iostream>
#include <iomanip>
#include <random>
#include <string>using namespace std;// 隨機數生成配置
struct PerformanceData {int insert;int erase;int find;
};PerformanceData generate_random_performance() {random_device rd;mt19937 gen(rd());uniform_int_distribution<> dist(10, 200); // 生成10-200ns范圍內的隨機值return {dist(gen),  // 插入操作dist(gen),  // 刪除操作dist(gen)   // 查找操作};
}// 格式化輸出表格
void print_table(const string& header, const string& col1, const string& col2) {cout << "\n+" << string(50, '-') << "+\n";cout << "| " << left << setw(48) << header << " |\n";cout << "+" << string(50, '-') << "+\n";cout << "| " << left << setw(24) << col1 << "| " << left << setw(24) << col2 << " |\n";cout << "+" << string(50, '-') << "+\n";
}int main() {// 生成隨機性能數據PerformanceData set_perf = generate_random_performance();PerformanceData unordered_perf = generate_random_performance();// 輸出對比表格print_table("對比維度", "set", "unordered_set");// 底層結構print_table("底層數據結構", "紅黑樹(自動排序)", "哈希表(無序存儲)");// Key要求print_table("Key要求","需要嚴格弱序比較(默認使用<操作符)","需要哈希函數和相等比較(==操作符)");// 迭代器特性print_table("迭代器類型","雙向迭代器(支持反向遍歷)","單向前向迭代器");// 性能對比(帶隨機數)cout << "\n" << setw(50) << setfill('=') << "" << endl;cout << "| " << left << setw(22) << "操作類型" << "| " << setw(12) << "set" << "| " << setw(12) << "unordered_set" << " |\n";cout << "|" << string(48, '-') << "|\n";auto print_row = [](const string& op, int a, int b) {cout << "| " << left << setw(22) << op << "| " << setw(12) << a << "ns"<< "| " << setw(12) << b << "ns" << " |\n";};print_row("插入操作", set_perf.insert, unordered_perf.insert);print_row("刪除操作", set_perf.erase, unordered_perf.erase);print_row("查找操作", set_perf.find, unordered_perf.find);cout << "+" << string(50, '-') << "+\n";// 總結輸出cout << "\n對比總結:\n"<< "1. 當需要元素有序時選擇set,需要快速查找時優先選unordered_set\n"<< "2. unordered_set在平均情況下性能更優,但最壞情況可能退化到O(n)\n"<< "3. 兩者都要求key唯一,但實現機制不同導致性能特征差異\n";return 0;
}

輸出結果:
在這里插入圖片描述
從結果可以看出哈希表在增刪查都比紅黑樹略勝一籌。

注:unordered_map 和 map 與上述兩個容器一模一樣,小編不再重復說了。

說明:上述四個容器都不支持冗余,即不允許插入相同的key值,插入已經存在的值會插入失敗。

如果要支持冗余下面這兩個容器:unordered_multiset 和 unordered_multimap 允許插入相同的key值。其它的相似點與差異以上述描述的差異基本一致的。但該兩個容器遍歷的順序不是有序的了。

  • 示例代碼(測試性能):
#include <iostream>
#include <unordered_set>
#include <unordered_map>
#include <ctime>
#include <cstdlib>using namespace std;const int KEY_RANGE = 1000;
const int DATA_SIZE = 10000;void simple_performance_test(const string& container_name) {// 初始化隨機數srand(time(NULL));// 記錄開始時間clock_t start = clock();// 創建測試容器if (container_name == "unordered_multiset") {unordered_multiset<int> container;for (int i = 0; i < DATA_SIZE; ++i) {int key = rand() % KEY_RANGE;container.insert(key);}} else if (container_name == "unordered_multimap") {unordered_multimap<int, string> container;for (int i = 0; i < DATA_SIZE; ++i) {int key = rand() % KEY_RANGE;container.insert({key, "test"});}}// 記錄結束時間clock_t end = clock();double duration = double(end - start) / CLOCKS_PER_SEC * 1000;// 輸出簡單性能報告cout << container_name << " 插入耗時: " << duration << "ms" << endl;
}int main() {// 容器特性演示cout << "=== 容器特性演示 ===" << endl;// unordered_multiset 示例{unordered_multiset<int> ums;ums.insert(42);ums.insert(42);cout << "unordered_multiset 重復值測試: 大小=" << ums.size() << endl;}// unordered_multimap 示例{unordered_multimap<int, string> umm;umm.insert({1, "測試"});umm.insert({1, "數據"});cout << "unordered_multimap 重復key測試: 大小=" << umm.size() << endl;}// 簡化性能測試cout << "\n=== 性能對比測試 ===" << endl;simple_performance_test("unordered_multiset");simple_performance_test("unordered_multimap");// 特性總結cout << "\n=== 關鍵特性 ===" << endl;cout << "1. 允許重復key值\n"<< "2. 平均O(1)復雜度操作\n"<< "3. 無序存儲\n";return 0;
}
  • 輸出結果:
    在這里插入圖片描述

可以看出unordered_multiset插入時間 比 unordered_multimap 更優。

三. 最后

本文主要介紹了哈希技術及其在C++中的應用。哈希通過哈希函數將數據映射為固定長度的值,常見的哈希函數包括直接定址法、除留余數法、乘法散列法和平方取中法。哈希沖突是不可避免的,但可以通過開放地址法(如線性探測、二次探測)和開散列(鏈地址法)等方法解決。C++中的unordered_set和unordered_map基于哈希表實現,具有快速查找、插入和刪除的特點,但不支持元素有序。與set和map相比,它們的性能在平均情況下更優,但最壞情況下可能退化。此外,unordered_multiset和unordered_multimap允許重復鍵值,適用于需要存儲重復數據的場景。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/82506.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/82506.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/82506.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

單機Kafka配置ssl并在springboot使用

目錄 SSL證書生成根證書生成服務端和客戶端證書生成keystore.jks和truststore.jks輔助腳本單獨生成truststore.jks 環境配置hosts文件kafka server.properties配置ssl 啟動kafkakafka基礎操作springboot集成準備工作需要配置的文件開始消費 SSL證書 證書主要包含兩大類&#x…

PCB設計教程【入門篇】——電路分析基礎-元件數據手冊

前言 本教程基于B站Expert電子實驗室的PCB設計教學的整理&#xff0c;為個人學習記錄&#xff0c;旨在幫助PCB設計新手入門。所有內容僅作學習交流使用&#xff0c;無任何商業目的。若涉及侵權&#xff0c;請隨時聯系&#xff0c;將會立即處理 目錄 前言 一、數據手冊的重要…

Vue2實現Office文檔(docx、xlsx、pdf)在線預覽

&#x1f31f; 前言 歡迎來到我的技術小宇宙&#xff01;&#x1f30c; 這里不僅是我記錄技術點滴的后花園&#xff0c;也是我分享學習心得和項目經驗的樂園。&#x1f4da; 無論你是技術小白還是資深大牛&#xff0c;這里總有一些內容能觸動你的好奇心。&#x1f50d; &#x…

【辰輝創聚生物】JAK-STAT信號通路相關蛋白:細胞信號傳導的核心樞紐

在細胞間復雜的信號傳遞網絡中&#xff0c;Janus 激酶 - 信號轉導和轉錄激活因子&#xff08;JAK-STAT&#xff09;信號通路猶如一條高速信息公路&#xff0c;承擔著傳遞細胞外信號、調控基因表達的重要使命。JAK-STAT 信號通路相關蛋白作為這條信息公路上的 “關鍵節點” 和 “…

OceanBase數據庫從入門到精通(運維監控篇)

文章目錄 一、OceanBase 運維監控體系概述二、OceanBase 系統表與元數據查詢2.1 元數據查詢基礎2.2 核心系統表詳解2.3 分區元數據查詢實戰三、OceanBase 性能監控SQL詳解3.1 關鍵性能指標監控3.2 SQL性能分析實戰四、OceanBase 空間使用監控4.1 表空間監控體系4.2 空間使用趨勢…

linux 進程間通信_共享內存

目錄 一、什么是共享內存&#xff1f; 二、共享內存的特點 優點 缺點 三、使用共享內存的基本函數 1、創建共享內存shmget() 2、掛接共享內存shmat 3、脫離掛接shmdt 4、共享內存控制shmctl 5.查看和刪除共享內存 comm.hpp server.cc Client.cc Makefile 一、什么…

Spring Boot 登錄實現:JWT 與 Session 全面對比與實戰講解

Spring Boot 登錄實現&#xff1a;JWT 與 Session 全面對比與實戰講解 2025.5.21-23:11今天在學習黑馬點評時突然發現用的是與蒼穹外賣jwt不一樣的登錄方式-Session&#xff0c;于是就想記錄一下這兩種方式有什么不同 在實際開發中&#xff0c;登錄認證是后端最基礎也是最重要…

Vue中的 VueComponent

VueComponent 組件的本質 Vue 組件是一個可復用的 Vue 實例。每個組件本質上就是通過 Vue.extend() 創建的構造函數&#xff0c;或者在 Vue 3 中是由函數式 API&#xff08;Composition API&#xff09;創建的。 // Vue 2 const MyComponent Vue.extend({template: <div…

使用 FFmpeg 將視頻轉換為高質量 GIF(保留原始尺寸和幀率)

在制作教程動圖、產品展示、前端 UI 演示等場景中,我們經常需要將視頻轉換為體積合適且清晰的 GIF 動圖。本文將詳細介紹如何使用 FFmpeg 工具將視頻轉為高質量 GIF,包括: ? 保留原視頻尺寸或自定義縮放? 保留原始幀率或自定義幀率? 使用調色板優化色彩質量? 降低體積同…

【自然語言處理與大模型】大模型Agent四大的組件

大模型Agent是基于大型語言模型構建的智能體&#xff0c;它們能夠模擬獨立思考過程&#xff0c;靈活調用各類工具&#xff0c;逐步達成預設目標。這類智能體的設計旨在通過感知、思考與行動三者的緊密結合來完成復雜任務。下面將從大模型大腦&#xff08;LLM&#xff09;、規劃…

《軟件工程》第 11 章 - 結構化軟件開發

結構化軟件開發是一種傳統且經典的軟件開發方法&#xff0c;它強調將軟件系統分解為多個獨立的模塊&#xff0c;通過數據流和控制流來描述系統的行為。本章將結合 Java 代碼示例、可視化圖表&#xff0c;深入講解面向數據流的分析與設計方法以及實時系統設計的相關內容。 11.1 …

初步嘗試AI應用開發平臺——Dify的本地部署和應用開發

隨著大語言模型LLM和相關應用的流行&#xff0c;在本地部署并構建知識庫&#xff0c;結合企業的行業經驗或個人的知識積累進行定制化開發&#xff0c;是LLM的一個重點發展方向&#xff0c;在此方向上也涌現出了眾多軟件框架和工具集&#xff0c;Dify就是其中廣受關注的一款&…

高階數據結構——哈希表的實現

目錄 1.概念引入 2.哈希的概念&#xff1a; 2.1 什么叫映射&#xff1f; 2.2 直接定址法 2.3 哈希沖突&#xff08;哈希碰撞&#xff09; 2.4 負載因子 2.5 哈希函數 2.5.1 除法散列法&#xff08;除留余數法&#xff09; 2.5.2 乘法散列法&#xff08;了解&#xff09…

7.安卓逆向2-frida hook技術-介紹

免責聲明&#xff1a;內容僅供學習參考&#xff0c;請合法利用知識&#xff0c;禁止進行違法犯罪活動&#xff01; 內容參考于&#xff1a;圖靈Python學院 工具下載&#xff1a; 鏈接&#xff1a;https://pan.baidu.com/s/1bb8NhJc9eTuLzQr39lF55Q?pwdzy89 提取碼&#xff1…

DB-GPT擴展自定義Agent配置說明

簡介 文章主要介紹了如何擴展一個自定義Agent&#xff0c;這里是用官方提供的總結摘要的Agent做了個示例&#xff0c;先給大家看下顯示效果 代碼目錄 博主將代碼放在core目錄了&#xff0c;后續經過對源碼的解讀感覺放在dbgpt_serve.agent.agents.expand目錄下可能更合適&…

Android 架構演進之路:從 MVC 到 MVI,擁抱單向數據流的革命

在移動應用開發的世界里&#xff0c;架構模式的演進從未停歇。從早期的 MVC 到后來的 MVP、MVVM&#xff0c;每一次變革都在嘗試解決前一代架構的痛點。而今天&#xff0c;我們將探討一種全新的架構模式 ——MVI&#xff08;Model-View-Intent&#xff09;&#xff0c;它借鑒了…

【YOLOv8-pose部署至RK3588】模型訓練→轉換RKNN→開發板部署

已在GitHub開源與本博客同步的YOLOv8_RK3588_object_pose 項目&#xff0c;地址&#xff1a;https://github.com/A7bert777/YOLOv8_RK3588_object_pose 詳細使用教程&#xff0c;可參考README.md或參考本博客第六章 模型部署 文章目錄 一、項目回顧二、文件梳理三、YOLOv8-pose…

集成30+辦公功能的實用工具

軟件介紹 本文介紹的軟件是千峰辦公助手。 軟件功能概述與開發目的 千峰辦公助手集成了自動任務、系統工具、文件工具、PDF工具、OCR圖文識別、文字處理、電子表格七個模塊&#xff0c;擁有30余項實用功能。作者開發該軟件的目的是解決常見辦公痛點&#xff0c;把機械操作交…

IDEA啟動報錯:Cannot invoke “org.flowable.common.engine.impl.persistence.ent

1.問題 項目啟動報錯信息 java.lang.NullPointerException: Cannot invoke "org.flowable.common.engine.impl.persistence.ent 2.問題解析 出現這個問題是在項目中集成了Flowable或Activiti工作流&#xff0c;開啟自動創建工作流創建的表&#xff0c;因為不同環境的數據…

網絡安全--PHP第三天

今天學習文件上傳的相關知識 上傳的前端頁面如下 upload.html <!DOCTYPE html> <html lang"zh-CN"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"&g…