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基础学习暂时就这么多,下一章开始实践