DBM KV數(shù)據(jù)庫(kù)原理(一)

一檬果、目的

??DBM提供了針對(duì)DBM-style顷蟀、string-keyed數(shù)據(jù)庫(kù)類(lèi)似字典的接口赃阀。
??dbm面向DBM數(shù)據(jù)庫(kù)丑瞧,利用簡(jiǎn)單的字符串值作為key來(lái)訪問(wèn)包括字符串的記錄甫恩。它內(nèi)置的whichdb()方法會(huì)識(shí)別 數(shù)據(jù)庫(kù)逆济,并使用合適的模塊來(lái)打開(kāi)數(shù)據(jù)庫(kù)。它經(jīng)常也用在shelve模塊的后端,shelve使用pickle模塊序列化對(duì)象后存入一個(gè)DBM數(shù)據(jù)庫(kù)奖慌。
??DBM對(duì)外提供的接口

extern int  dbminit (char *file);
extern datum    fetch (datum key);
extern int  store (datum key, datum content);
extern int  delete (datum key);
extern datum    firstkey (void);
extern datum    nextkey (datum key);
extern int  dbmclose (void);
二霎终、數(shù)據(jù)庫(kù)類(lèi)型

??Python中自帶了多個(gè)訪問(wèn)DBM風(fēng)格數(shù)據(jù)庫(kù)的模塊。默認(rèn)的實(shí)現(xiàn)取決于當(dāng)前系統(tǒng)上可用的庫(kù)和編譯Python時(shí)使用的選項(xiàng)升薯。與特定實(shí)現(xiàn)分離的接口允許Python程序與其他語(yǔ)言的程序交換數(shù)據(jù)莱褒,且不會(huì)自動(dòng)在可用格式之間切換,也可編寫(xiě)在多個(gè)平臺(tái)上運(yùn)行的便攜式數(shù)據(jù)文件涎劈。

  • dbm.gnu
    ??dbm.gnu是GNU項(xiàng)目中dbm庫(kù)版本的接口广凸,它與這里描述的其他DBM實(shí)現(xiàn)同樣的工作,對(duì)open()方法提供了flags標(biāo)志進(jìn)行了一些修改蛛枚。除了標(biāo)準(zhǔn)的'r'谅海、'w'、'c'蹦浦、'n'外扭吁,dbm.gun.open()還提供了:
    • 'f':表示使用fast模式打開(kāi)數(shù)據(jù)庫(kù)。這種模式下寫(xiě)入數(shù)據(jù)庫(kù)是一個(gè)異步操作盲镶。
    • 's':表示使用synchronized模式打開(kāi)數(shù)據(jù)庫(kù)侥袜。這種模式下,對(duì)數(shù)據(jù)庫(kù)所做的更改會(huì)立即寫(xiě)入文件溉贿,而不是在數(shù)據(jù)庫(kù)關(guān)閉或其他同步操作時(shí)進(jìn)行延遲寫(xiě)入枫吧。
    • 'u':表示使用無(wú)鎖打開(kāi)數(shù)據(jù)庫(kù)。
      gdbm對(duì)應(yīng)的接口如下:
    extern GDBM_FILE gdbm_fd_open (int fd, const char *file_name, int block_size,
    int flags, void (*fatal_func) (const char *));
    extern GDBM_FILE gdbm_open (const char *, int, int, int, void (*)(const char *));
    extern int gdbm_close (GDBM_FILE);
    extern int gdbm_store (GDBM_FILE, datum, datum, int);
    extern datum gdbm_fetch (GDBM_FILE, datum);
    extern int gdbm_delete (GDBM_FILE, datum);
    extern datum gdbm_firstkey (GDBM_FILE);
    extern datum gdbm_nextkey (GDBM_FILE, datum);
    extern int gdbm_reorganize (GDBM_FILE);
    extern int gdbm_sync (GDBM_FILE);
    extern int gdbm_exists (GDBM_FILE, datum);
    extern int gdbm_setopt (GDBM_FILE, int, void *, int);
    extern int gdbm_fdesc (GDBM_FILE);
    
  • dbm.ndbm
    ??dbm.ndbm 模塊提供了一個(gè) Unix 上 ndbm 的接口實(shí)現(xiàn)宇色,依賴(lài)于模塊在編譯時(shí)的配置九杂。模塊的 library 屬性會(huì)識(shí)別出 configure 的名稱(chēng)然后查找出擴(kuò)展模塊是什么時(shí)候進(jìn)行編譯的。
    ??ndbm對(duì)應(yīng)的接口如下:
    extern DBM  *dbm_open (char *file, int flags, int mode);
    extern void  dbm_close (DBM *dbf);
    extern datum  dbm_fetch (DBM *dbf, datum key);
    extern int  dbm_store (DBM *dbf, datum key, datum content, int flags);
    extern int  dbm_delete (DBM *dbf, datum key);
    extern datum  dbm_firstkey (DBM *dbf);
    extern datum  dbm_nextkey (DBM *dbf);
    extern int       dbm_error (DBM *dbf);
    extern void      dbm_clearerr (DBM *dbf);
    extern int  dbm_dirfno (DBM *dbf);
    extern int  dbm_pagfno (DBM *dbf);
    extern int  dbm_rdonly (DBM *dbf);
    
  • dbm.dumb
    ??當(dāng)沒(méi)有其他實(shí)現(xiàn)可用時(shí)宣蠕, dbm.dumb 模塊為 DBM API 提供了一個(gè)可移植的回退實(shí)現(xiàn)例隆。 使用 dbm.dumb 不需要外部依賴(lài),但比大多數(shù)其他實(shí)現(xiàn)要慢抢蚀。
    ??dbm镀层、gdbm適合存儲(chǔ)靜態(tài)的、索引話(huà)的數(shù)據(jù)結(jié)構(gòu)思币,適用于處理那些被頻繁訪問(wèn)但卻很少被更新的數(shù)據(jù)鹿响,因?yàn)樗鼊?chuàng)建數(shù)據(jù)項(xiàng)時(shí)非常慢羡微,但檢索數(shù)據(jù)項(xiàng)時(shí)非彻榷觯快。
三妈倔、gdbm頭文件

本系列中主要以gdbm代碼為例博投,對(duì)dbm的原理和實(shí)現(xiàn)進(jìn)行分析和說(shuō)明。

  • 頭文件
    • autoconf.h
      由configure腳本根據(jù)config.h.in模版生成盯蝴。autoconf.h為平臺(tái)相關(guān)的頭文件毅哗、函數(shù)听怕、庫(kù)等定義常量標(biāo)志。
    • systems.h
      包含平臺(tái)相關(guān)的頭文件和代碼虑绵。一些函數(shù)不同平臺(tái)名稱(chēng)不一致尿瞭。system.h把這些函數(shù)和代碼編寫(xiě)成統(tǒng)一的接口,以供程序使用翅睛,這樣就隱藏了底層不同平臺(tái)的差異声搁。
    • gdbmerror.h
      gdbm的錯(cuò)誤碼列表。主要由內(nèi)存分配捕发、文件讀寫(xiě)疏旨、文件鎖定、數(shù)據(jù)庫(kù)操作方面的錯(cuò)誤扎酷。
    • gdbmconst.h
      gdbm要用到的一些常量定義檐涝。包括gdbm_open、gdbm_store法挨、gdbm_setopt的一些常量參數(shù)谁榜、桶緩存的大小。
    • gdbmdefs.h
      包含了gdbm數(shù)據(jù)庫(kù)文件的結(jié)構(gòu)凡纳。這些定義具有兼容性惰爬,它們既用在gdbm中,也用在dbm惫企、ndbm中撕瞧。

四、dbm數(shù)據(jù)存儲(chǔ)原理

??一般數(shù)據(jù)庫(kù)的實(shí)現(xiàn)大體思路是用一個(gè)數(shù)據(jù)文件存放數(shù)據(jù)狞尔,這個(gè)數(shù)據(jù)文件其實(shí)就是普通的文件丛版,但是文件的結(jié)構(gòu)設(shè)計(jì)很復(fù)雜。另外偏序,也可以使用一個(gè)文件來(lái)保存索引页畦,如果數(shù)據(jù)量不是很大,則可以把索引存放在內(nèi)存中(如mysql的heap)研儒。gdbm數(shù)據(jù)庫(kù)的存儲(chǔ)方式采用的是可擴(kuò)展散列表豫缨。
??散列基本思想:通過(guò)由散列函數(shù)決定鍵值與散列地址的對(duì)應(yīng)關(guān)系來(lái)實(shí)現(xiàn)存儲(chǔ)組織和查找運(yùn)算,按照散列存儲(chǔ)方式構(gòu)造的存儲(chǔ)結(jié)構(gòu)稱(chēng)為散列表端朵。
??散列技術(shù):散列技術(shù)的核心是散列函數(shù)好芭。散列函數(shù)是一種將鍵值映射為散列表中的存儲(chǔ)位置的函數(shù)。對(duì)任意給定的動(dòng)態(tài)查找表T冲呢,如果選定了某個(gè)理想的散列函數(shù)H以及響應(yīng)的散列表L舍败,則對(duì)T中的每個(gè)數(shù)據(jù)元素X,函數(shù)值H(X.key)就是X在散列表L中的出處位置。插入或建表時(shí)數(shù)據(jù)元素X將被放置在該文件位置上邻薯,并且查找X時(shí)也到該位置上查找裙戏。

五、gdbm數(shù)據(jù)庫(kù)文件結(jié)構(gòu)
  • 文件信息結(jié)構(gòu)gdbm_file_info
struct gdbm_file_info
{

 char *name;                    // 文件名
 unsigned read_write :2;        // 讀寫(xiě)模式標(biāo)志
 unsigned fast_write :1;        // 同步寫(xiě)文件標(biāo)志
 unsigned central_free :1;      //
 unsigned coalesce_blocks :1;  //
 unsigned file_locking :1;      // 文件鎖標(biāo)志
 unsigned memory_mapping :1;    // 是否使用mmap
 unsigned cloexec :1;          // 是否使用GDBM_CLOEXEC標(biāo)志打開(kāi)數(shù)據(jù)庫(kù)
 unsigned need_recovery :1;    // 最后一個(gè)Fatal Error時(shí)厕诡,數(shù)據(jù)庫(kù)是否需要恢復(fù)
 gdbm_error last_error;        // 最后一個(gè)錯(cuò)誤碼
 int last_syserror;            // 最后一個(gè)系統(tǒng)錯(cuò)誤碼
 char *last_errstr;             // 最后一個(gè)錯(cuò)誤字符串描述
 enum { LOCKING_NONE = 0, LOCKING_FLOCK, LOCKING_LOCKF, LOCKING_FCNTL } lock_type;
 void (*fatal_err) (const char *);  // 錯(cuò)誤處理函數(shù)指針
 int desc;                        // 文件描述
 gdbm_file_header *header;        // 文件頭指針
 off_t *dir;                      // 散列表目錄指針
 cache_elem *bucket_cache;        // 桶緩存數(shù)組指針
 size_t cache_size;              // 
 size_t last_read;
 hash_bucket *bucket;            // 當(dāng)前緩存項(xiàng)讀的桶指針
 int bucket_dir;                  // 當(dāng)前目錄項(xiàng)的索引
 cache_elem *cache_entry;        // 當(dāng)前桶緩存項(xiàng)

 unsigned header_changed :1;
 unsigned directory_changed :1;
 unsigned bucket_changed :1;
 unsigned second_changed :1;
 /* Mmap info */
 size_t mapped_size_max;/* Max. allowed value for mapped_size */
 void  *mapped_region;  /* Mapped region */
 size_t mapped_size;    /* Size of the region */
 off_t  mapped_pos;     /* Current offset in the region */
 off_t  mapped_off;     /* Position in the file where the region begins */
};
  • 文件頭gdbm_file_header
typedef struct
{
 int   header_magic;  // 版本
 int   block_size;    // 最佳傳遞大小
 off_t dir;           // 散列目錄表的偏移地址
 int   dir_size;      // 目錄項(xiàng)的大小
 int   dir_bits;      // 目錄地址占用的比特?cái)?shù)
 int   bucket_size;   // 桶的字節(jié)大小
 int   bucket_elems;  // 桶中元素個(gè)數(shù)
 off_t next_block;    // 下一個(gè)未分配的塊的偏移地址
 avail_block avail;   // 可用塊
} gdbm_file_header;
  • 散列桶hash_bucket
typedef struct
{
 int   av_count;                        // 可用塊元素個(gè)數(shù) 
 avail_elem bucket_avail[BUCKET_AVAIL];  // 可用塊元素列表
 int   bucket_bits;                     // 標(biāo)識(shí)桶的二進(jìn)制系列
 int   count;                           // 桶的元素個(gè)數(shù)
 bucket_element h_table[1];             // 桶的元素列表(真正的可擴(kuò)展三列表累榜,它會(huì)隨著文件的增長(zhǎng)而分裂×橄樱可用塊元素中包含了可用存儲(chǔ)塊地址和大小信柿,桶元素中包含了要管理的關(guān)鍵字/數(shù)據(jù)的地址和大小)
} hash_bucket;
  • 桶元素bucket_element
typedef struct
{
 int   hash_value;       // 哈希值
 char  key_start[SMALL]; // 關(guān)鍵字的前SMALL字節(jié)
 off_t data_pointer;     // 關(guān)鍵字記錄的偏移地址
 int   key_size;         // 關(guān)鍵字的大小
 int   data_size;        // 數(shù)據(jù)大小
} bucket_element;
  • 可用塊avail_block
typedef struct
{
 int   size;             // 可用塊元素大小
 int   count;            // 可用塊元素個(gè)數(shù)
 off_t next_block;       // 下一個(gè)可用塊的偏移地址
 avail_elem av_table[1]; // 可用塊元素列表
} avail_block;
  • 可用塊元素avail_elem
typedef struct
{
 int   av_size;  // 可用塊大小
 off_t  av_adr;  // 可用塊的偏移地址(磁盤(pán)文件上各個(gè)可用存儲(chǔ)塊本身的位置不會(huì)有任何改變,而是通過(guò)操作知識(shí)可用塊的avail_elem元素來(lái)對(duì)可用塊進(jìn)行存取)
} avail_elem;
  • 桶緩存數(shù)組bucket_cache
cache_elem *bucket_cache;  // 由多個(gè)緩存項(xiàng)組成的一個(gè)數(shù)組醒第,在桶緩存初始化時(shí)渔嚷,文件信息結(jié)構(gòu)dbf中bucket_cache指針指向這個(gè)數(shù)組
size_t cache_size;
  • 桶緩存中的緩存項(xiàng)cache_elem
// 緩存中的緩存項(xiàng)cache_elem不僅包含了實(shí)際的桶,還包含了數(shù)據(jù)緩存塊及一些標(biāo)志稠曼。在桶緩存初始化時(shí)形病,dbf中的指針cache_entry初始時(shí)指向桶緩存中的第一個(gè)緩存項(xiàng)
typedef struct
{
 hash_bucket *   ca_bucket;  // 實(shí)際桶指針
 off_t           ca_adr;      // 偏移地址
 char            ca_changed;   // 更改標(biāo)志
 data_cache_elem ca_data;    // 數(shù)據(jù)緩存
} cache_elem;
  • 緩存項(xiàng)中的數(shù)據(jù)緩存元素data_cache_elem
typedef struct
{
 int     hash_val;  // 哈希值
 int     data_size;  // 數(shù)據(jù)長(zhǎng)度
 int     key_size;  // 關(guān)鍵字長(zhǎng)度
 char    *dptr;    // 指向數(shù)據(jù)起點(diǎn)的指針
 size_t  dsize;    // 偏移位置值
 int     elem_loc;  //
} data_cache_elem;
  • 散列桶目錄表dir
off_t *dir; // 每個(gè)元素是一個(gè)off_t類(lèi)型,根據(jù)元素中存放的偏移地址值訪問(wèn)磁盤(pán)上相應(yīng)的散列桶
gdbm fs.png

??存放在磁盤(pán)上的一個(gè)新的數(shù)據(jù)庫(kù)文件只包含頭文件gdbm_file_header霞幅、桶目錄表dir[dir_size/4]漠吻、初始的一個(gè)桶hash_bucket(以后隨著文件的增長(zhǎng)會(huì)分裂為多個(gè)桶),它們按照順序存放司恳。這里gdbm_file_header中存放了活動(dòng)的可用塊avali_block以及下一個(gè)可分配可用塊的偏移地址途乃,avali_block中的avali_elem猜真正指出了數(shù)據(jù)存儲(chǔ)塊的大小和偏移地址。hash_bucket中的bucket_element有指向關(guān)鍵字/數(shù)據(jù)的指針data_pointer(注意數(shù)據(jù)直接存儲(chǔ)在關(guān)鍵字之后)扔傅,關(guān)鍵字大小及數(shù)據(jù)的大小耍共,同時(shí)還含有關(guān)鍵字的一小部分實(shí)際值。

??為了高效地對(duì)數(shù)據(jù)進(jìn)行存取猎塞,這里實(shí)現(xiàn)了一個(gè)緩存系統(tǒng)试读,主要有緩存項(xiàng)cache_elem結(jié)構(gòu)和數(shù)據(jù)緩存元素data_cache_elem結(jié)構(gòu)。cache_elem中有指向?qū)嶋H的桶的指針(桶在磁盤(pán)上荠耽,用來(lái)對(duì)數(shù)據(jù)進(jìn)行定位钩骇、查找、存取等)铝量,還包含了數(shù)據(jù)緩存元素及一些標(biāo)志倘屹。數(shù)據(jù)緩存元素中指明了我們要存取的實(shí)際數(shù)據(jù),通過(guò)內(nèi)存中的緩存系統(tǒng)慢叨,我們可以大大提高數(shù)據(jù)的存取速度纽匙。

公眾號(hào):變成之禪,專(zhuān)注CDN插爹、后臺(tái)開(kāi)發(fā)哄辣、算法和大數(shù)據(jù)请梢,歡迎關(guān)注交流

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末赠尾,一起剝皮案震驚了整個(gè)濱河市力穗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌气嫁,老刑警劉巖当窗,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異寸宵,居然都是意外死亡崖面,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門(mén)梯影,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)巫员,“玉大人,你說(shuō)我怎么就攤上這事甲棍〖蚴叮” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵感猛,是天一觀的道長(zhǎng)七扰。 經(jīng)常有香客問(wèn)我,道長(zhǎng)陪白,這世上最難降的妖魔是什么颈走? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮咱士,結(jié)果婚禮上立由,老公的妹妹穿的比我還像新娘。我一直安慰自己序厉,他們只是感情好拆吆,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著脂矫,像睡著了一般枣耀。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上庭再,一...
    開(kāi)封第一講書(shū)人閱讀 52,156評(píng)論 1 308
  • 那天捞奕,我揣著相機(jī)與錄音,去河邊找鬼拄轻。 笑死颅围,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的恨搓。 我是一名探鬼主播院促,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼筏养,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了常拓?” 一聲冷哼從身側(cè)響起渐溶,我...
    開(kāi)封第一講書(shū)人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎弄抬,沒(méi)想到半個(gè)月后茎辐,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡掂恕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年拖陆,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片懊亡。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡依啰,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出店枣,到底是詐尸還是另有隱情速警,我是刑警寧澤,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布艰争,位于F島的核電站坏瞄,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏甩卓。R本人自食惡果不足惜鸠匀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望逾柿。 院中可真熱鬧缀棍,春花似錦、人聲如沸机错。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)弱匪。三九已至青瀑,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間萧诫,已是汗流浹背斥难。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留帘饶,地道東北人哑诊。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像及刻,于是被迫代替她去往敵國(guó)和親镀裤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子竞阐,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容