go get github.com/boltdb/boltandroid
db, err := bolt.Open("my.db", 0600, nil)
其中open
的第一個參數爲路徑,若是數據庫不存在則會建立名爲my.db的數據庫, 第二個爲文件操做,第三個參數是可選參數, 內部能夠配置只讀和超時時間等,
特別須要注意的地方就是由於boltdb是文件操做類型的數據庫,因此只能單點寫入和讀取,若是多個同時操做的話後者會被掛起直到前者關閉操做爲止, boltdb一次只容許一個讀寫事務,但一次容許多個只讀事務。因此數據具備較強的一致性。ios
所以單個事務和從它們建立的全部對象(例如桶、鍵)都不是線程安全的。與數據在多個概念你必須爲每個或使用鎖機制來保證只有一個goroutine裏操做改變數據。
只讀事務和讀寫事物一般不該該在同一個goroutine裏同時打開。因爲讀寫事務須要週期性地從新映射數據文件,這可能致使死鎖。git
boltdb的讀寫事務操做咱們可使用DB.Update()
來完成形如:github
err := db.Update(func(tx *bolt.Tx) error { ... return nil })
在閉包fun中,在結束時返回nil來提交事務。您還能夠經過返回一個錯誤在任何點回滾事務。全部數據庫操做都容許在讀寫事務中進行。
始終要關注err返回,由於它將報告致使您的事務不能完成的全部磁盤故障。golang
每一次新的事物都須要等待上一次事物的結束,這種開銷咱們能夠經過DB.Batch()
批處理來完成數據庫
err := db.Batch(func(tx *bolt.Tx) error { ... return nil })
在批處理過程當中若是某個事務失敗了,批處理會屢次調用這個函數函數返回成功則成功。若是中途失敗了,則整個事務會回滾。json
只讀事務可使用DB.View()
來完成瀏覽器
err := db.View(func(tx *bolt.Tx) error { ... return nil })
不改變數據的操做均可以經過只讀事務來完成, 您只能檢索桶、檢索值,或在只讀事務中複製數據庫。緩存
DB.Begin()
啓動函數包含在db.update和db.batch中,該函數啓動事務開始執行事務並返回結果關閉事務,這是boltdb推薦的方式,有時候你可能須要手動啓動事物你可使用Tx.Begin()
來開始,切記不要忘記關閉事務。安全
// Start a writable transaction. tx, err := db.Begin(true) if err != nil { return err } defer tx.Rollback() // Use the transaction... _, err := tx.CreateBucket([]byte("MyBucket")) if err != nil { return err } // Commit the transaction and check for error. if err := tx.Commit(); err != nil { return err }
桶是數據庫中鍵/值對的集合。桶中的全部鍵必須是惟一的。您可使用DB.CreateBucket()
建立一個桶:
db.Update(func(tx *bolt.Tx) error { b, err := tx.CreateBucket([]byte("MyBucket")) if err != nil { return fmt.Errorf("create bucket: %s", err) } return nil })
你也能夠是實用Tx.CreateBucketIfNotExists()
來建立桶,該函數會先判斷是否已經存在該桶不存在即建立, 刪除桶可使用Tx.DeleteBucket()
來完成
存儲鍵值對到桶裏可使用Bucket.Put()
來完成:
db.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte("MyFriendsBucket")) err := b.Put([]byte("one"), []byte("zhangsan")) return err })
獲取鍵值Bucket.Get()
:
db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte("MyFriendsBucket")) v := b.Get([]byte("one")) fmt.Printf("The answer is: %s\n", v) return nil })
get()
函數不返回一個錯誤,由於它的運行是保證工做(除非有某種系統故障)。若是鍵存在,那麼它將返回它的值。若是它不存在,那麼它將返回nil。
還須要注意的是當事務打開都get返回的值時惟一有效的,若是你須要將該值用於其餘事務,你能夠經過copy
拷貝到其餘的byte slice中
利用nextsequence()
功能,你可讓boltdb生成序列做爲你鍵值對的惟一標識。見下面的示例。
func (s *Store) CreateUser(u *User) error { return s.db.Update(func(tx *bolt.Tx) error { // 建立users桶 b := tx.Bucket([]byte("users")) // 生成自增序列 id, _ = b.NextSequence() u.ID = int(id) // Marshal user data into bytes. buf, err := json.Marshal(u) if err != nil { return err } // Persist bytes to users bucket. return b.Put(itob(u.ID), buf) }) } // itob returns an 8-byte big endian representation of v. func itob(v int) []byte { b := make([]byte, 8) binary.BigEndian.PutUint64(b, uint64(v)) return b } type User struct { ID int ... }
boltdb以桶中的字節排序順序存儲鍵。這使得在這些鍵上的順序迭代很是快。要遍歷鍵,咱們將使用遊標Cursor()
:
db.View(func(tx *bolt.Tx) error { // Assume bucket exists and has keys b := tx.Bucket([]byte("MyBucket")) c := b.Cursor() for k, v := c.First(); k != nil; k, v = c.Next() { fmt.Printf("key=%s, value=%s\n", k, v) } return nil })
遊標Cursor()
容許您移動到鍵列表中的特定點,並一次一個地經過操做鍵前進或後退。
光標上有如下函數:
First() 移動到第一個健. Last() 移動到最後一個健. Seek() 移動到特定的一個健. Next() 移動到下一個健. Prev() 移動到上一個健.
這些函數中的每個都返回一個包含(key []byte, value []byte)的簽名。當你有光標迭代結束,next()將返回一個nil。在調用next()或prev()以前,你必須尋求一個位置使用first(),last(),或seek()。若是您不尋求位置,則這些函數將返回一個nil鍵。
在迭代過程當中,若是鍵爲非零,但值爲0,則意味着鍵指向一個桶而不是一個值。用桶.bucket()訪問子桶。
遍歷一個key的前綴,你能夠結合seek()
和bytes.hasprefix()
:
db.View(func(tx *bolt.Tx) error { // Assume bucket exists and has keys c := tx.Bucket([]byte("MyBucket")).Cursor() prefix := []byte("1234") for k, v := c.Seek(prefix); bytes.HasPrefix(k, prefix); k, v = c.Next() { fmt.Printf("key=%s, value=%s\n", k, v) } return nil })
另外一個常見的用例是掃描範圍,例如時間範圍。若是你使用一個合適的時間編碼,如rfc3339而後能夠查詢特定日期範圍的數據:
db.View(func(tx *bolt.Tx) error { // Assume our events bucket exists and has RFC3339 encoded time keys. c := tx.Bucket([]byte("Events")).Cursor() // Our time range spans the 90's decade. min := []byte("1990-01-01T00:00:00Z") max := []byte("2000-01-01T00:00:00Z") // Iterate over the 90's. for k, v := c.Seek(min); k != nil && bytes.Compare(k, max) <= 0; k, v = c.Next() { fmt.Printf("%s: %s\n", k, v) } return nil })
若是你知道所在桶中擁有鍵,你也可使用ForEach()
來迭代:
db.View(func(tx *bolt.Tx) error { // Assume bucket exists and has keys b := tx.Bucket([]byte("MyBucket")) b.ForEach(func(k, v []byte) error { fmt.Printf("key=%s, value=%s\n", k, v) return nil }) return nil })
還能夠在一個鍵中存儲一個桶,以建立嵌套的桶:
func (*Bucket) CreateBucket(key []byte) (*Bucket, error) func (*Bucket) CreateBucketIfNotExists(key []byte) (*Bucket, error) func (*Bucket) DeleteBucket(key []byte) error
boltdb是一個單一的文件,因此很容易備份。你可使用TX.writeto()
函數寫一致的數據庫。若是從只讀事務調用這個函數,它將執行熱備份,而不會阻塞其餘數據庫的讀寫操做。
默認狀況下,它將使用一個常規文件句柄,該句柄將利用操做系統的頁面緩存。有關優化大於RAM數據集的信息,請參見Tx
文檔。
一個常見的用例是在HTTP上進行備份,這樣您就可使用像cURL
這樣的工具來進行數據庫備份:
func BackupHandleFunc(w http.ResponseWriter, req *http.Request) { err := db.View(func(tx *bolt.Tx) error { w.Header().Set("Content-Type", "application/octet-stream") w.Header().Set("Content-Disposition", `attachment; filename="my.db"`) w.Header().Set("Content-Length", strconv.Itoa(int(tx.Size()))) _, err := tx.WriteTo(w) return err }) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) } }
而後您可使用此命令進行備份:$ curl http://localhost/backup > my.db
或者你能夠打開你的瀏覽器以http://localhost/backup,它會自動下載。
若是你想備份到另外一個文件,你可使用TX.copyfile()
輔助功能。
數據庫對運行的許多內部操做保持一個運行計數,這樣您就能夠更好地瞭解發生了什麼。經過捕捉這些數據的快照,咱們能夠看到在這個時間範圍內執行了哪些操做。
例如,咱們能夠開始一個goroutine裏記錄統計每10秒:
go func() { // Grab the initial stats. prev := db.Stats() for { // Wait for 10s. time.Sleep(10 * time.Second) // Grab the current stats and diff them. stats := db.Stats() diff := stats.Sub(&prev) // Encode stats to JSON and print to STDERR. json.NewEncoder(os.Stderr).Encode(diff) // Save stats for the next loop. prev = stats }
有時建立一個共享的只讀boltdb數據庫是有用的。對此,設置options.readonly國旗打開數據庫時。只讀模式使用共享鎖容許多個進程從數據庫中讀取,但它將阻塞任何以讀寫方式打開數據庫的進程。
db, err := bolt.Open("my.db", 0666, &bolt.Options{ReadOnly: true}) if err != nil { log.Fatal(err) }
boltdb可以運行在移動設備上利用的工具結合特徵GoMobile。建立一個結構體,包含您的數據庫邏輯和參考一個bolt.db與初始化contstructor須要在文件路徑,數據庫文件將存儲。使用這種方法,Android和iOS都不須要額外的權限或清理。
func NewBoltDB(filepath string) *BoltDB { db, err := bolt.Open(filepath+"/demo.db", 0600, nil) if err != nil { log.Fatal(err) } return &BoltDB{db} } type BoltDB struct { db *bolt.DB ... } func (b *BoltDB) Path() string { return b.db.Path() } func (b *BoltDB) Close() { b.db.Close() }
數據庫邏輯應定義爲此包裝器結構中的方法。
要從本機語言初始化此結構(兩個平臺如今都將本地存儲與雲同步)。這些片斷禁用數據庫文件的功能):
Android
String path; if (android.os.Build.VERSION.SDK_INT >=android.os.Build.VERSION_CODES.LOLLIPOP){ path = getNoBackupFilesDir().getAbsolutePath(); } else{ path = getFilesDir().getAbsolutePath(); } Boltmobiledemo.BoltDB boltDB = Boltmobiledemo.NewBoltDB(path)
IOS
- (void)demo { NSString* path = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) objectAtIndex:0]; GoBoltmobiledemoBoltDB * demo = GoBoltmobiledemoNewBoltDB(path); [self addSkipBackupAttributeToItemAtPath:demo.path]; //Some DB Logic would go here [demo close]; } - (BOOL)addSkipBackupAttributeToItemAtPath:(NSString *) filePathString { NSURL* URL= [NSURL fileURLWithPath: filePathString]; assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]); NSError *error = nil; BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES] forKey: NSURLIsExcludedFromBackupKey error: &error]; if(!success){ NSLog(@"Error excluding %@ from backup %@", [URL lastPathComponent], error); } return success; }
1.下載工具go get github.com/boltdb/boltd
而後編譯cmd下的main文件生成可執行文件更名爲boltd
拷貝boltd到 *.db同級目錄,執行以下:
而後打開網站:
2.命令行工具
https://github.com/hasit/bolterboltdb基礎學習暫時就這麼多,下一章開始實踐