持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第20天,点击查看活动详情
Isar:用于 Futter 可跨平台的超快数据库
官方文档:Home | Isar Database
pub:isar | Dart Package (flutter-io.cn)
本文翻译自:Full-text search | Isar Database
译时版本:3.0.2
全文搜索
全文搜索是在数据库中搜索文本的强大方式。 你应该已经习惯了 索引 是如何工作的,但现在我们来复习下基础知识。
索引的工作像是一个查找表,它允许查询引擎快速找到给定值的记录。
例如,如果你的对象中有一个 title 字段,
你可以在这个字段上创建索引,用给定的 title 查找对象时可以更快。
为什么全文搜索有用?
可以方便地使用过滤器检索文本。
有各种字符串的操作,如 .startsWith()、 .contains() 和 .matches() 。
过滤器的问题是它们的运行时间是 O(n) , n 是集合中的记录数。
字符串操作如 .matches() 的成本尤其高。
全文检索比过滤器快很多,但是索引有一些局限性。 在该篇方法中,我们会看一下围绕这些局限如何工作。
基础示例
想法总是相同的:代替索引整个文本,我们对文本中的单词进行索引,这样就可以分别查找它们了。
我们创建一下最基础的全文搜索索引:
class Message {
Id? id;
late String content;
@Index()
List<String> get contentWords => content.split(' ');
}
现在我们可以查找带有 content 中特定单词的消息:
final posts = await isar.messages
.where()
.contentWordsAnyEqualTo('hello')
.findAll();
该查询超级快,但是有一些问题:
- 只能查找整个单词
- 不考虑标点
- 不支持其它空白字符
以正确的方式分割文本
让我们尝试改善下前面的示例。 我们能尝试引入复杂的正则表达式来确定单词分割,但是它会比较慢,边界情况也有错误。
Unicode Annex #29 定义了如何为大多数语言正确地将文本分割为单词。这相当复杂但是幸运的是,Isar 为我们做了繁重的工作:
Isar.splitWords('hello world'); // -> ['hello', 'world']
Isar.splitWords('The quick (“brown”) fox can’t jump 32.3 feet, right?');
// -> ['The', 'quick', 'brown', 'fox', 'can’t', 'jump', '32.3', 'feet', 'right']
我想要更多地控制
十分简单! 我们可以改变索引同时支持前缀匹配和大小写不敏感的匹配:
class Post {
int? id;
late String title;
@Index(type: IndexType.value, caseSensitive: false)
List<String> get titleWords => title.split(' ');
}
默认情况下,Isar 会将单词存储为哈希值,因为这样能更快和更高的空间效率。
但是哈希值不能用于前缀匹配。
可以使用 IndexType.value 改变索引为直接使用单词来代替。
它提供了 .titleWordsAnyStartsWith() where 子句:
final posts = await isar.posts
.where()
.titleWordsAnyStartsWith('hel')
.or()
.titleWordsAnyStartsWith('welco')
.or()
.titleWordsAnyStartsWith('howd')
.findAll();
我也需要 .endsWith()
必须滴!
我们使用一个技巧来实现 .endsWith() 匹配:
class Post {
int? id;
late String title;
@Index(type: IndexType.value, caseSensitive: false)
List<String> get revTitleWords {
return Isar.splitWords(title).map((word) => word.reversed).toList();
}
}
不要忘记反转查找目标(的字符串):
final posts = await isar.posts
.where()
.revTitleWordsAnyStartsWith('lcome'.reversed)
.findAll();
词干算法
不幸的是,索引不支持包含匹配(其它数据库也一样)。 但是有一些替代方案值得一看。 这高度依赖于你选择择的使用场景。 例如,索引单词词干代替整个单词。
词干算法是语言规范化的处理过程,经过这种处理,一个单词的多种形式可以减少为一个常用形式,例如:
connection
connections
connective ---> connect
connected
connecting
也有一些高级形式如 词形还原。
拼音算法
拼音算法 是将单词的发音作为索引的算法。 换句话说,它允许你搜索和查找目标相似发音的单词。
大多数的拼音算法只支持单个语言。
探测法
探测法是使用英语中的发音声音索引名字的拼音算法。 目标是将同音异义词编码为相同的表现,这样即使它们在拼写上有细微不同也可以匹配。 这是非常易懂的算法,它有多种改进版本。
使用该算法,"Robert" 和 "Rupert" 会返回相同的字符串 "R163" , 但"Rubin" 返回 "R150". "Ashcraft" 和 "Ashcroft" 都会返回 "A261" 。
双元音
双元音 拼音编码算法是该算法的第二代。 它对原来的元音算法进行了大量的基本设计改进。
双元音试图解释斯拉夫语、日耳曼语、凯尔特语、希腊语、法语、意大利语、西班牙语、中文和其他起源语言的英语中的无数不规则性。