【Linux庖丁解牛】— system V共享內存!

1. 什么是system V

System V IPC(Interprocess Communication,進程間通信)是Unix系統中一種經典的進程間通信機制,由AT&T在System V.2版本中引入,并廣泛應用于Linux等現代操作系統中。它通過三種核心機制實現進程間的同步、數據傳遞和資源共享。

在Linux中支持了這種標準,專門設計了IPC通信模塊。

2. 共享內存原理

這里我們先來宏觀的認識一下什么是共享內存。假設有倆個進程ab,如果它們需要進行進程間通信,除了我們之前說的命名管道,還可以通過共享內存的方式實現。我們之前再談動態庫加載原理的時候說過進程的地址空間的堆棧之間有一塊共享區,用來記錄動態庫的虛擬地址。其實,在這塊空間中,還用一片區域->共享內存區。進程ab在物理內存中申請同一塊區域,然后各自通過頁表映射建立物理內存和共享內存區之間的關系。至此,兩個進程就可以看到同一份資源了!以上就是我們對共享內存的宏觀認識,還有許多細節沒有說。

> 我們剛剛所說的所有工作都涉及內核數據結構和磁盤,這些工作都由操作系統完成,我們使用相應的系統調用完成上面的工作。

> 在實際情況中,可能有多組進程在進行進程間通信,那勢必有多個共享內存,有的是被創建,有的正在打開,有的正在關閉……所以,在內核中勢必也會有描述共享內存的結構體對象,也勢必會有管理這些共享內存的內核數據結構!

3. shmget[share memory get]

shmget是我們用來獲取共享內存的接口:

> 參數size是用來設置我們創建共享內存的大小的,很好理解。

> 第三個參數是共享內存標記位, 有兩個選項:IPC_CREAT、IPC_EXCL。

IPC_CREAT:只帶這一個選項表示,如果目標共享內存不存在則創建并打開共享內存,否則就直接打開已近存在的目標共享內存。

IPC_EXCL:該選項單獨使用無任何意義,必須和IPC_CREAT一起使用,使用是表示如果目標內存不存在則創建并打開該共享內存,如果存在,則會直接報錯!從解釋上來看該選項是保證我們創建一個全新的共享內存。

> 不過,這里還有許多問題:我們怎么知道一個共享內存到底是否存在,并且如何保證兩個進程打開同一個共享內存呢?

> 這就由第一個參數key來標識共享內存的唯一性了!這個key不是由內核直接生成,而是讓用戶來構建并傳入給操作系統的。這是為什么呢???

> 假如,進程a在內存中創建了一個共享內存區,內核直接生成對應放id給管理該區域的結構體對象。進程b需要和進程a進行進程間通信,那么進程b就需要拿到這個id來找到同一塊共享內存但是,id也是數據,如果進程a可以把id給到進程b,它們不就已經可以進程間通信了嗎!!!所以,由內核自己生成是做不到共享內存的!

> 但是,如果這兩個進程其中一方在創建共享內存時就做好約定,在用戶層規定一個key,傳給操作系統,讓這個key來唯一標記這個共享內存。有朝一日,另一個進程就可以通過這個key找到目標共享內存來完成進程間通信!

> 其實,這個key原理不就是我們用路徑和管道文件名找同一個管道文件一樣的嗎。

> 好了,明白了上面的原理,我們就來說說key如何給定。理論上來說,這個key我們可以隨便給,但是,為了減少key之間的沖突,系統給了我們一個生成key的接口ftok()

這個接口會根據用戶傳入的字符串和id整合形成一個key并返回給用戶,正常情況下,我們也是使用這個接口來生成key。?這里有一個問題:為什么操作系統不這樣設置呢->提供一個系統調用,在內核遍歷已有的key,生成一個不存在的key返回給用戶,這樣做不是更好嗎?但是,如果其他用戶同時生成key,這些key會不會一樣呢?

> 最后,我們再來說一下shmget的返回值

?如果一個共享內存被創建成功,會返回一個整形來唯一標記這個共享內存。這個整形是給用戶看到的,而key則是給操作系統來尋找共享內存的。這就好比我們之前學文件時,打開一個文件時,系統通過file*的指針找到文件,卻返回一個整形文件描述符fd給用戶使用。

4. 共享內存demon代碼編寫

4.1 預備代碼的編寫過程

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstdio>
#include <fcntl.h>
#include <unistd.h>
#include <string>
#include <sys/ipc.h>
#include <sys/shm.h>#define ERR_EXIT(m)         \do                      \{                       \perror(m);          \exit(EXIT_FAILURE); \} while (0)const std::string gpathname = ".";
int gproj_id = 0x66;
int g_size = 1024;
int g_default_id=-1;// 共享內存
class shm
{
public:shm() : _shm_id(g_default_id), _size(g_size) {}// 創建共享內存void create_shm(){// 獲取keykey_t k = ftok(gpathname.c_str(), gproj_id);if (k < 0){// 獲取key失敗ERR_EXIT("ftok");}printf("key:0x%0x\n", k);// 創建共享內存_shm_id = shmget(k, _size, IPC_CREAT | IPC_EXCL);if (_shm_id < 0){// 創建共享內存失敗ERR_EXIT("shmget");}printf("_shm_id:%d\n", _shm_id);}~shm() {}private:int _shm_id;int _size;
};

./server運行之后,我們果然創建共享內存成功了,也看到了相應的key和id。?

不過,當我們結束進程,再次運行時,卻發現共享內存創建失敗了。這說明之前的共享內存并沒有隨進程的結束而釋放,事實上,共享內存的生命周期是隨系統的,我們關閉系統重啟之后,共享內存便釋放了。

我們可以用命令:ipcs? -m 來查看系統內的共享內存情況。【ipcs即查看系統進程間通信資源,-m則標識查看共享內存部分】。

如果我們想要刪除一個共享內存資源,可用命令ipsrm -m +shmid?

接下來,我們來看看代碼級別怎么刪除共享內存資源的,這里我們再認識一個接口shmctl:?

該系統調用是用來控制共享內存資源的,包括刪除,查看共享內存資源屬性等。第二個參數用來控制我們想要對共享內存做和控制,而第三個參數我們暫時用不到,直接設為nullptr即可。

要想釋放目標共享內存,我們僅需將第二個參數設為IPC_RMID即可。好了,下面我們就來實現destroy接口。

// 釋放共享內存資源void destroy(){if (_shm_id == g_default_id){// 沒有創建共享內存資源,無需釋放return;}int n = shmctl(_shm_id, IPC_RMID, nullptr);if (n < 0){ERR_EXIT("shmctl");}printf("共享內存釋放成功! shmid->%d\n", _shm_id);}

?有了上面的鋪墊,我們就可以來學習如何使用共享內存來完成進程間通信了。首先,我們需要將我們創建的共享內存和我們的進程關聯【本質就是讓物理地址和進程虛擬地址空間完成映射】。這由系統完成,所以由對應的系統調用幫我們完成工作->shmat【at->attach】:

第一個參數很好理解,第二個參數用來設置固定虛擬地址映射,在我們應用層開發一般不需要關心這個參數,設為nullptr即可,第三個參數是用來標識我們的共享內存資源的權限的,我們一般設置為0,則系統會使用缺省權限->只讀和只寫。最后,如果成功完成內存和進程地址空間的關聯,則該接口會返回系統選定虛擬地址映射共享內存起始虛擬地址,這個返回值和我們使用的malloc返回值是一樣的,使用上也沒什么區別,我們這樣就很好理解了。

好了,接下來我們就設計一個接口來完成共享內存和進程的關聯:

//進程和共享內存完成關聯void attach(){_start_addr=shmat(_shm_id,nullptr,0);if((long long)_start_addr<0){ERR_EXIT("shmat");}printf("進程關聯成功!_start_addr->%p\n",_start_addr);}

誒?為什么我們在關聯共享內存時報錯了呢,這個報錯說明我們的行為被決絕了。其實,共享內存和文件在某些方面有些類似。共享內存也有對應的讀寫執權限,所以我們在創建共享內存時,需要設定相應的權限,做法也是和文件權限一樣。

?執行結果:

現在,服務端創建對應的共享內存并完成地址空間的映射。那么,客戶端也需要相應的接口獲取同一份共享內存并進行地址空間的映射。創建和刪除共享內存的工作在客戶端也就不需要做了!

具體做法如下圖所示:

我們做完以上工作之后,就可以開始進程間通信了。做法非常簡單,將我們返回的虛擬地址用指針指向,把共享內存當做長字符串來使用就可以了。

4.2 共享內存優缺點?

最后,我們再來總結一下:我們發現在使用共享內存實現進程間通信的讀寫的時候,并沒有使用系統調用,而我們在使用管道的時候,切切實實的使用了系統調用。很好理解,因為共享內存的讀寫是在進程的虛擬地址空間上面操作的【這屬于用戶層】,所以共享區屬于用戶層可以讓用戶直接使用,而管道讀寫則是在內核文件緩沖區完成的,這屬于系統層。

共享區也是因此有以下的優點:
> 共享區是進程間通信中,速度最快的方式:映射之后,內容直接被雙方進程看到,不需要進行系統調用來獲取和寫入。

不過,共享內存機制的優點也是犧牲了很多換來的:第一點就是共享內存沒有像管道那樣的同步機制服務端開啟后就一直讀,如果客戶端不開啟呢??而管道文件一方開始讀后,如果另一方沒有寫,則讀取放就會同步阻塞并不開始讀取,知道另一方寫入。另一點就是共享內存沒有保護機制,這里的保護不是指對共享內存的保護,而是對讀寫數據的保護。如果寫入端對自己寫入的內容在讀取時有特定的讀取要求【比如一次讀多少字節,一次讀一整句話等等】,目前的共享內存就無能為力了,因為讀取方不停的讀,就無所謂讀取格式的要求了,它也正因此是進程間通信中最快的方式。

那么,我們目前有沒有什么方案可以將我們的數據保護起來呢?當然有了,我們可以同時用管道把這兩個進程關聯起來。而這個管道的作用就是用來做服務端等待wait,客戶端喚醒wake假設客戶端自己寫入兩個字符后,服務端成對的讀取打印結果。那我在客戶端寫入成對字符后,通過管道寫入來喚醒【喚醒的方式隨便啦】服務端服務端通過是否收到管道信號來決定是否讀取共享內存中的數據。如此一來,我們就通過管道,講我們的共享內存的讀寫控制起來了!話不多說,直接上代碼!

客戶端和服務端之間的通信邏輯:

?結果如下:

4.3 共享內存去關聯?

上面,我們在釋放共享內存的時候是直接shmctl刪除的,但是我們再刪除之前還遺漏了一步,就是去關聯!在釋放共享內存之前,我們還應該將關聯該共享內存的所有進程去關聯。這里我們使用shmdt接口即可,使用也很簡單,傳入虛擬地址空間的其實地址即可。

?修改以下部分即可:

4.3 共享內存大小問題?

然后,我們再來說一下創建共享內存時的大小問題,實際上,共享內存的大小一定是4kb的整數倍的 。但是如果我們創建大小時,給定的不是4kb的整數被,那么系統則會向上取整開辟對應大小的共享內存空間。不過,我們用ipcs -m 命令查看到的大小還是我們自己定義的大小!

4.4 獲取共享內存屬性

在最后的最后,我們再來見一見共享內存是如何被系統管理起來的。

內核中,有一種結構體對象shmid_ds,該結構體對象記錄了共享內存的一些屬性,其中有一個結構體ipc_perm里面記錄了共享內存的key!

?我們使用shmctl傳入IPC_STAT參數即可查看到先關的屬性!

5. 源碼?

5.1 comm.hpp

#pragma once
#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <cstdio>
#include <fcntl.h>
#include <unistd.h>
#include <string>
#include <sys/ipc.h>
#include <sys/shm.h>#define FIFO_FILE "fifo"
#define FIFO_PATH "."const std::string gpathname = ".";
int gproj_id = 0x66;
int g_size = 4097;
int g_default_id = -1;
int gmode = 0666;#define USER "user"
#define CREATER "creater"#define ERR_EXIT(m)         \do                      \{                       \perror(m);          \exit(EXIT_FAILURE); \} while (0)

5.2 Shm.hpp

#include "comm.hpp"// 共享內存
class shm
{
private:// 創建共享內存void create_help(int flag){// 創建共享內存// _shm_id = shmget(k, _size, IPC_CREAT | IPC_EXCL | gmode);_shm_id = shmget(_key, _size, flag);if (_shm_id < 0){// 創建共享內存失敗ERR_EXIT("shmget");}printf("共享內存創建成功!_shm_id->%d\n", _shm_id);}// 創建共享內存void create_shm(){create_help(IPC_CREAT | IPC_EXCL | gmode);}// 獲取共享內存void get_shm(){create_help(IPC_CREAT);}// 釋放共享內存資源void destroy(){if (_shm_id == g_default_id){// 沒有創建共享內存資源,無需釋放return;}// 去關聯detach();if (_user_type == CREATER){int n = shmctl(_shm_id, IPC_RMID, nullptr);if (n < 0){ERR_EXIT("shmctl");}printf("共享內存釋放成功! shmid->%d\n", _shm_id);}}// 進程和共享內存去關聯void detach(){int n = shmdt(_start_addr);if (n < 0)ERR_EXIT("shmdt");printf("共享內存去關聯成功!\n");}// 進程和共享內存完成關聯void attach(){_start_addr = shmat(_shm_id, nullptr, 0);if ((long long)_start_addr < 0){ERR_EXIT("shmat");}printf("進程關聯成功!_start_addr->%p\n", _start_addr);}public:shm(const std::string &pathname, int projid, const std::string &user_type): _shm_id(g_default_id),_size(g_size),_user_type(user_type){// 獲取key_key = ftok(pathname.c_str(), projid);if (_key < 0){// 獲取key失敗ERR_EXIT("ftok");}printf("成功獲取key!key->0x%0x\n", _key);if (user_type == CREATER)create_shm();else if (user_type == USER)get_shm();else{}attach();}~shm(){if (_user_type == CREATER)destroy();}void *get_start_addr(){return _start_addr;}int get_size(){return _size;}// 獲取共享內存相關屬性void get_property(){struct shmid_ds tmp;int n = shmctl(_shm_id, IPC_STAT, &tmp);if (n < 0)ERR_EXIT("shmctl");printf("shm_segsz->%ld\n", tmp.shm_segsz);printf("key->0x%0x\n", tmp.shm_perm.__key);}private:key_t _key;int _shm_id;int _size;void *_start_addr;std::string _user_type;
};

5.3 name_fifo.hpp

#include "comm.hpp"// 命名管道類
class name_fifo
{
public:// 構造函數name_fifo(const std::string &path, const std::string &name): _path(path),_name(name){umask(0);_fifo_name = _path + "/" + _name;// 創建命名管道int n = mkfifo(_fifo_name.c_str(), 0666);if (n < 0){// 命名管道創建失敗// perror("命名管道創建失敗\n");ERR_EXIT("mkfifo");}else{std::cout << "管道創建成功!" << std::endl;}}// 析構函數~name_fifo(){// 刪除管道文件int n = unlink(_fifo_name.c_str());if (n < 0){// perror("刪除管道文件失敗!\n");ERR_EXIT("unlink");}else{std::cout << "管道刪除成功!" << std::endl;}}private:std::string _path;std::string _name;std::string _fifo_name;
};// 命名管道操作類
class fifo_opre
{
public:fifo_opre(const std::string &path, const std::string &name): _path(path),_name(name),_fd(-1){_fifo_name = _path + "/" + _name;}void open_for_read(){// 創建成功->服務端打開管道->讀取內容_fd = open(_fifo_name.c_str(), O_RDONLY);if (_fd < 0){// 命名管道打開失敗// perror("命名管道打開失敗\n");ERR_EXIT("open");}else{std::cout << "管道打開成功!" << std::endl;}}void open_for_write(){// 打開管道文件_fd = open(_fifo_name.c_str(), O_WRONLY);if (_fd < 0){// 命名管道打開失敗// perror("命名管道打開失敗\n");ERR_EXIT("open");}else{std::cout << "管道打開成功!" << std::endl;}}bool wait(){char c;int num = read(_fd, &c, 1);if (num > 0)return true;return false;}bool wake(){char c = 'a';int num = write(_fd, &c, 1);if (num > 0)return true;return false;}void Close(){if (_fd > 0)close(_fd);}~fifo_opre(){}private:std::string _path;std::string _name;std::string _fifo_name;int _fd;
};

5.4 server.cc

#include "comm.hpp"
#include "Shm.hpp"
#include "name_fifo.hpp"int main()
{// 服務端先創建關聯共享內存shm s(gpathname, gproj_id, CREATER);// // 創建命名管道// name_fifo nf(FIFO_PATH, FIFO_FILE);// char *mem = (char *)s.get_start_addr();// // 命名管道讀取端// fifo_opre fifo_reader(FIFO_PATH, FIFO_FILE);// fifo_reader.open_for_read();// // 服務端讀取// while (true)// {//     // 如果等待成功則讀取//     if (fifo_reader.wait())//     {//         printf("%s\n", mem);//     }//     else//         break;// }// // 關閉命名管道// fifo_reader.Close();s.get_property();return 0;
}

5.5 client.cc

#include "comm.hpp"
#include "Shm.hpp"
#include "name_fifo.hpp"int main()
{// 客戶端獲取共享內存shm s(gpathname, gproj_id, USER);char *mem = (char *)s.get_start_addr();// 客戶端打開管道寫入喚醒fifo_opre fifo_writer(FIFO_PATH, FIFO_FILE);fifo_writer.open_for_write();int index = 0;for (char c = 'A'; c <= 'Z'; index += 2, c++){sleep(1);mem[index] = c;mem[index + 1] = c;fifo_writer.wake();}return 0;
}

5.6 Makefile

.PHPNY:all
all:client server
client:client.ccg++ -o $@ $^ -std=c++11
server:server.ccg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm -f client server fifo

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

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

相關文章

從輸入到路徑:AI賦能的地圖語義解析與可視化探索之旅(2025空間智能全景)

??摘要??在空間智能爆發的2025年&#xff0c;地圖系統已從靜態導航工具進化為??實時決策中樞??。本文深度解析AI如何重構地理信息處理全鏈路&#xff1a;通過??多模態語義理解??&#xff08;文本/語音/圖像→空間意圖&#xff09;、??動態路網建模??&#xff0…

安全運維新趨勢:AI 驅動的自動化威脅檢測

在數字化浪潮中&#xff0c;網絡攻擊正從 “單點突破” 進化為 “鏈狀打擊”&#xff1a;2024 年某金融機構遭遇供應鏈攻擊&#xff0c;惡意代碼通過運維通道潛伏 3 個月&#xff0c;傳統規則引擎因未識別 “正常運維指令中的異常參數”&#xff0c;導致數據泄露損失過億。這背…

數據庫復合索引設計:為什么等值查詢列應該放在范圍查詢列前面?

前言作為后端開發工程師&#xff0c;我們經常會遇到數據庫查詢性能問題。在一次系統優化中&#xff0c;我發現一個簡單的索引順序調整竟然讓查詢速度提升了10倍&#xff01;這讓我意識到復合索引列順序的重要性。今天&#xff0c;我就來分享一下這個經驗&#xff0c;希望能幫助…

【PMP備考】每日一練 - 2

1、一個建筑項目的項目經理發現&#xff0c;他管理的項目所在地附近正在新建一條新的水管線。公司政策要求&#xff0c;在他的團隊繼續完成這個項目之前&#xff0c;必須先填寫一系列有關城市環境變化的表格。這是那兩種情況的例子&#xff1f;&#xff08;選2個選項&#xff0…

【三】ObservableCollection 與 List 的區別

文章目錄前言一、核心概念簡介ObservableCollectionList二、關鍵差異對比三、典型使用場景ObservableCollection 的適用場景List 的適用場景四、在Community Toolkit MVVM中使用ObservableCollection<Data>和List<Data>場景1&#xff1a;動態列表&#xff08;Obser…

網安-SSRF-pikachu

目錄 SSRF:Server-Side Request Forgery PHP curl PHP 可能引起SSRF的函數 PHP其他函數 CURL其他協議 SSRF利用&#xff1a; SSRF的發現 工具 SSRF的防御 pikachu-SSRF 一&#xff1a;curl 1.訪問連接&#xff1a; 2.讀取本地文件 3.dict協議掃描主機端口 二&…

在Centos系統上如何有效刪除文件和目錄的指令匯總

CentOS系統是一款開源的類Unix操作系統&#xff0c;極其親和程序員和技術人員。這個系統最大的優勢就是其高度自由化的特性&#xff0c;世界各地的開發者可以依照實際需求去修改和運行。在這個操作系統中&#xff0c;如果你想刪除文件和目錄&#xff0c;你可以使用各式各樣的命…

Spring(四) 關于AOP的源碼解析與思考

Spring&#xff08;四&#xff09; 關于AOP的源碼解析與思考 每種語言都有其獨特的機制和特點&#xff0c;那么說到Java你可能會首先想到反射&#xff0c;反射是Java語言提供的一種能夠在程序運行時動態操作類或對象的能力&#xff0c;比如獲取某個對象的類定義、獲取類聲明的屬…

Android 15 Settings 搜索框:引入關鍵字過濾功能

在日常使用 Android 手機時,我們經常會用到“設置”應用中的搜索功能來快速定位所需選項。然而,有時搜索結果可能會包含一些我們不希望看到或者過于寬泛的條目。 本文將深入探討這一變化,通過分析 SearchResultsAdapter.java 文件中的代碼修改,揭示 Android 如何實現對特定…

Python-魔術方法-創建、初始化與銷毀-hash-bool-可視化-運算符重載-容器和大小-可調用對象-上下文管理-反射-描述器-二分-學習筆記

序 欠4前年的一份筆記 &#xff0c;獻給今后的自己。 魔術方法 特殊屬性查看屬性如果dir&#xff08;lobji&#xff09;參數obj包含方法 __dir__()&#xff0c;該方法將被調用。如果參數obj不包含__dir__()&#xff0c; 該方法將最大限度地收集參數信息。 dir()對于不同類型的對…

redis的一些疑問

spring集成redisCacheEvict(value "commonCache", key "#uniqueid_userInfo")什么時候會執行緩存移除呢&#xff1f;如果方法執行異常是否移除&#xff1f;如果緩存不存在還會移除么&#xff1f;這個移除會在redis的執行歷史命令中監控到么&#xff1f;.…

3.檢查函數 if (!CheckStart()) return 的妙用 C#例子

在桌面/WPF 開發中&#xff0c;我們經常需要在按鈕事件里先判斷“能不能做”&#xff0c;再決定“怎么做”。如果校驗不過&#xff0c;就直接返回&#xff1b;校驗通過&#xff0c;才繼續執行業務邏輯。 今天分享一個極簡寫法&#xff1a;if (!CheckStart()) return;&#xff0…

炎熱工廠救援:算法打造安全壁壘

高溫天氣下智慧工廠&#xff1a;算法賦能&#xff0c;安全救援無憂背景&#xff1a;極端高溫下工廠的嚴峻挑戰近年來&#xff0c;極端高溫天氣頻發&#xff0c;部分地區氣溫接近甚至超過50℃。在這樣酷熱的環境中&#xff0c;工廠面臨著諸多嚴峻問題。一方面&#xff0c;高溫容…

pgsql模板是什么?

查找所有的數據庫 select datname from pg_database運行該命令后&#xff0c;我們會發現其中出現了一些其它的數據庫接下來&#xff0c;我們分析 template0 和 template1 的作用。template1 template1 是 PostgreSQL 默認用于創建新數據庫的模板。當執行 CREATE DATABASE new_d…

LLM 不知道答案,但是知道去調用工具獲取答案?

思考&#xff1a; LLM 自己“不知道”某個事實性問題的答案&#xff0c;但仍然能“知道”去調用工具獲取正確答案&#xff0c;這聽起來確實有點像個悖論該內容觸及了大型語言模型&#xff08;LLM&#xff09;的核心局限性以及&#xff08;Agents&#xff09;的智能所在。實際上…

2025年7月11日學習筆記一周歸納——模式識別與機器學習

2025年7月11日學習筆記&一周歸納——模式識別與機器學習一.一周工作二.我的一些筆記匯總三.發現的一些新的學習資料和愛用好物1.百度網盤AI筆記&#xff1a;2.b站資料&#xff1a;3.聽說的一些好書&#xff1a;一.一周工作 本周學習了清華大學張學工汪小我老師的模式識別與…

LeetCode 138題解 | 隨機鏈表的復制

隨機鏈表的復制一、題目鏈接二、題目三、分析四、代碼一、題目鏈接 138.隨機鏈表的復制 二、題目 三、分析 數據結構初階階段&#xff0c;為了控制隨機指針&#xff0c;我們將拷貝結點鏈接在原節點的后面解決&#xff0c;后面拷貝節點還得解下來鏈接&#xff0c;非常麻煩。這…

【計算機存儲架構】分布式存儲架構

引言&#xff1a;數據洪流時代的存儲革命“數據是新時代的石油” —— 但傳統存儲正成為制約數據價值釋放的瓶頸核心矛盾&#xff1a;全球數據量爆炸增長&#xff1a;IDC預測2025年全球數據量將達175ZB&#xff08;1ZB10億TB&#xff09;傳統存儲瓶頸&#xff1a;單機IOPS上限僅…

【Linux-云原生-筆記】數據庫操作基礎

一、什么是數據庫&#xff1f;數據庫就是一個有組織、可高效訪問、管理和更新的電子化信息&#xff08;數據&#xff09;集合庫。簡單來說&#xff0c;數據庫就是一個高級的Excel二、安裝數據庫并初始化1、安裝數據庫&#xff08;MySQL&#xff09;dnf search一下mysql數據庫的…

HarmonyOS中各種動畫的使用介紹

鴻蒙&#xff08;HarmonyOS&#xff09;提供了豐富的動畫能力&#xff0c;涵蓋屬性動畫、顯式動畫、轉場動畫、幀動畫等多種類型&#xff0c;適用于不同場景的交互需求。以下是鴻蒙中各類動畫的詳細解析及使用示例&#xff1a;1. 屬性動畫&#xff08;Property Animation&#…