作者: 遊手好閒的石頭成 E-Mail: shirock@mail.educities.edu.tw ----------------- GDBM/NDBM使用介紹 GDBM - GNU database manager,一套簡單的資料庫管理函數。 gdbm 的常見版本是 1.73 ,在大部份的 FreeBSD, Linux 系統中皆已提供,若未提供, 則可利用搜尋引擎尋找 gdbm-1.7.3.tar.gz 或 gdbm-1.8.0.tar.gz 並下載,利用 configure 指令即可輕鬆安裝。 在安裝時,可以選擇是否一併安裝 dbm 及 ndbm 的相容介面。 dbm 是 UNIX 上一套古老的常見資料庫處理介面,而 ndbm 則是改良 dbm 缺失而設計的, 兩者的介面差異頗大。 gdbm 則是根據 ndbm 的介面設計的,兩者間非常相似, ndbm 的函數名稱皆以 dbm_ 開頭, 而 gdbm 為了加以區別,則以 gdbm_ 開頭,除此之外,皆可對應。 gdbm 所提供的資料庫管理介面,與 ndbm 非常相似,因此 gdbm 的說明,也可以適用於 ndbm 上。 可用兩種方式判斷有無安裝 gdbm : 1.查詢有無 gdbm 的 manpage $ man gdbm 2.查詢有無 gdbm 的 library $ ls /usr/lib/libgdbm* gdbm 提供一套管理 key/data 類型的資料庫的函數,其特徵是資料庫中,每筆記錄都有 一個唯一的鍵值。 這種 key/data 格式的資料檔案其實很常見,例如: -------------------------------------------- rock: 983-1231, rock@isu.edu.tw xyz: 931-4321, xyz@touc.edu.tw -------------------------------------------- 一個鍵值,一個記錄內容。 在使用 gdbm 前,通常我們會用循序檔來存放這類的資料,一筆一行或一區塊,一 筆一筆的存下來,這樣的好處是: 1.修改方便 可以用一般的文字編輯工具增修資料,不用另行設計。 2.節省空間 有多少資料就用多少空間,不像用 struct 的隨機檔,不論資料量多少,所有 記錄都佔同樣的空間。 但其缺點有: 1.管理不便 當記錄數達數百、甚至數萬筆時,繼續用這種方式管理,可是件令人痛苦的事。 2.使用負擔大 資料搜尋費時,所有資料都必須從頭開始找,在增修資料時也很費功夫,當資料 量大、使用率高時,會對系統造成負擔。 而 gdbm 則是針對此缺點提出的一個解決方案,其利用 hash 表存放鍵值,大幅節省 搜尋時間,同時仍允許記錄採用浮動長度,在時間及空間兩者中取得平衡。 其 hash 表是用可擴充式 hash 表演算法,會視需要自動擴充 hash 表,不需擔心 hash 表的大小。 gdbm 將資料庫管理動作分成三個: 1.storing 儲存 2.retrieval 擷取 3.deletion 刪除 加上開啟資料庫及走訪(visiting)的動作,提供七個主要的函數。 再加上一個存放上述函數呼叫錯誤狀態值的變數 gdbm_errno 。 其 prototype 如下: ================================================= #include typedef struct { char *dptr; int dsize; } datum; extern gdbm_error gdbm_errno; GDBM_FILE gdbm_open (name, block_size, read_write, mode, fatal_func); char * name; int block_size, read_write, mode; void (*fatal_func) (); void gdbm_close (dbf); GDBM_FILE dbf; int gdbm_store (dbf, key, content, flag); GDBM_FILE dbf; datum key, content; int flag; datum gdbm_fetch (dbf, key); GDBM_FILE dbf; datum key; int gdbm_delete (dbf, key); GDBM_FILE dbf; datum key; datum gdbm_firstkey (dbf); GDBM_FILE dbf; datum gdbm_nextkey (dbf, key); GDBM_FILE dbf; datum key; ================================================= datum 是用來表示記錄鍵值及記錄內容的 structure 。 1.gdbm_open() 開啟資料庫。 gdbm 將開啟者分成 reader 及 writer ,在同一時間內可以有多個 reader , 但一次只能有一個 writer 。 當指定的資料庫有 reader 在使用時,其他人就無 法成為 writer ,當有 writer 在使用時,其他人就無法成為 reader 或 writer。 GDBM_FILE 是傳回給開啟者的 handle 值。 name 是資料庫的檔案名稱,此檔案是完整的, gdbm 不會添加其他字眼上去。 而 gdbm 也就只會建立這一個資料庫檔案,不會另外再建索引檔或關連檔。 block_size 在建立資料庫時指定,指示 gdbm 以多大的記憶體區塊來讀寫磁碟資 料,此值最小是 512 (bytes) ,但不妨指定 0 ,由 gdbm 自行根據檔案系統的狀 態決定。 read_write 在指示開啟者的開啟型式,可指定的有四: GDBM_READER 成為 reader GDBM_WRITER 成為 writer GDBM_WRCREAT 成為 writer ,若資料庫不存在則建立之 GDBM_NEWDB 成為 writer ,並建立新的資料庫,不論是否存在舊資料庫 若欲成為 writer ,則可以再附帶一個設定值 GDBM_FAST ,指示 gdbm 在寫入資 料時,不要馬上進行與磁碟內容的同步化動作。 此行為在 gdbm 1.8 後,為預設動作。 mode 在指示建立資料庫時,檔案的屬性,其意義同 chmod() 。 一般設為 0644 (rw-r--r--),若沒有建立資料庫的動作,則指定 0 即可。 Fatal_func 為函數指標,當開啟資料庫,發現一個錯誤時,就呼叫該函數, 一般皆設為 NULL ,由 gdbm 自行以預設動作處理。 當 gdbm 無法成為 reader 或 writer 時,會立即返回,而不會自動擱置, 此時 gdbm_open() 傳回 NULL ,可由 gdbm_errno 得知錯誤狀態碼。 範例: --------------------------------------- GDBM_FILE dbf; dbf = gdbm_open("mydata.db", 0, GDBM_READER, 0, NULL); if( dbf == NULL ) { printf("Can not open database\n, %s\n", gdbm_strerror(gdbm_errno) ); } --------------------------------------- 2.gdbm_close() 關閉資料庫,不會有錯誤傳回。 範例: --------------------------------------- gdbm_close(dbf); --------------------------------------- 3.gdbm_store() 儲存記錄。 將記錄鍵值及記錄內容存入資料庫中,存入動作分為增加及替代,在 flag 中指定, 其值有二: GDBM_INSERT 增加一筆新記錄,若鍵值已存在,則儲存動作失敗。 GDBM_REPLACE 替代記錄內容。 其傳回值有三: -1 無法儲存。 0 儲存成功。 1 當使用 GDBM_INSERT 時,鍵值已存在,無法儲存。 範例: --------------------------------------- char keybuf[256], databuf[256]; datum key, data; gets(keybuf); gets(databuf); key.dptr = keybuf; key.dsize = strlen(keybuf); data.dptr = databuf; data.dsize = strlen(databuf); rc = gdbm_store(dbf, key, data, GDBM_INSERT); if( rc != 0 ) { printf("Can not store record\n, %s\n", gdbm_strerror(gdbm_errno) ); } --------------------------------------- 注意,這裡示範的是資料鍵值及內容都是一般文字,如果鍵值及內容中,包 含 '\0' 時,就不能用 strlen() 去計算 size 了。 4.gdbm_fetch() 根據指定鍵值擷取記錄。 傳回記錄的內容,要注意的是,記錄內容的空間是由 gdbm 以 malloc() 配置的, 但 gdbm 不會自動釋放,故當你不再使用該資料時,記得將其釋放。 若記錄內容指向 NULL ,表無此記錄。 範例: --------------------------------------- char keybuf[256]; datum key, data; gets(keybuf); key.dptr = keybuf; key.dsize = strlen(keybuf); data = gdbm_fetch(dbf, key); if( data.dptr == NULL ) { puts("Record not found!\n"); } else { printf("Record found (%d): %s", data.dsize, data.dptr); free(data.dptr); } --------------------------------------- 若你只是單純地想查詢鍵值是否已存在於資料庫,而不想擷取資料內容時,可以使 用 gdbm_exists(dbf, key) ,其傳回非零值(true)時,表示鍵值存在。 5.gdbm_delete() 根據指定鍵值移除記錄。 傳回 0 表示成功刪除, -1 表示刪除失敗。 範例: --------------------------------------- char keybuf[256]; datum key; gets(keybuf); key.dptr = keybuf; key.dsize = strlen(keybuf); rc = gdbm_delete(dbf, key); if( rc != 0 ) printf("Record can not delete.\n %s", gdbm_strerror(gdbm_errno)); --------------------------------------- 6.gdbm_firstkey() / gdbm_nextkey() 走訪資料庫。 根據 hash 表走訪資料庫,由於其並非根據資料存入順序或鍵值的排序走訪,因此 走訪結果並無次序,但可以保證的是,每筆記錄都會被走訪一次,不會遺漏。 key.dptr 是由 gdbm 以 malloc() 配置的,但 gdbm 不會自動釋放,故當你走訪完 畢時,記得將其釋放。 當 key.dptr 指向 NULL 時,表示走訪完畢。 注意,當你在走訪資料庫時,不要增加或刪除任何記錄,因為這將改變 hash 表的 內容,造成其走訪時的遺漏。 範例: --------------------------------------- datum key, nextkey; int n = 1; puts("Key list:\n"); key = gdbm_firstkey(dbf); while( key.dptr != NULL ) { printf("%d: %s", n, key.dptr); n++; nextkey = gdbm_nextkey(dbf, key); free(key.dptr); key = nextkey; } --------------------------------------- 7.gdbm_reorganize() 重整資料庫。 這個函數使用時,請再三斟酌,不要常使用。 隨著資料庫的增刪,時間一久,資料庫中,就會留下許多未被使用的空隙,此時可 呼叫 gdbm_reorganize(dbf) 進行資料庫的重整,去除那些空隙,適度縮減資料庫 檔案的大小。 除非呼叫 gdbm_reorganize() ,否則 gdbm 不會縮減資料庫檔案的長度,但是 gdbm 也不是一直往後增加資料,當舊有資料被刪除時, gdbm 會儘然重複使用那些空下來 的空間。 附註: 1.參考文件 GDBM(3), gdbm.h 詳細說明請參閱此 manpage 。 $ man gdbm 2.在本文中,一共提到了十個 gdbm 函數及一個 gdbm 變數: gdbm_open() gdbm_close() gdbm_store() gdbm_fetch() gdbm_delete() gdbm_firstkey() gdbm_nextkey() gdbm_exists() gdbm_reorganize() gdbm_strerror() gdbm_errno reader 無法使用 gdbm_store(), gdbm_delete() 及 gdbm_reorganize() 。