[官文翻译]Dart轻量超快键值数据库Hive - 基础

1,568 阅读7分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第17天,点击查看活动详情


原文链接:

pub: hive | Dart Package (flutter-io.cn)

pub译文: [译]纯Dart键值(对象)数据库hive - 掘金 (juejin.cn)

译时版本: hive 2.1.0


box 是什么?

在 Hive 中,所有的数据都被组织到 box 中。box 可以比作 SQL 中的表,但是它没有结构,可以包含任何内容。

对于小的应用,单个 box 差不多够用。对于更复杂的问题,box 是极好的组织数据的方式。box 也可以加密用来存储敏感数据。

打开 box

在能够使用 box 之前,必须先打开它。对于常规的 box ,这会从本地存储加载所有数据到内存中用于即时访问。

var box = await Hive.openBox<E>('testBox');
参数说明
namebox 的名称,它指定了存储位置,和检查一个 box 是否存在。 大小写敏感
encryptionKey该键必须是32位长度的字节数组,用来加密和解密 box 中的所有值。
keyComparator默认情况下,键使用词典编纂排序。该参数允许提供一个自定义排序。
compactionStrategy指定自动压缩的规则。
crashRecovery如果应用在写操作运行时被杀死了,最后的实体可能会被破坏。该实体会在应用重启用自动删除。如果不需要这种行为,可以指定为不可用。
path默认情况下,box 存储在 Hive.init() 指定的目录中。使用该参数,可以指定 box 应该存储的位置。
bytes代替使用文件作为后台,可以提供二进制形式的 box 并打开一个内存中的 box 。
E可选的类型参数指定了 box 中值的类型。

如果 box 已经打开,它会立即作为结果返回,然后所有指定的参数都会忽略。

一旦你获得了一个 box 实例,就可以读、写和删除实体了。

获取打开的 box

Hive 存储了所有打开的 box 的引用。 如果想要获取一个打开的 box ,可以使用:

var box = Hive.box('myBox');

该方法对于 Flutter 应用特别有用,因为不需要在组件间传递 box 。

关闭 box

如果不再需要某个 box ,可以关闭它。所有缓存的 box 的键值会从内存中清掉,然后 box 文件会在所有活动的读写操作完成之后关闭。

在应用的运行时保持一个 box 处于打开的状态是非常好的做法。如果将来再次需要 box ,只需要使其保持打开的状态。

var box = await Hive.openBox('myBox');
await box.put('hello', 'world');
await box.close();

在应用存在之前,应用调用 Hive.close() 关闭所有打开的 box 。不必担心关闭 Hive 之前应用会被杀掉,这没有关系。

类型参数:Box<E>

当打开一个 box 时,可以指定它可能只包含一个指定类型的值。例如,用户的 box 应该如下打开:

var box = await Hive.openBox<User>('users');

box.add(User());

box.add(5); // Compile time error* (编译时错误)

该 box 也可以包含 User 的子类型。

Hive.box() 指定同样的类型参数很重要。不能用不同的类型参数多次打开相同的 box 。

await Hive.openBox<User>('users');

Hive.box<User>('users'); // OK

Hive.box('users'); // ERROR
Hive.box<Dog>('users'); // ERROR

由于 Dart 的限制,不支持泛型参数如 Box<List> 。


读写

从 box 中进行读操作非常简单:

var box = Hive.box('myBox');

String name = box.get('name');

DateTime birthday = box.get('birthday');

如果键不存在,会返回 null 。也可以选择指定 defaultValue (默认值),用于 key 不存在时返回。

double height = box.get('randomKey', defaultValue: 17.5);

get() 返回的列表永远是 List<dynamic>Map<dynamic, dynamic>类型的 Map )类型。可使用 list.cast<SomeType>() 转换为指定的类型。

每个对象只存在一次

用指定的键值永远都会返回对象的同一个实例。对于基本类型没有影响,因为基本类型是不可变的,但是对于其它对象是必要的。

这里有个示例:

??

官网的示例代码显示不出来呢?

initialList 和 myList 是同一个实例,共享相同的数据。

在上面的示例中,只有缓存的对象被改变,而不是底层数据。要持久化该改变,需要调用 box.put('myList', myList) 。

向 box 中写内容就像是向 Map 中写内容。所有的键都必须是 ASCII 字符串,用最大长度为 255 字符或者使用无符号的32位整数。

var box = Hive.box('myBox');

box.put('name', 'Paul');

box.put('friends', ['Dave', 'Simon', 'Lisa']);

box.put(123, 'test');

box.putAll({'key1': 'value1', 42: 'life'});

你可能想知道写工作为什么不需要异步代码的原因。这是 Hive 的主要优点之一。 改动会尽快在后台写入到磁盘上,但是所有的监听器都会被即时通知。如果异步操作失败了(当然不应该出现),会使用旧的值再次通知所有的监听器。 如果想要确保写操作成功,只需要 await 其 Future

该运转机制和 lazy box 不同: 只要 put() 返回的 Future 没有完成, get() 都会返回旧的值(不存在的时候返回 null)。

下面的代码展示了差异:

var box = await Hive.openBox('box');

box.put('key', 'value');
print(box.get('key')); //  值

var lazyBox = await Hive.openLazyBox('lazyBox');

var future = lazyBox.put('key', 'value');
print(lazyBox.get('key')); // null

await future;
print(lazyBox.get('key')); // 值

删除

如果想要改变现有的值,可以覆写它,如使用 put() 或删除它:

???

官网的示例代码显示不出来呢?

如果键不存在,则无需访问磁盘,然后返回的 Future 会立刻完成。

写入 null 和 删除

写入 null 和删除值是 不同的 。

import 'package:hive/hive.dart';

void main() async {
  var box = await Hive.openBox('writeNullBox');

  box.put('key', 'value');

  box.put('key', null);
  print(box.containsKey('key'));

  box.delete('key');
  print(box.containsKey('key'));
}

在Flutter中使用Hive

Hive 支持所有 Flutter 支持的平台。

初始化 Flutter 应用

在能够打开 box 之前,Hive 需要知道可以存储它的数据的位置。 对于允许访问的目录,Android 和 iOS 有非常严格的规则。可以使用 path_provider 包获取一个有效的目录。

hive_flutter 包提供了 Hive.initFlutter() 扩展方法,该方法会处理所有事情。

可监听的值

如果希望组件基于 Hive 中存储的值进行刷新,可以使用 ValueListenableBuilderbox.listenable() 方法提供了 ValueListenable ,它也可以和 provider 包一起使用。

import 'package:flutter/material.dart';

import 'package:hive/hive.dart';
import 'package:hive_flutter/hive_flutter.dart';

void main() async {
  await Hive.initFlutter();
  await Hive.openBox('settings');
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Demo Settings',
      home: Scaffold(
        body: ValueListenableBuilder<Box>(
          valueListenable: Hive.box('settings').listenable(),
          builder: (context, box, widget) {
            return Center(
              child: Switch(
                value: box.get('darkMode', defaultValue: false),
                onChanged: (val) {
                  box.put('darkMode', val);
                },
              ),
            );
          },
        ),
      ),
    );
  }
}

每次 darkMode 关联的值改变时, builder 都会被调用,并且 Switch 组件会刷新。

监听指定的键

只在必要时刷新组件是比较好的实践。如果一个组件只依赖 box 中的指定键,可以提供  keys 参数:

ValueListenableBuilder<Box>(
  valueListenable: Hive.box('settings').listenable(keys: ['firstKey', 'secondKey']),
  builder: (context, box, widget) {
    // build widget
  },
)

异步调用

一些 Hive 的方法如 put() 或 delete() 是异步的。处理异步调用并不是很有趣,因为必须使用 FutureBuilder 或 StreamBuilder 并处理异常。

好消息是不需要 await 所有 Hive 返回的  Future 。发生改变时会即时反映,即使 Future 还未完成。如果想要确保一个写操作成功,只需要 await 其 Future


自增和下标

我们已经知道 Hive 支持无符号的键。如果你喜欢的话,可以使用自增的键。在排序和访问多个对象时会非常有用。可以像 List 一样使用 Box 。

import 'package:hive/hive.dart';

void main() async {
  var friends = await Hive.openBox('friends');
  friends.clear();

  friends.add('Lisa');            // index 0, key 0
  friends.add('Dave');            // index 1, key 1
  friends.put(123, 'Marco');      // index 2, key 123
  friends.add('Paul');            // index 3, key 124

  print(friends.getAt(0));
  print(friends.get(0));
  
  print(friends.getAt(1));
  print(friends.get(1));
  
  print(friends.getAt(2));
  print(friends.get(123));
  
  print(friends.getAt(3));
  print(friends.get(124));
}

也有 getAt() 、 putAt() 和 deleteAt() 方法使用它们的下标访问或改变值。

默认情况下,字符串键以词典编纂方式排序,它们也有下标。

即使只使用自增的键,也不应该依赖键和下标是相同的。