一起养成写作习惯!这是我参与「日新计划 4 月更文挑战」的第17天,点击查看活动详情。
原文链接:
- What are boxes? (hivedb.dev)
- Read & Write (hivedb.dev)
- Hive in Flutter (hivedb.dev)
- Auto increment & indices (hivedb.dev)
pub:
hive | Dart Package (flutter-io.cn)
pub译文:
[译]纯Dart键值(对象)数据库hive – ()
译时版本:
hive 2.1.0
box 是什么?
在 Hive 中,所有的数据都被组织到 box 中。box 可以比作 SQL 中的表,但是它没有结构,可以包含任何内容。
对于小的应用,单个 box 差不多够用。对于更复杂的问题,box 是极好的组织数据的方式。box 也可以加密用来存储敏感数据。
打开 box
在能够使用 box 之前,必须先打开它。对于常规的 box ,这会从本地存储加载所有数据到内存中用于即时访问。
var box = await Hive.openBox<E>('testBox');
参数 | 说明 |
---|---|
name | box 的名称,它指定了存储位置,和检查一个 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 中存储的值进行刷新,可以使用ValueListenableBuilder
。box.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()
方法使用它们的下标访问或改变值。
默认情况下,字符串键以词典编纂方式排序,它们也有下标。
即使只使用自增的键,也不应该依赖键和下标是相同的。