亲宝软件园·资讯

展开

Flutter键值存储数据库

编程之路从0到1 人气:0

Flutter 键值存储数据库

键值存储是开发中十分常见的需求,在Flutter开发中,一般使用 shared_preferences 插件来实现。shared_preferences 本质上就是将键值对保存到一个XML文件中进行持久化。

而shared_preferences 实际上存在一定缺陷,譬如其性能较差,不适合处理大量数据,不能创建新的XML文件,所有数据存在同一个文件中。除此外,还有其他一些持久化方案,如SQLite、Hive等。

SQLite是关系型数据库,使用起来相对繁琐;而Hive是用Dart实现的一个轻量级键值对数据库,它使用简单,但同样性能较差,而且在储存大量数据时,更加耗费内存,因为它是一次性将所有数据读取到内存中,这在移动端不是很可取。当然,如果你只是用来存储几个简单的配置项数据,那也够用了。

unqlite

以上概述了一下Flutter的键值持久化方案,那么接下来就隆介绍一下本文推荐的Flutter键值存储方案——一个轻量级嵌入式nosql数据库unqlite!

unqlite是一个嵌入式的数据库,它实现了一个独立的、无服务器、零配置、事务性的nosql数据库引擎。它是一个文档存储数据库,类似于MongoDB, Redis, CouchDB等,同时也是一个标准的key/value存储数据库,类似于BerkeleyDB, LevelDB, 等。

简单概括,unqlite是一个标准C语言实现的轻量级、高性能nosql数据库,经过编译后,它只有几百KB大小,可以嵌入到我们的App中,它与SQLite数据库类似,区别在于一个是基于SQL的关系型数据库,一个是NoSql数据库。

unqlite_flutter

既然unqlite是一个C语言编写的数据库,那么Flutter开发如何使用它呢?

随着Dart版本的不断迭代,Dart语言的FFI接口逐渐成熟,FFI类似于Java的JNI,赋予了Dart语言直接调用C语言的能力。有了这种能力,那么基于C/C++开发的优秀的高性能的库,都可以用于Flutter开发中,可以说是C/C++生态为我所用!想学习Flutter的FFI开发,可以参考博主的B站视频 Dart FFI开发入门 以及 程序员的C

当然,这里我已经完成了Dart FFI调用的封装,可以直接依赖我开发好的插件库——unqlite_flutter

目前已完成的功能:

快速上手

简单键值对存储

添加依赖:

  unqlite: ^0.0.3
  unqlite_flutter: ^0.0.3

示例代码:

// 创建或打开一个数据库
UnQLite db = UnQLite.open("${appDocDir.path}/test.db");
// 保存键值对
db.store("name", "Alex");
db.store("age", 18);
db.store(19, "haha");
// 通过指定泛型获取值
debugPrint(db.fetch<String>("name"));
debugPrint('${db.fetch<int>("age")}');
debugPrint(db.fetch<String>(19));
// 另一种获取值的方法
db.fetchCallback<int>("age", (val) {
    debugPrint('age=$val');
});

当然,还有另一种获取数据的方式,可能比fetch更快:

var cursor = db.cursor();
cursor.seek('name');
debugPrint('=> ${cursor.key} => ${cursor.value}');

你还可以使用事务。在一个事务中,如果发生异常,你可以回滚所有操作:

    var trans = db.transaction().begin();
    try {
      for (var i = 0; i < 100000; i++) {
        if (i == 10) {
          // 这里我们抛出一个异常
          throw Exception('test');
        }
        db.store("transaction_$i", "here is a transaction_$i");
      }
      trans.commit();
    } catch (e) {
      // 处理异常,手动回滚事务
      trans.rollback();
    }

以上示例中,我们保存十万个数据,一旦中间发生了什么异常,我们执行回滚操作,则前面所有的保存的数据将被取消。

你还可以使用迭代器来进行遍历:

for (var entry in db.cursor()) {
  var content = '${entry.key} => ${entry.value}';
  debugPrint(content);
}

JSON

处理JSON文档

    UnQLite db = UnQLite.open("${appDocDir.path}/test2.db");
    var users = db.collection("users");
	// Create a collection
    users.create();
	// 保存JSON
    users.store(jsonDecode('''
{
        "title": "test json string",
        "author": [
                "arcticfox1919"
        ],
        "year": 2022,
        "like": "flutter"
}
    '''));
    users.store({'name': 'Mickey', 'age': 17});
    users.store([
      {'name': 'Alice', 'age': 18},
      {'name': 'Bruce', 'age': 19},
      {'name': 'Charlie', 'age': 20},
    ]);
	// 获取全部数据
    print(users.all());
    // print(users.fetch(0));
    // print(users.fetch(1));
    // print(users.fetch(2));
    // print(users.errorLog());
    print(users.creationDate());
    print(users.len());
    print(users.fetchCurrent());
    // 删除全部
    users.drop();
    db.close();

为什么你应该使用unqlite_flutter?

它有什么缺点? 因为使用了dart ffi,所以不能支持Flutter web。

下面是一些性能测试数据,这里没有列出内存占用的百分比,但可以肯定 unqlite 比Hive 使用了更少的内存:

UnQLite:

UnQLite init:1 ms
write 100,000 entries :611 ms
fetch 100,000 entries :370 ms
seek  100,000 entries :215 ms
iterate 100,000 entries :225 ms
transaction rollback :39 ms

Hive:

Hive init:48 ms
put 100,000 entries :807 ms
get 100,000 entries :290 ms

这是用于测试的代码,两者都在同一部真机上以profile模式运行:

    testUnQLite() async {
    var appDocDir = await getApplicationDocumentsDirectory();
    final start = DateTime.now().millisecondsSinceEpoch;
    UnQLite db = UnQLite.open("${appDocDir.path}/test.db");
    final t1 = DateTime.now().millisecondsSinceEpoch;
    for (var i = 0; i < 100000; i++) {
      db.store("my_key_$i", "Here is a value for testing—$i");
    }
    final t2 = DateTime.now().millisecondsSinceEpoch;
    for (var i = 0; i < 100000; i++) {
      var r = db.fetch<String>("my_key_$i");
      // debugPrint("fetch :$r");
    }
    final t3 = DateTime.now().millisecondsSinceEpoch;
    var cursor = db.cursor();
    for (var i = 0; i < 100000; i++) {
      cursor.seek('my_key_$i');
      // debugPrint('=> ${cursor.key} => ${cursor.value}');
    }
    final t4 = DateTime.now().millisecondsSinceEpoch;
    var count = 0;
    for (var entry in db.cursor()) {
      count++;
      var content = '${entry.key} => ${entry.value}';
      // debugPrint(content);
    }
    print('count => $count');
    final t5 = DateTime.now().millisecondsSinceEpoch;
    var trans = db.transaction().begin();
    try {
      for (var i = 0; i < 100000; i++) {
        if (i == 10) {
          throw Exception('test');
        }
        db.store("transaction_$i", "here is a transaction_$i");
      }
      trans.commit();
    } catch (e) {
      trans.rollback();
    }
    final t6 = DateTime.now().millisecondsSinceEpoch;
    debugPrint("UnQLite init:${t1-start} ms");
    debugPrint("write 100,000 entries :${t2-t1} ms");
    debugPrint("fetch 100,000 entries :${t3-t2} ms");
    debugPrint("seek  100,000 entries :${t4-t3} ms");
    debugPrint("iterate 100,000 entries :${t5-t4} ms");
    debugPrint("transaction rollback :${t6-t5} ms");
    db.close();
  }
  testHive() async {
    var appDocDir = await getApplicationDocumentsDirectory();
    var path = appDocDir.path;
    final start = DateTime.now().millisecondsSinceEpoch;
    Hive.init(path);
    var box = await Hive.openBox('testBox');
    final t1 = DateTime.now().millisecondsSinceEpoch;
    for (var i = 0; i < 100000; i++) {
      box.put("my_key_$i", "here is a transaction_$i");
    }
    final t2 = DateTime.now().millisecondsSinceEpoch;
    for (var i = 0; i < 100000; i++) {
      var name = box.get('my_key_$i');
    }
    final t3 = DateTime.now().millisecondsSinceEpoch;
    box.close();
    debugPrint("Hive init:${t1-start} ms");
    debugPrint("put 100,000 entries :${t2-t1} ms");
    debugPrint("get 100,000 entries :${t3-t2} ms");
  }

加载全部内容

相关教程
猜你喜欢
用户评论