Flutter的键值存储数据库使用示例详解

目录
  • Flutter 键值存储数据库
  • unqlite
  • unqlite_flutter
  • 快速上手
    • 简单键值对存储
    • JSON
  • 为什么你应该使用unqlite_flutter?

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是一个无服务器的数据库,这意味着它的通讯是直接读取数据库文件的(从磁盘读取),没有中间服务器进程。
  • 然后unqlite也不像其他服务器一样,需要安装、配置以及权限管理等。它是直接嵌入到我们的程序中的
  • unqlite是一个单一数据库文件,它的数据库是一个普通的磁盘文件。这就意味着你可以把它随意的复制或备份到其他地方。
  • unqlite文件格式是跨平台的。在一台机器上编写的数据库文件可以复制到具有不同体系结构的不同机器上并在其上使用。
  • unqlite是一个标准的键/值存储数据库,类似于BerkeleyDB,Tokyo Cabinet,LevelDB等,但具有丰富的功能集,包括对事务的支持(ACID)。在KV存储下,键和值都被视为简单的字节数组,因此内容可以是ASCII字符串,二进制blob甚至磁盘文件。

简单概括,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

目前已完成的功能:

  • key-value 储存
  • JSON 文档储存

快速上手

简单键值对存储

添加依赖:

  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?

  • 比 Hive 更快,占用更少的内存
  • 可以支持 JSON 文档

它有什么缺点? 因为使用了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");
  }

以上就是Flutter的键值存储数据库使用示例详解的详细内容,更多关于Flutter键值存储数据库的资料请关注我们其它相关文章!

(0)

相关推荐

  • ios开发Flutter之数据存储

    目录 偏好存储 sqlite 创建表 数据插入 数据查询 数据修改 删除表 删除数据库 偏好存储 shared_preferences 类比iOS中的UserDefaults,使用方法比较简单. 地址戳这里 pub get之后会自动出现一个这样的文件generated_plugin_registrant.dart 数据存储: void _incrementCounter() { //创建对象,用于操作存储和读取. SharedPreferences.getInstance().then((Sha

  • Flutter中数据库的使用教程详解

    在Flutter开发过程中,我门有时候需要对一些数据进行本地的持久化存储,使用sp文件形式虽然也能解决问题,但是有时数据量较大的时候,显然我们文件形式就不太合适了,这时候我们就需要使用数据库进行存储,我们知道在原生中有系统提供的轻量级sqlite数据库,在Flutter强大的生态环境中,也有这样一个数据库插件sqflite: ^2.0.2可以同时在Androud.iOS中进行数据库操作. 1. 创建数据库:这里我以存储我的搜索历史记录为例. 首先导入: import 'package:sqfli

  • Flutter持久化存储之数据库存储(sqflite)详解

    前言 数据库存储是我们常用的存储方式之一,对大批量数据有增.删.改.查操作需求时,我们就会想到使用数据库,Flutter中提供了一个sqflite插件供我们用于大量数据执行CRUD操作.本篇我们就来一起学习sqflite的使用. sqflite是一款轻量级的关系型数据库,类似SQLite. 在Flutter平台我们使用sqflite库来同时支持Android 和iOS. sqflite使用 引入插件 在pubspec.yaml文件中添加path_provider插件,最新版本为1.0.0,如下:

  • Flutter数据库的使用方法

    说明 Flutter原生是没有支持数据库操作的,它使用SQLlit插件来使应用具有使用数据库的能力.其实就是Flutter通过插件来与原生系统沟通,来进行数据库操作. 平台支持 FLutter的SQLite插件支持IOS,安卓,和MacOS平台 如果要对Linux / Windows / DartVM进行支持请使用sqflite_common_ffi 不支持web平台 数据库操作在安卓或ios的后台执行 使用案例 notepad_sqflite 可以在iOS / Android / Window

  • Flutter的键值存储数据库使用示例详解

    目录 Flutter 键值存储数据库 unqlite unqlite_flutter 快速上手 简单键值对存储 JSON 为什么你应该使用unqlite_flutter? Flutter 键值存储数据库 键值存储是开发中十分常见的需求,在Flutter开发中,一般使用 shared_preferences 插件来实现.shared_preferences 本质上就是将键值对保存到一个XML文件中进行持久化. 而shared_preferences 实际上存在一定缺陷,譬如其性能较差,不适合处理大

  • Flutter 弹性布局基石flex算法flexible示例详解

    目录 flex 布局算法 非弹性组件在 main 轴受到的约束是 unbounded fit 参数 Expanded Spacer flex 布局算法 Flutter 弹性布局的基石 是 flex 和 flexible.理解了这两个 widget,后面的row,column 就都轻而易举了.本文用示例的方式详细介绍 flex 的布局算法. 先布局 flex 为 0 或 null 的 child.在 main 轴上 child 受到的约束是 unbounded.如果 crossAxisAlignm

  • JS中原始值和引用值的储存方式示例详解

    在ECMAscript中,变量可以存放两种类型的值,即原始值和引用值 原始值指的是代表原始数据类型的值,也叫基本数据类型,包括:Number.Stirng.Boolean.Null.Underfined 引用值指的是复合数据类型的值,包括:Object.Function.Array.Date.RegExp 根据数据类型不同,有的变量储存在栈中,有的储存在堆中.具体区别如下: 原始变量及他们的值储存在栈中,当把一个原始变量传递给另一个原始变量时,是把一个栈房间的东西复制到另一个栈房间,且这两个原始

  • 实现像php一样方便的go ORM数据库操作示例详解

    目录 引言 php的方便 go的麻烦 解决方案 写在最后 引言 很多人都是从php转过来的吧,不知道你们有没有发现,go界的orm并没有像php的orm一样好用.这篇文章里,我们认真的讨论下这个问题,并且会在后面提出解决方案. php的方便 比如你想实现一个关联查询,在php里,你只需要不断的使用箭头函数就可以了. $users = DB::table('users')->whereIn('id', [1, 2, 3])->orderBy('name', 'desc')->get();

  • Flutter封装组动画混合动画AnimatedGroup示例详解

    目录 一.来源 二.AnimatedGroup使用示例: 三.AnimatedGroup源码 最后 一.来源 项目中遇到混合动画的情况,每次实现都需要生命一堆属性,让代码变得杂乱,难以维护. 参考 iOS 组动画 CAAimationGroup, 随花半天时间封装一个混合动画组件 AnimatedGroup. 此组件基于极简.高扩展.高适用的封装原则,基本满足当前项目开发. 二.AnimatedGroup使用示例: // // AnimatedGroupDemo.dart // flutter_

  • PHP中替换键名的简易方法示例详解

    YII框架中封装好了的数据库操作函数,默认输出的时候,将数据库字段名作为数组的键名进行输出,但是有些时候带有键名的数据不能够满足未知情况下的操作,譬如:数据库数据导出为EXCEL等比较非正常的操作. 所以这边需要对数据库结果集进行解析,下面就是针对这种特殊情况的一个简单方法: 复制代码 代码如下: /** * @todo 针对YII 查询输出带有数据库表字段名键名进行优化EXCEL表格输出 * @todo 替换键名为0.1.2... * @param array $data * @return

  • SpringMVC框架搭建idea2021.3.2操作数据库的示例详解

    目录 1.目录 2.PersonController 3.PersonMapper 4.Person 5.PersonServiceImpl 6.PersonService 7.jdbc.properties 8.springmvc-servlet.xml 9.sql 10.pom 1.目录 2.PersonController package com.sk.controller; import com.sk.entity.Person; import com.sk.service.Person

  • Flutter 首页必用组件NestedScrollView的示例详解

    昨天Flutter 1.17版本重磅发布,新的版本主要是优化性能.修复bug,有人觉得此版本毫无亮点,但也从另一方面体现了Flutter目前针对移动端已经较为完善,想了解具体内容,文末有链接,如果你想升级到最新版本,建议慎重,有些人升级后项目无法运行. 今天介绍的组件是NestedScrollView,大部分的App首页都会用到这个组件. 可以在其内部嵌套其他滚动视图的滚动视图,其滚动位置是固有链接的. 在普通的ScrollView中, 如果有一个Sliver组件容纳了一个TabBarView,

  • springBoot配置国产达梦数据库的示例详解

    1. pom <!-- 达梦数据库驱动 --> <dependency> <groupId>com.dm</groupId> <artifactId>DmJdbcDriver18</artifactId> <version>1.8</version> </dependency> maven中央仓库里面没有,需要手动安装到maven本地仓库 mvn install:install-file -Dfil

  • Golang将Map的键值对调的实现示例

    目录 一.Map是什么? 二.详细代码 1.对调键值 2.进行调用 总结 PS:golang无序的键值对集合map 一.Map是什么? map是一堆键值对的未排序集合,类似Python中字典的概念,它的格式为map[keyType]valueType,是一个key-value的hash结构.map的读取和设置也类似slice一样,通过key来操作,只是slice的index只能是int类型,而map多了很多类型,可以是int,可以是string及所有完全定义了==与!=操作的类型 二.详细代码

随机推荐