-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Java|Kotlin 全文搜索
全文搜索(Full-Text-Search,简称 FTS),是 SQLite 提供的功能之一。它支持更快速、更便捷地搜索数据库内的信息,常用于应用内的全局搜索等功能。
WCDB 内建了全文搜索的支持,对中文、日文等非空格分割的语言做了针对性的优化;对英文做了词性还原,使搜索不受词形、时态的限制;从而使搜索更加精确。
虚拟表是 SQLite 的一个特性,可以更自由地自定义数据库操作的行为。在模型绑定一章,我们提到了虚拟表映射,但没有具体介绍。而在全文搜索中,它是不可或缺的一部分。下面是全文搜索的虚拟表映射的配置示例:
// Java
@WCDBTableCoding(ftsModule =
@FTSModule(version = FTSVersion.FTS5, //设置fts版本
tokenizer = BuiltinTokenizer.Verbatim) //设置分词器
)
public class FTSSample {
@WCDBField(isNotIndexed = true) //设置id列不建立fts索引
public int id;
@WCDBField
public String summary;
@WCDBField
public String description;
}
// 注册本数据库需要用到的分词器
database.addTokenizer(BuiltinTokenizer.Verbatim);
// 创建虚表
database.createVirtualTable("sampleVirtualTable", DBFTSSample.INSTANCE);
// Kotlin
@WCDBTableCoding(ftsModule =
@FTSModule(version = FTSVersion.FTS5, //设置fts版本
tokenizer = BuiltinTokenizer.Verbatim) //设置分词器
)
public class FTSSample {
@WCDBField(isNotIndexed = true) //设置id列不建立fts索引
public int id;
@WCDBField
public String summary;
@WCDBField
public String description;
}
// 注册本数据库需要用到的分词器
database.addTokenizer(BuiltinTokenizer.Verbatim)
// 创建虚表
database.createVirtualTable("sampleVirtualTable", DBFTSSample)
全文搜索的虚拟表映射一般只需定义模块和分词器即可,这里还使用FTS专用的列约束配置isNotIndexed
设置id
这个不参与全文搜索的列不建索引,这样可以节省空间。定义完成后,先调用addTokenizer(String)
方法往数据库中注册分词器,这个操作要在使用到这个分词器之前执行。定义完成后,调用 createVirtualTable(String, TableBinding<T>)
方法,则会根据字段映射和虚拟表映射创建虚拟表。
全文搜索的速度依赖于其索引。
// Java
FTSSample english = new FTSSample();
english.id = 1;
english.summary = "WCDB is a cross-platform database framework developed by WeChat.";
english.description = "WCDB is an efficient, complete, easy-to-use mobile database framework used in the WeChat application. It can be a replacement for Core Data, SQLite & FMDB.";
FTSSample chinese = new FTSSample();
chinese.id = 2;
chinese.summary = "WCDB 是微信开发的跨平台数据库框架";
chinese.description = "WCDB 是微信中使用的高效、完整、易用的移动数据库框架。它可以作为 CoreData、SQLite 和 FMDB 的替代。";
database.insertObjects(Arrays.asList(english, chinese),
DBFTSSample.allFields(),
"sampleVirtualTable");
// Kotlin
val english = FTSSample()
english.id = 1
english.summary = "WCDB is a cross-platform database framework developed by WeChat."
english.description =
"WCDB is an efficient, complete, easy-to-use mobile database framework used in the WeChat application. It can be a replacement for Core Data, SQLite & FMDB."
val chinese = FTSSample()
chinese.id = 2
chinese.summary = "WCDB 是微信开发的跨平台数据库框架"
chinese.description =
"WCDB 是微信中使用的高效、完整、易用的移动数据库框架。它可以作为 CoreData、SQLite 和 FMDB 的替代。"
database.insertObjects(listOf(english, chinese),
DBFTSSample.allFields(),
"sampleVirtualTable")
建立索引的操作与普通表插入数据基本一致。
全文搜索与普通表不同,必须使用 match
函数进行查找。
// Java
FTSSample objectMatchFrame = database.getFirstObject(
DBFTSSample.allFields(),
"sampleVirtualTable",
DBFTSSample.summary.match("frame*"));
System.out.print(objectMatchFrame.summary); // 输出 "WCDB is a cross-platform database framework developed by WeChat."
// 词形还原特性,通过 "efficiency" 也可以搜索到 "efficient"
FTSSample objectMatchEffiency = database.getFirstObject(
DBFTSSample.allFields(),
"sampleVirtualTable",
DBFTSSample.description.match("efficiency"));
System.out.print(objectMatchEffiency.description);// 输出 "WCDB is an efficient, complete, easy-to-use mobile database framework used in the WeChat application. It can be a replacement for Core Data, SQLite & FMDB."
// Kotlin
val objectMatchFrame = database.getFirstObject(
DBFTSSample.allFields(),
"sampleVirtualTable",
DBFTSSample.summary.match("frame*")
)
print(objectMatchFrame.summary) // 输出 "WCDB is a cross-platform database framework developed by WeChat."
// 词形还原特性,通过 "efficiency" 也可以搜索到 "efficient"
val objectMatchEffiency = database.getFirstObject(
DBFTSSample.allFields(),
"sampleVirtualTable",
DBFTSSample.description.match("efficiency")
)
print(objectMatchEffiency.description) // 输出 "WCDB is an efficient, complete, easy-to-use mobile database framework used in the WeChat application. It can be a replacement for Core Data, SQLite & FMDB."
SQLite 分词必须从首字母查起,如"frame*",而类似"*amework"这样从单词中间查起是不支持的。
全文搜索中有一列隐藏字段,它与表名一致。通过它可以对全表的所有字段进行查询。
// Java
Column tableColumn = new Column("sampleVirtualTable");
List<FTSSample> objects = database.getAllObjects(
DBFTSSample.allFields(),
"sampleVirtualTable",
tableColumn.match("SQLite"));
System.out.print(objects.get(0).description); // 输出 "WCDB is an efficient, complete, easy-to-use mobile database framework used in the WeChat application. It can be a replacement for Core Data, SQLite & FMDB."
System.out.print(objects.get(1).description); // 输出 "WCDB 是微信中使用的高效、完整、易用的移动数据库框架。它可以作为 CoreData、SQLite 和 FMDB 的替代。"
// Kotlin
val tableColumn = Column("sampleVirtualTable")
val objects = database.getAllObjects(
DBFTSSample.allFields(),
"sampleVirtualTable",
tableColumn.match("SQLite")
)
print(objects[0].description) // 输出 "WCDB is an efficient, complete, easy-to-use mobile database framework used in the WeChat application. It can be a replacement for Core Data, SQLite & FMDB."
print(objects[1].description) // 输出 "WCDB 是微信中使用的高效、完整、易用的移动数据库框架。它可以作为 CoreData、SQLite 和 FMDB 的替代。"
分词器是全文搜索的关键模块,它实现将输入内容拆分成多个Token并提供这些Token的位置,搜索引擎再对这些Token建立索引。
SQLite的FTS组件有提供内置的分词器,同时还支持自定义分词器。WCDB 在 SQLite 原有分词器的基础上,自己实现了下面三个分词器:
-
BuiltinTokenizer.OneOrBinary
,用于FTS3。 -
BuiltinTokenizer.Verbatim
,用于 FTS5,逻辑上和BuiltinTokenizer.OneOrBinary
基本一致。 -
BuiltinTokenizer.Pinyin
,用于 FTS5,可以实现拼音搜索。使用时需要使用class Database.config(pinyinDict:)
接口配置汉字到拼音的映射表。
BuiltinTokenizer.OneOrBinary
和BuiltinTokenizer.Verbatim
的用法和功能基本一样,上面已经有示例,这里就不再补充。下面通过例子介绍一下拼音搜索的实现方法:
@WCDBTableCoding(ftsModule =
@FTSModule(version = FTSVersion.FTS5, //配置 FTS 版本
tokenizer = BuiltinTokenizer.Pinyin)//配置拼音分词器
)
public class PinyinObject {
@WCDBField
public String content;
}
//配置汉字拼音映射表,支持配置多音字
Database.configPinyinDict(new HashMap<String, List<String>>() {{
put("单" , Arrays.asList("shan", "dan", "chan" ));
put("于" , Arrays.asList( "yu" ));
put("骑" , Arrays.asList( "qi" ));
put("模" , Arrays.asList( "mo", "mu" ));
put("具" , Arrays.asList( "ju" ));
put("车" , Arrays.asList( "che" ));
}});
// 注册拼音分词器
database.addTokenizer(BuiltinTokenizer.Pinyin);
//创建虚表
database.createVirtualTable("pinyinTable", DBPinyinObject.INSTANCE);
//写入数据
PinyinObject obj = new PinyinObject();
obj.content = "单于骑模具单车";
database.insertObject(obj, DBPinyinObject.allFields(), "pinyinTable");
//支持多音字搜索、拼音首字母搜索和拼音前缀搜索
String[] queries = new String[] {
"\"shan yu qi mu ju dan che\"",
"\"chan yu qi mo ju shan che\"",
"\"dan yu qi mo ju chan che\"",
"\"dan yu qi mu ju ch\"*",
"\"dan yu qi mo ju d\"*",
"\"s y q m j d c\"",
"\"c y q m j s c\"",
"\"c y q m j\""
};
//支持多音字搜索、拼音首字母搜索和拼音前缀搜索
for(String query : queries) {
List<PinyinObject> results = database.getAllObjects(DBPinyinObject.allFields(),
"pinyinTable", DBPinyinObject.content.match(query));
assert results.size() == 1;
assert results.get(0).content.equals(obj.content);
}
以上内容都可以用Kotlin实现,因为篇幅关系就不演示了,下同。
使用了拼音分词器之后,无法再用原内容来搜索,只能搜索拼音。如果需要支持原文搜索的话,需要再另外建一个FTS表来支持。在两个表的情况下,可以使用
FTSModule
中配置externaTable
来只保存一份原文,减小空间占用,原理见SQLite External Content Table。
现有的分词器还可以传入参数来做一些配置,需要添加参数的分词器可以直接在BindVirtualTable
配置中添加。WCDB 实现的BuiltinTokenizer.OneOrBinary
和BuiltinTokenizer.Verbatim
两个分词器有下面两个可配置参数:
-
BuiltinTokenizer.Parameter.SimplifyChinese
配置了之后可以支持用简体汉字来搜索繁体汉字,不过需要使用configTraditionalChineseDict(Map)
来配置简繁体汉字映射表。 -
BuiltinTokenizer.Parameter.SkipStemming
关闭英文单词的词性还原功能。
这两个配置参数可以叠加配置。
下面以简体汉字搜繁体汉字为例,介绍分词器参数的用法:
@WCDBTableCoding(
ftsModule = @FTSModule(
version = FTSVersion.FTS5,
tokenizer = BuiltinTokenizer.Verbatim,
tokenizerParameters = BuiltinTokenizer.Parameter.SimplifyChinese
)
)
public class TraditionalChineseObject {
@WCDBField
public String content;
}
//配置简繁体汉字映射表,需要在建索引和搜索前设置
Database.configTraditionalChineseDict(new HashMap<String, String>(){{
put("們", "们");
put("員", "员");
}});
//给数据库注册分词器
database.addTokenizer(BuiltinTokenizer.Verbatim);
//创建虚表
database.createVirtualTable(tableName, DBTraditionalChineseObject.INSTANCE);
Table<TraditionalChineseObject> table = database.getTable(tableName, DBTraditionalChineseObject.INSTANCE);
//建索引
TraditionalChineseObject obj = new TraditionalChineseObject();
obj.content = "我們是程序員";
table.insertObject(obj);
//可以使用繁体来搜索
List<TraditionalChineseObject> matchObject1 = table.getAllObjects(
DBTraditionalChineseObject.content.match("我們是程序員"));
Assert.assertTrue(matchObject1.size() == 1 &&
matchObject1.get(0).content.equals(obj.content));
//也可以使用简体来搜索
List<TraditionalChineseObject> matchObject2 = table.getAllObjects(
DBTraditionalChineseObject.content.match("我们是程序员"));
Assert.assertTrue(matchObject2.size() == 1 &&
matchObject2.get(0).content.equals(obj.content));
- 欢迎使用 WCDB
- 基础教程
- 进阶教程
- 欢迎使用 WCDB
- 基础教程
- 进阶教程
- 欢迎使用 WCDB
- 基础教程
- 进阶教程
- 欢迎使用 WCDB
- 基础教程
- 进阶教程