iOS標準庫中經常使用數據結構和算法以內存池

上一篇:iOS標準庫中經常使用數據結構和算法之位串linux

⛲️內存池

內存池提供了內存的複用和持久的存儲功能。設想一個場景,當你分配了一塊大內存而且填寫了內容,可是你又不是常常去訪問這塊內存。這樣的內存利用率將不高,並且沒法複用。而若是是採用內存池則能夠很輕鬆解決這個問題:你只須要從內存池中申請這塊內存,設置完內容後當不須要用時你能夠將這塊內存放入內存池中,供其餘地方在申請時進行復用,而當你再次須要時則只須要從新申請便可。內存池提供了內存分配編號並且設置dirty標誌的概念,當你把分配的內存放入內存池並設置dirty標誌後,系統就會在適當的時候將這塊內存的內容寫回到磁盤,這樣當你再次根據內存編號來訪問內存時,系統就又會從磁盤中將內容讀取到內存中去。算法

功能:在iOS中提供了一套內存池管理的API,你能夠用這套API來實現上述的功能,並且系統內部不少功能也是藉助內存池來實現內存複用和磁盤存儲的。緩存

頭文件: #include <mpool.h>, #include <db.h>bash

平臺: BSD系統,linux系統cookie

1、內存池的建立、同步和關閉

功能:建立和關閉一個內存池對象並和磁盤文件綁定以便進行同步操做。數據結構

函數簽名app

//建立一個內存池對象
 MPOOL * mpool_open(void *key, int fd, pgno_t pagesize, pgno_t maxcache);

//將內存池中的髒數據同步寫回到磁盤文件中
int mpool_sync(MPOOL *mp);

//關閉和銷燬內存池對象。
int mpool_close(MPOOL *mp);

複製代碼

參數數據結構和算法

key:[in] 保留字段,暫時沒有用處,傳遞NULL便可。函數

fd:[in] 內存池關聯的磁盤文件句柄,文件句柄須要用open函數來打開。post

pagesize:[in] 內存池中每次申請和分配的內存的尺寸大小,單位是字節。

maxcache:[in] 內存池中內存頁的最大緩存數量。若是池中緩存的內存數量超過了最大緩存的數量就會複用已經存在的內存,而不是每次都分配新的內存。 return:[out] 返回一個新建立的內存池對象,其餘兩個函數成功返回0,失敗返回非0.

描述

  1. 內存池中的內存的分配和獲取是以頁爲單位進行的,每次分配的頁的尺寸大小由pagesize指定,同時內存池也指定了最大的緩存頁數量maxcache。每次從內存池中分配一頁內存時,除了會返回分配的內存地址外,還會返回這一頁內存的編號。這個編號對於內存池中的內存頁來講是惟一的。由於內存池中的內存是能夠被複用的,所以就有多是不一樣的編號的內存頁所獲得的內存地址是相同的。

  2. 每個內存池對象都會要和一個文件關聯起來,以便可以實現內存數據的永久存儲和內存的複用。文件句柄必須用open函數來打開,好比下面的例子:

int fd = open("/Users/apple/mpool", O_RDWR|O_APPEND|O_CREAT,0660);
複製代碼
  1. 當咱們不須要使用某個內存頁時或者內存頁的內容有改動則咱們須要將這個內存頁放入回內存池中,並將頁標誌爲dirty標誌。這樣系統就會在適當的時候將此內存頁的數據寫回到磁盤文件中,同時此內存頁也會在後續被重複利用。

  2. 當咱們想將全部設置爲dirty標誌的內存頁當即寫入磁盤時則須要調用mpool_sync函數進行同步處理。

  3. 當咱們再也不須要內存池時,則能夠經過mpool_close來關閉內存池對象,須要注意的是關閉內存池並不會將內存中的數據回寫到磁盤中去。

2、內存池中內存的獲取

功能: 從內存池中申請分配一頁新的內存或者獲取現有緩存中的內存。

函數簽名:

//從內存池中申請分配一頁新的內存
void *  mpool_new(MPOOL *mp, pgno_t *pgnoaddr);
//根據內存編號頁獲取對應的內存。
void * mpool_get(MPOOL *mp, pgno_t pgno, u_int flags);
複製代碼

參數:

mp:[in] 內存池對象。

pgnoaddr:[out] 用於mpool_new函數,用於保存新分配的內存頁編號。

pngno:[in] 用於mpool_get函數,指定要獲取的內存頁的編號。

flags:[in] 此參數暫時無用。

return:[out] 返回分配或者獲取的內存地址。若是分配或者獲取失敗則返回NULL。

描述:

  1. 不管是new仍是get每次從內存池裏面分配或者獲取的內存頁的大小都是由上述mpool_open函數中的pagesize參數指定的大小。
  2. 系統內部分配的內存是用calloc函數實現的,可是咱們不須要手動調用free來對內存進行釋放處理。
  3. 每一個內存頁都有一個惟一的頁編號,並且每次分配的頁編號也會一直遞增下去。
  4. mpool_new函數申請分配新的內存時,若是當前緩存中的內存頁小於maxcache數量則老是分配新的內存,只有當緩存數量大於maxcache時纔會從現有的緩存中尋找一頁能夠被重複利用的內存頁,若是沒有能夠重複利用的頁面,則會繼續分配新的內存頁。
  5. mpool_get函數則根據內存頁的編號獲取對應的內存頁。若是編號不存在則返回NULL。須要注意的是通常在獲取了某一頁內存後,不要進行重複獲取操做,不然在DEBUG狀態下會返回異常。另一個狀況是有可能相同的頁編號下兩次獲取的內存地址是不同的,由於系統實現內部有內存複用的機制。

3、內存池中內存的放回

功能:將分配或者申請的內存頁放回到內存池中去,以便進行重複利用。

函數簽名:

int  mpool_put(MPOOL *mp, void *pgaddr, u_int flags);

複製代碼

參數:

mp: [in] 內存池對象。

pgaddr:[in] 要放入緩存的內存頁地址。這個地址由mpool_get/new兩個函數返回。

flags:[in] 放回的屬性,通常設置爲0或者MPOOL_DIRTY。

return:[in] 函數調用成功返回0,失敗返回非0

描述

  1. 這個函數用來將內存頁放入回內存池緩存中,以便對內存進行重複利用。當將某個內存地址放入回緩存後,將不能再次訪問這個內存地址了。若是要想繼續訪問內存中的數據則須要藉助上述的mpool_get/new函數來從新獲取。
  2. flags:屬性若是指定爲0時,代表放棄此次內存中的內容的修改,系統不會將內存中的內容寫入到磁盤中,而只是將內存放入緩存中供其餘地方重複使用。而若是設置爲MPOOL_DIRTY時,則代表將這頁內存中的數據設置爲dirty標誌,除了一樣將內存放入緩存中重複利用外,則會在適當的時候將內存中的數據寫入到磁盤中,以便下次進行讀取。

4、內存池磁盤讀寫通知

功能:註冊回調函數,當某頁內存要寫回到磁盤或者要從磁盤中讀取時就會調用指定的回調函數。

函數簽名:

void mpool_filter(MPOOL *mp, void (*pgin)(void *, pgno_t, void *),
         void (*pgout)(void *, pgno_t, void *), void *pgcookie);
複製代碼

參數:

mp:[in] 內存池對象.

pgin: [in]: 回調函數,當某個內存頁的數據須要從磁盤讀取時,會在讀取完成後調用這個回調函數。

pgout:[in]: 回調函數,當某個內存頁的數據要到磁盤時,會在寫入完成後調用這個回調函數。

pgcookie: [in] 上述兩個回調函數的附加參數。

描述

由於內存池中的內存頁會進行復用,以及會在適當的時候將內容同步到磁盤中,或者從磁盤中將內容讀取到內存中,所以能夠藉助這個函數來監控這些磁盤文件和內存之間的讀寫操做。pgin和pgout函數的格式定義以下:

//pgin和pgout回調函數的格式。
//pgcookie:是mpool_filter函數中傳入的參數。
//pgno: 要進行讀寫的內存頁編號
//pageaddr: 要進行讀寫的內存地址。
void (*pgcallback)(void *pgcookie, pgno_t pgno, void *pageaddr);

複製代碼

5、示例代碼

#include <mpool.h>
#include <db.h>

 //建立並打開一個文件。
 int fd = open("/Users/apple/mpool", O_RDWR|O_APPEND|O_CREAT,0660);

//建立一個內存池對象,每頁的內存100個字節,最大的緩存數量爲4
 MPOOL *pool = mpool_open(NULL, fd, 100, 4);

   
//從內存池中分配一個新的內存頁,這裏對返回的內存填寫數據。
 pgno_t pidx1, pidx2 = 0;
 char *mem1 =  (char*)mpool_new(pool, &pidx1);
 memcpy(mem1, "aaa", 4);
    
 char *mem2 = (char*)mpool_new(pool, &pidx2);
 memcpy(mem2, "bbb", 4);
    
//將分配的內存mem1放回內存池中,可是內容不保存到磁盤
 mpool_put(pool, mem1, 0);
//將分配的內存mem2放回內存池中,可是內容保存到磁盤。
 mpool_put(pool, mem2, MPOOL_DIRTY);
    
//通過上面的操做後mem1,mem2將不能繼續再訪問了,須要訪問時須要再次調用mpool_get。   
mem1 = (char*)mpool_get(pool, pidx1, 0);
mem2 =   (char*)mpool_get(pool, pidx2, 0);

//上面的mem1和mem2可能和前面的new返回的地址是不同的。所以在內存池中不能經過地址來作惟一比較,而應該將編號來進行比較。
       
//將全部設置爲dirty標誌的內存也寫回到磁盤中去。
 mpool_sync(pool);

 mpool_close(pool);  //關閉內存池。

 close(fd);  //關閉文件。

複製代碼

內存池爲iOS系統底層開發提供了一個很是重要的能力,咱們能夠好好利用內存池來對內存進行管理,以及一些須要進行持久化的數據也能夠藉助內存池來進行保存,經過內存池提升內存的重複利用率。

相關文章
相關標籤/搜索