🔹🔸◻️ 【译】FlexSearch v0.7.0 中文文档

3,648 阅读25分钟

FlexSearch v0.7.0

新版本终于可用了。FlexSearch v0.7.0是一个现代的重新实现,是从头开始开发的。结果是在每个方面都有了改进,涵盖了过去3年收集的大量增强和改进。

这个新版本与旧版本具有很好的兼容性,但是它可能需要在代码中执行一些迁移步骤。


在原始搜索速度方面,FlexSearch优于所有的搜索库,并提供灵活的搜索功能,如多字段搜索、语音转换或部分匹配。

根据所使用的选项,它还提供了内存效率最高的索引。FlexSearch引入了一种名为 “上下文索引”的新的评分算法,该算法基于预评分的词汇词典体系结构,实际执行查询的速度比其他库快了100万倍。FlexSearch还为您提供了一个非阻塞异步处理模型,以及web工作者,通过专用的平衡线程并行执行任何索引更新或查询。

支持平台:

  • 浏览器
  • node . js

获得最新的稳定版本(推荐)

BuildFileCDN
flexsearch.bundle.jsDownloadrawcdn.githack.com/nextapps-de…
flexsearch.light.jsDownloadrawcdn.githack.com/nextapps-de…
flexsearch.compact.jsDownloadrawcdn.githack.com/nextapps-de…
flexsearch.es5.js *Downloadrawcdn.githack.com/nextapps-de…
ES6 ModulesDownloadThe /dist/module/ folder of this Github repository
  • "flexsearch.es5.js"包含了支持EcmaScript 5的 pollyfills。

获取最新的npm

npm install flexsearch

上下文搜索

注意:这个特性在默认情况下是禁用的,因为它扩展了内存的使用。阅读这里获得更多关于和如何启用的信息。

FlexSearch引入了一种新的评分机制,称为上下文搜索(Contextual Search),它是由Thomas Wilkerling(该库的作者)发明的。上下文搜索将查询提升到一个全新的水平,但也需要一些额外的内存(取决于深度)。这个概念的基本思想是通过上下文来限制相关性,而不是通过对应文档的整个距离来计算相关性。通过这种方式,上下文搜索还可以改进基于大量文本数据的相关性查询的结果。

context of Relevance

加载库

索引有三种类型:

  1. Index是一种扁平的高性能索引,用于存储id-内容对。
  2. Worker / WorkerIndex也是一个平面索引,它存储id-content-pair,但在后台作为一个专用的Worker线程运行。
  3. 文档是多字段索引,可以存储复杂的JSON文档(也可以存在工作索引)。

大多数人可能只需要其中一个。

ES6 Modules (Browser):

import Index from "./index.js";
import Document from "./document.js";
import WorkerIndex from "./worker/index.js";

const index = new Index(options);
const document = new Document(options);
const worker = new WorkerIndex(options);
Bundle (Browser)
<html>
<head>
    <script src="js/flexsearch.bundle.js"></script>
</head>
...

Or via CDN:

<script src="https://cdn.jsdelivr.net/gh/nextapps-de/flexsearch@0.7.2/dist/flexsearch.bundle.js"></script>

AMD:

var FlexSearch = require("./flexsearch.js");
Load one of the builds from the folder dist within your html as a script and use as follows:

var index = new FlexSearch.Index(options);
var document = new FlexSearch.Document(options);
var worker = new FlexSearch.Worker(options);

Node.js

npm install flexsearch

In your code include as follows:

const { Index, Document, Worker } = require("flexsearch");

const index = new Index(options);
const document = new Document(options);
const worker = new Worker(options);

基本用法和变体

index.add(id, text);
index.search(text);
index.search(text, limit);
index.search(text, options);
index.search(text, limit, options);
index.search(options);

document.add(doc);
document.add(id, doc);
document.search(text);
document.search(text, limit);
document.search(text, options);
document.search(text, limit, options);
document.search(options);

worker.add(id, text);
worker.search(text);
worker.search(text, limit);
worker.search(text, options);
worker.search(text, limit, options);
worker.search(text, limit, options, callback);
worker.search(options);

worker从Index类型继承而不是从Document类型继承。因此,WorkerIndex基本上就像标准的FlexSearch Index一样工作。文档中的worker - support需要通过在创建过程中传递适当的选项{worker: true}来启用。

在Worker索引上调用的每个方法都被视为异步方法。您将获得一个Promise,或者您可以提供一个回调函数作为最后一个参数。

API 概述

全局方法

  • FlexSearch.registerCharset(name, charset)
  • FlexSearch.registerLanguage(name, language)

Index methods:

  • Index.add(id, string) *
  • Index.append(id, string) *
  • Index.update(id, string) *
  • Index.remove(id) *
  • Index.search(string, , ) *
  • Index.search(options) *
  • async Index.export(handler)
  • async Index.import(key, data)

WorkerIndex methods:

  • async Index.add(id, string)
  • async Index.append(id, string)
  • async Index.update(id, string)
  • async Index.remove(id)
  • async Index.search(string, , )
  • async Index.search(options)
  • async Index.export(handler) (WIP)
  • async Index.import(key, data) (WIP)

Document methods:

  • Document.add(, document) *
  • Document.append(, document) *
  • Document.update(, document) *
  • Document.remove(id || document) *
  • Document.search(string, , ) *
  • Document.search(options) *
  • async Document.export(handler)
  • async Document.import(key, data)
  • 对于这些方法中的每一个,都存在一个等效的异步方法:

Async Version:

  • async .addAsync( ... , )
  • async .appendAsync( ... , )
  • async .updateAsync( ... , )
  • async .removeAsync( ... , )
  • async .searchAsync( ... , )

Async方法将返回一个Promise,或者您可以传递一个回调函数作为最后一个参数。

方法export和import始终是异步的,对于基于worker的Index调用的每个方法也是异步的。

选项

FlexSearch是高度可定制的。使用正确的选项可以真正提高您的结果,以及内存经济和查询时间。

Index 选项

OptionValuesDescriptionDefault
preset"memory" "performance" "match" "score" "default"将配置文件作为快捷方式或作为自定义设置的基础。"default"
tokenize"strict" "forward" "reverse" "full"索引模式(标记器)。选择一个内置函数或传递一个自定义标记器函数。"strict"
cacheBoolean Number启用/禁用和/或设置缓存项的容量。当传递一个数字作为限制时,缓存自动平衡存储的条目与它们的流行程度相关。注意:当只使用“true”时,缓存是没有限制的,增长是无限的。false
resolutionNumber设置评分分辨率9
context启用/禁用上下文索引。当传递“true”作为值时,它将接受上下文的默认值。false
optimizeBoolean当启用时,它为索引使用一个内存优化的堆栈流。true
boostfunction(arr, str, int) => float在将内容索引到索引时使用的自定义增强函数。函数有这样的签名:' function (words[], term, index) => Float '。它有3个参数,你得到一个包含所有单词的数组,当前项和单词数组中当前项的索引。您可以应用自己的计算,例如,一个术语的出现并返回该因子(<1表示相关性降低,>1表示相关性增加)。注意:该特性目前仅限使用标记赋予器“strict”。null
charsetCharset Payload String (key)提供自定义字符集有效负载或传递内置字符集的一个键。"latin"
languageLanguage Payload String (key)提供自定义语言负载或传递内置语言的语言简写标志(ISO-3166)。null
encodefalse "default" "simple" "balance" "advanced" "extra" function(str) => [words]编码类型。选择一个内置函数或传递一个自定义编码函数。"default"
stemmerfalse String Functionfalse
filterfalse String Functionfalse
matcherfalse String Functionfalse
workerBoolean启用/禁用并设置运行工作线程的计数。false
documentDocument Descriptor包括文档索引和存储的定义。

Context 选项

OptionValuesDescriptionDefault
resolutionNumber设置上下文的评分分辨率1
depthfalse Number启用/禁用上下文索引,并设置上下文关联距离。深度是一个术语被认为相关的最大单词/标记数。1
bidirectionalBooleantrue

Document 选项

OptionValuesDescriptionDefault
idString"id"
tagfalse String"tag"
indexString Array Array
storeBoolean String Arrayfalse

字符集选项

OptionValuesDescriptionDefault
splitfalse RegExp String当使用非自定义标记器(内置的,例如。“前进”)。使用字符串/char或使用正则表达式(默认:/\W+/)。/[\W_]+/
rtlBoolean支持从右到左的编码。false
encodefunction(str) => [words]自定义编码函数。/lang/latin/default.js

搜索选项

OptionValuesDescriptionDefault
limitnumber设定结果的极限。100
offsetnumber应用偏移量(跳过项)。0
suggestBoolean在结果中启用建议。false

文档搜索选项

  • 除了上面的索引搜索选项还有以下。 Option | Values | Description | Default | | ---- | ---- | ---- | ---- | | index | String Array Array | | | | tag | String Array | | | | enrich | Boolean | 用相应的文档充实结果中的id。| false | | bool | "and" "or" | 设置在搜索多个字段或标记时使用的逻辑运算符。| "or" |

    Tokenizer(前缀搜索)

    令牌生成器还会影响所需的内存,如查询时间和部分匹配的灵活性。尝试选择最上层的这些标记器,以满足您的需要:

    OptionDescriptionExampleMemory Factor (n = length of word)
    "strict"索引整个单词foobar* 1
    "forward"向前向递增索引单词foobar foobar* n
    "reverse"在两个方向上递增索引单词foobar foobar* 2n - 1
    "full"索引所有可能的组合foobar foobar* n * (n - 1)

    Encoders

    编码还会影响所需的记忆,如查询时间和语音匹配。尝试选择这些编码器中最上层的符合您的需要,或传入自定义编码器:

    OptionDescriptionFalse-Positives压缩
    false关编码no0%
    "default"情况在敏感编码no0%
    "simple"区分大小写编码字符集规范化no~ 3%
    "balance"区分大小写编码字符集规范化文字转换no~ 30%
    "advanced"区分大小写编码字符集规范化文字转换语音规范化no~ 40%
    "extra"区分大小写编码字符集规范化文字转换语音规范化Soundex转换yes~ 65%
    function()通过*function(string):[words]*传递自定义编码

    使用

    创建一个索引

    var index = new Index();
    

    创建一个新的索引,并选择一个预置:

    var index = new Index("performance");
    

    使用自定义选项创建一个新的索引:

    var index = new Index({
        charset: "latin:extra",
        tokenize: "reverse",
        resolution: 9
    });
    

    创建一个新的索引,并使用自定义选项扩展预置:

    var index = new FlexSearch({
        preset: "memory",
        tokenize: "forward",
        resolution: 5
    });
    

    将文本项添加到索引中

    应该添加到索引中的每个内容都需要一个ID。如果您的内容没有ID,那么您需要通过传递索引或计数或其他作为ID的东西来创建一个ID(强烈建议使用类型number的值)。这些id是对给定内容的唯一引用。当您通过现有id更新或添加内容时,这一点非常重要。当不需要考虑引用时,可以简单地使用 count++ 之类的简单方法。

    Index.add(id, string)

    index.add(0, "John Doe");
    

    搜索项目

    Index.search(string | options, , )

    index.search("John");
    

    限制搜索结果数量:

    index.search("John", 10);
    

    检查已经建立索引的id是否存在

    你可以检查一个ID是否已经被索引:

    if(index.contain(1)){
        console.log("ID is already in index");
    }
    

    Async

    你可以在它的异步版本中调用每个方法,例如index。addAsync或index.searchAsync。

    你可以给每个异步函数分配回调函数:

    index.addAsync(id, content, function(){
        console.log("Task Done");
    });
    
    index.searchAsync(query, function(result){
        console.log("Results: ", result);
    });
    

    或者不传递回调函数,而是返回Promise:

    index.addAsync(id, content).then(function(){
        console.log("Task Done");
    });
    
    index.searchAsync(query).then(function(result){
        console.log("Results: ", result);
    });
    

    或者使用 async  await:

    async function add(){
        await index.addAsync(id, content);
        console.log("Task Done");
    }
    
    async function search(){
        const results = await index.searchAsync(query);
        console.log("Results: ", result);
    }
    

    附加内容

    你可以添加内容到现有的索引,如:

    index.append(id, content);
    

    这不会像执行索引时那样覆盖旧的索引内容。更新(id、内容)。记住这个索引。当id已经被索引时,Add (id, content)也将执行“更新”。

    附加的内容将有自己的上下文和自己的完整解析。因此,相关性不是堆叠的,而是有自己的上下文。 举个例子:

    index.add(0, "some index");
    index.append(0, "some appended content");
    
    index.add(1, "some text");
    index.append(1, "index appended content");
    

    当你查询index.search("index")时,你会得到索引id 1作为结果中的第一个条目,因为上下文从0开始附加数据(不是堆叠到旧上下文),这里"index"是第一项。

    如果您不想要这种行为,那么只需使用标准索引。添加(id,内容)并提供内容的完整长度。

    从索引中更新项

    Index.update(id, string)

    index.update(0, "Max Miller");
    

    从索引中移除项

    Index.remove(id)

    index.remove(0);
    

    添加自定义编译器

    标记器将单词/术语分解成组件或部分。 在创建/初始化过程中定义一个私有自定义标记器:

    var index = new FlexSearch({
        tokenize: function(str){
            return str.split(/\s-//g);
        }
    });
    

    记号赋予器函数获取一个字符串作为参数,并且必须返回一个表示单词或术语的字符串数组。在某些语言中,每个字符都是一个术语,也不通过空格分隔。

    添加特定于语言的词干和/或过滤器

    Stemmer:同一词的几种语言变体(如:词根)。“运行”和“运行”)

    过滤:从索引中完全过滤掉的单词的黑名单(例如:“and”,“to”或“be”) 在创建/初始化过程中分配一个私有的自定义词干或过滤器:

    var index = new FlexSearch({
        stemmer: {
            // object {key: replacement}
            "ational": "ate",
            "tional": "tion",
            "enci": "ence",
            "ing": ""
        },
        filter: [
            // array blacklist
            "in",
            "into",
            "is",
            "isn't",
            "it",
            "it's"
        ]
    });
    

    使用自定义过滤器,例如:

    var index = new FlexSearch({
        filter: function(value){
            // just add values with length > 1 to the index
            return value.length > 1;
        }
    });
    

    或者为语言全局分配词干/过滤器:

    Stemmer作为对象(键值对)传递,过滤器作为数组传递。

    FlexSearch.registerLanguage("us", {
        stemmer: { /* ... */ },
        filter:  [ /* ... */ ]
    });
    

    从右到左的支持

    当使用RTL时,将标记赋予器至少设置为“反向”或“完整”。 只需将字段“rtl”设置为true,并使用一个兼容的标记器:

    var index = new Index({
        encode: str => str.toLowerCase().split(/[^a-z]+/),
        tokenize: "reverse",
        rtl: true
    });
    

    索引文件(域搜索)

    文件描述符

    假设我们的文档有这样的数据结构:

    { 
        "id": 0, 
        "content": "some text"
    }
    

    旧语法FlexSearch v0.6.3(不再支持了!):

    const index = new Document({
        doc: {
            id: "id",
            field: ["content"]
        }
    });
    

    文档描述符发生了轻微的变化,不再有字段分支,而只是应用更高一级,因此key成为选项的主要成员。 对于新的语法,字段“doc”被重命名为document,字段“field”被重命名为index:

    const index = new Document({
        document: {
            id: "id",
            index: ["content"]
        }
    });
    
    index.add({ 
        id: 0, 
        content: "some text"
    });
    

    字段id描述了id或惟一键在文档中的位置。默认键在未传递时默认获取值id,所以你可以将上面的示例缩短为:

    const index = new Document({
        document: {
            index: ["content"]
        }
    });
    

    成员索引有一个字段列表,您希望从文档中对这些字段建立索引。当只选择一个字段时,您可以传递一个字符串。当同时使用默认键id时,这将缩短为:

    const index = new Document({ document: "content" });
    index.add({ id: 0, content: "some text" });
    

    假设你有几个字段,你可以添加多个字段到索引:

    var docs = [{
        id: 0,
        title: "Title A",
        content: "Body A"
    },{
        id: 1,
        title: "Title B",
        content: "Body B"
    }];
    
    const index = new Document({
        id: "id",
        index: ["title", "content"]
    });
    

    你可以为每个字段传递自定义选项:

    const index = new Document({
        id: "id",
        index: [{
            field: "title",
            tokenize: "forward",
            optimize: true,
            resolution: 9
        },{
            field:  "content",
            tokenize: "strict",
            optimize: true,
            resolution: 5,
            minlength: 3,
            context: {
                depth: 1,
                resolution: 3
            }
        }]
    });
    

    字段选项在传递全局选项时也会被继承,例如:

    const index = new Document({
        tokenize: "strict",
        optimize: true,
        resolution: 9,
        document: {
            id: "id",
            index:[{
                field: "title",
                tokenize: "forward"
            },{
                field: "content",
                minlength: 3,
                context: {
                    depth: 1,
                    resolution: 3
                }
            }]
        }
    });
    

    注意:字段“content”中的上下文选项也会被相应的字段选项继承,而这个字段选项是由全局选项继承的。

    嵌套数据字段(复杂对象)

    假设文档数组看起来更复杂(有嵌套的分支等),例如:

    {
      "record": {
        "id": 0,
        "title": "some title",
        "content": {
          "header": "some text",
          "footer": "some text"
        }
      }
    }
    

    然后使用冒号分隔的符号"root:child"来定义文档描述符中的层次结构:

    const index = new Document({
        document: {
            id: "record:id",
            index: [
                "record:title",
                "record:content:header",
                "record:content:footer"
            ]
        }
    });
    

    只需添加想要查询的字段。不要向索引中添加字段,只需要在结果中(但没有查询)。为此目的,您可以独立于索引存储文档(请参阅下面的内容)。

    当你想通过一个字段进行查询时,你必须将你在文档中定义的字段的精确键作为字段名(冒号语法)传递:

    index.search(query, {
        index: [
            "record:title",
            "record:content:header",
            "record:content:footer"
        ]
    });
    

    Same as:

    index.search(query, [    "record:title",    "record:content:header",    "record:content:footer"]);
    

    使用领域的选择:

    index.search([{
        field: "record:title",
        query: "some query",
        limit: 100,
        suggest: true
    },{
        field: "record:title",
        query: "some other query",
        limit: 100,
        suggest: true
    }]);
    

    您可以使用不同的查询通过相同的字段执行搜索。

    当传递特定于字段的选项时,您需要为每个字段提供完整的配置。它们不像文档描述符那样被继承。

    复杂的文档

    你的文件需要遵循2条规则:

    1. 文档不能以根索引处的Array开头。这将引入顺序数据,目前还不支持。有关此类数据的解决方案,请参见下面。
    [ // not allowed as document start!
      {
        "id": 0,
        "title": "title"
      }
    ]
    
    1. id不能嵌套在数组中(父字段也不能是数组)。这将引入顺序数据,目前还不支持。有关此类数据的解决方案,请参见下面。
    {
      "records": [ // not allowed when ID or tag lives inside!
        {
          "id": 0,
          "title": "title"
        }
      ]
    }
    

    下面是一个受支持的复杂文档的例子:

    {
      "meta": {
        "tag": "cat",
        "id": 0
      },
      "contents": [
        {
          "body": {
            "title": "some title",
            "footer": "some text"
          },
          "keywords": ["some", "key", "words"]
        },
        {
          "body": {
            "title": "some title",
            "footer": "some text"
          },
          "keywords": ["some", "key", "words"]
        }
      ]
    }
    

    对应的文档描述符(当所有字段都应该被索引时)如下所示:

    const index = new Document({
        document: {
            id: "meta:id",
            tag: "meta:tag",
            index: [
                "contents[]:body:title",
                "contents[]:body:footer",
                "contents[]:keywords"
            ]
        }
    });
    

    同样,在搜索时,必须使用与字段定义相同的冒号分隔的字符串。

    index.search(query, { 
        index: "contents[]:body:title"
    });
    

    不支持的文档(顺序数据)

    这个例子打破了上面的两个规则:

    [ // not allowed as document start!
      {
        "tag": "cat",
        "records": [ // not allowed when ID or tag lives inside!
          {
            "id": 0,
            "body": {
              "title": "some title",
              "footer": "some text"
            },
            "keywords": ["some", "key", "words"]
          },
          {
            "id": 1,
            "body": {
              "title": "some title",
              "footer": "some text"
            },
            "keywords": ["some", "key", "words"]
          }
        ]
      }
    ]
    

    你需要应用某种结构规范化。

    这样的数据结构的解决方案是这样的:

    const index = new Document({
        document: {
            id: "record:id",
            tag: "tag",
            index: [
                "record:body:title",
                "record:body:footer",
                "record:body:keywords"
            ]
        }
    });
    
    function add(sequential_data){
    
        for(let x = 0, data; x < sequential_data.length; x++){
    
            data = sequential_data[x];
    
            for(let y = 0, record; y < data.records.length; y++){
    
                record = data.records[y];
    
                index.add({
                    id: record.id,
                    tag: data.tag,
                    record: record
                });
            }
        }  
    }
    
    // now just use add() helper method as usual:
    
    add([{
        // sequential structured data
        // take the data example above
    }]);
    

    当文档数据只有一个索引作为外层数组时,可以跳过第一个循环。

    向索引中添加/更新/删除文档

    只需将文档数组(或单个对象)传递给索引:

    index.add(docs);
    

    用单个对象或对象数组更新索引:

    index.update({
        data:{
            id: 0,
            title: "Foo",
            body: {
                content: "Bar"
            }
        }
    });
    

    从索引中删除单个对象或对象数组:

    index.remove(docs);
    

    当id是已知的,你也可以简单地删除(更快):

    index.remove(id);
    

    Join / Append数组

    在上面的复杂示例中,字段关键字是一个数组,但这里的标记没有像关键字[]那样的括号。它也将检测数组,但不是将每个条目附加到一个新的上下文,数组将被加入到一个大字符串并添加到索引中。

    这两种添加数组内容的方法的区别在于搜索时的相关性。当使用语法字段[]通过append()将数组的每个项添加到它自己的上下文时,最后一项的相关性与第一个项并发。当您在符号中留下括号时,它将把数组连接到一个以空格分隔的字符串。在这里,第一个条目的相关性最高,而最后一个条目的相关性最低。

    所以假设上面例子中的关键字是根据其受欢迎程度的相关性预先排序的,那么您希望保持这个顺序(相关性信息)。为此目的,不要在符号中添加括号。否则,它将在新的评分上下文中接受条目(旧的顺序将丢失)。

    您还可以使用左括号符号来获得更好的性能和更小的内存占用。当您不需要条目的相关性粒度时使用它。

    域搜索

    搜索所有字段:

    index.search(query);
    

    搜索特定的字段:

    index.search(query, { index: "title" });
    

    搜索给定的字段集:

    index.search(query, { index: ["title", "content"] });
    

    Same as:

    index.search(query, ["title", "content"]);
    

    向每个字段传递自定义修饰符和查询:

    index.search([{
        field: "content",
        query: "some query",
        limit: 100,
        suggest: true
    },{
        field: "content",
        query: "some other query",
        limit: 100,
        suggest: true
    }]);
    

    您可以使用不同的查询通过相同的字段执行搜索。

    结果集

    结果集的模式:

    fields[] => { field, result[] => { document }}

    第一个索引是应用查询的字段数组。每个字段都有一个记录(对象),它有两个属性“field”和“result”。“result”也是一个数组,包括这个特定字段的结果。结果可以是一个id数组,也可以是用存储的文档数据充实的数组。

    非丰富的结果集现在看起来像:

    [{    field: "title",    result: [0, 1, 2]
    },{
        field: "content",
        result: [3, 4, 5]
    }]
    

    一个丰富的结果集现在看起来像:

    [{
        field: "title",
        result: [
            { id: 0, doc: { /* document */ }},
            { id: 1, doc: { /* document */ }},
            { id: 2, doc: { /* document */ }}
        ]
    },{
        field: "content",
        result: [
            { id: 3, doc: { /* document */ }},
            { id: 4, doc: { /* document */ }},
            { id: 5, doc: { /* document */ }}
        ]
    }]
    

    当使用pluck而不是“field”时,你可以明确地选择一个字段,并得到一个平面表示:

    index.search(query, { pluck: "title", enrich: true });
    
    [
        { id: 0, doc: { /* document */ }},
        { id: 1, doc: { /* document */ }},
        { id: 2, doc: { /* document */ }}
    ]
    

    这个结果集代替了“布尔搜索”。与将bool逻辑应用于嵌套对象不同,您可以在结果集上动态地应用自己的逻辑。这为你如何处理结果打开了巨大的能力。因此,各个领域的结果不再被压缩成一个结果。它保留了一些重要的信息,比如领域的名称以及每个领域结果的相关性,这些不再被混合。

    默认情况下,字段搜索将应用带有布尔“或”逻辑的查询。对于给定的查询,每个字段都有自己的结果。

    有一种情况是仍然支持bool属性。当你想将默认的"or"逻辑从字段搜索转换为"and"时,例如:

    index.search(query, { 
        index: ["title", "content"],
        bool: "and" 
    });
    

    您只会得到两个字段中都包含查询的结果。

    Tags

    就像ID的键一样,定义标签的路径:

    const index = new Document({
        document: { 
            id: "id",
            tag: "tag",
            index: "content"
        }
    });
    
    index.add({
        id: 0,
        tag: "cat",
        content: "Some content ..."
    });
    

    你的数据也可以有多个标签作为一个数组:

    index.add({
        id: 1,
        tag: ["animal", "dog"],
        content: "Some content ..."
    });
    

    你可以通过以下方式执行特定标签的搜索:

    index.search(query, { 
        index: "content",
        tag: "animal" 
    });
    

    这只会给你带有给定标签的结果。

    搜索时使用多个标签:

    index.search(query, { 
        index: "content",
        tag: ["cat", "dog"]
    });
    

    这将给出用给定标记之一标记的结果。

    默认情况下,多个标签将作为布尔值“或”应用。它只需要存在其中一个标签。

    这是仍然支持bool属性的另一种情况。当你想将默认的"or"逻辑从标签搜索转换为"and"时,例如:

    index.search(query, { 
        index: "content",
        tag: ["dog", "animal"],
        bool: "and"
    });
    

    您只会得到包含两个标记的结果(在本例中,只有一个记录具有标记“dog”和“animal”)。

    Tag 搜索

    你也可以从一个或多个标签获取结果,当没有查询被传递:

    index.search({ tag: ["cat", "dog"] });
    

    在这种情况下,结果集看起来像:

    [{
        tag: "cat",
        result: [ /* all cats */ ]
    },{
        tag: "dog",
        result: [ /* all dogs */ ]
    }]
    

    Limit & Offset

    默认情况下,每个查询限制为100个条目。无边界查询会导致问题。您需要将限制设置为一个选项来调整大小。

    您可以为每个查询设置限制和偏移量:

    index.search(query, { limit: 20, offset: 100 });
    

    不能预先计算结果集的大小。这是FlexSearch的设计限制。当你真的需要统计你能够分页的所有结果时,只需分配一个足够高的限制,并返回所有的结果,并手动应用你的分页偏移量(这也适用于服务器端)。FlexSearch足够快,这不是问题。

    文档存储

    只有文档索引才能拥有存储。在仅存储id -content对时,也可以使用文档索引而不是平面索引来实现此功能。

    您可以独立定义哪些字段应该被索引,哪些字段应该被存储。这样就可以索引不应该包含在搜索结果中的字段。

    不要在以下情况下使用商店:一个id数组作为结果就足够了,或者2。您已经将内容/文档存储在其他地方(索引之外)。

    当设置store属性时,您必须包含所有应该显式存储的字段(类似于白名单)。

    如果未设置store属性,则将原始文档存储为备用文档。

    这将把整个原始内容添加到store:

    const index = new Document({
        document: { 
            index: "content",
            store: true
        }
    });
    
    index.add({ id: 0, content: "some text" });
    

    从内部存储器访问文档

    你可以从store获得索引文档:

    var data = index.get(1);
    

    您可以通过以下方法直接更新/更改存储内容,而无需更改索引:

    index.set(1, data);
    

    要更新存储和索引,只需使用index。更新索引。添加或index.append。

    当您执行查询时,无论它是文档索引还是平面索引,您都会得到一个id数组。

    您可以选择丰富的查询结果与存储的内容自动:

    index.search(query, { enrich: true });
    

    你的结果现在看起来像:

    [{
        id: 0,
        doc: { /* content from store */ }
    },{
        id: 1,
        doc: { /* content from store */ }
    }]
    

    配置存储(推荐)

    这将从文档中添加特定的字段到存储中(存储中不需要ID):

    const index = new Document({
        document: {
            index: "content",
            store: ["author", "email"]
        }
    });
    
    index.add(id, content);
    

    您可以独立地配置什么应该被索引,什么应该被存储。强烈建议您尽可能地使用它。

    下面是一个配置doc和store的有用示例:

    const index = new Document({
        document: { 
            index: "content",
            store: ["author", "email"] 
        }
    });
    
    index.add({
        id: 0,
        author: "Jon Doe",
        email: "john@mail.com",
        content: "Some content for the index ..."
    });
    

    你可以通过查询内容,并将得到存储的值:

    index.search("some content", { enrich: true });
    

    你的结果现在看起来像:

    [{
        field: "content",
        result: [{
            id: 0,
            doc: {
                author: "Jon Doe",
                email: "john@mail.com",
            }
        }]
    }]
    

    “作者”和“电子邮件”字段都没有索引。

    链式

    简单的链方法如下:

    var index = FlexSearch.create()
                          .addMatcher({'â': 'a'})
                          .add(0, 'foo')
                          .add(1, 'bar');
    
    index.remove(0).update(1, 'foo').add(2, 'foobar');
    

    使上下文得分

    创建索引并使用默认上下文:

    var index = new FlexSearch({
    
        tokenize: "strict",
        context: true
    });
    

    创建一个索引,并为上下文应用自定义选项:

    var index = new FlexSearch({
    
        tokenize: "strict",
        context: { 
            resolution: 5,
            depth: 3,
            bidirectional: true
        }
    });
    

    上下文索引实际上只支持标记赋予器“strict”。

    上下文索引需要额外的内存,具体取决于深度。

    自动平衡缓存(根据流行程度)

    你需要在创建索引的时候初始化缓存和它的限制:

    const index = new Index({ cache: 100 });
    
    const results = index.searchCache(query);
    

    使用缓存的一个常见场景是键入时的自动完成或即时搜索。

    当传递一个数字作为限制时,缓存会自动平衡存储的条目与它们的流行程度相关。

    当只使用“true”时,缓存是不受限制的,执行速度实际上快了2-3倍(因为不需要运行平衡器)。

    work 并行 (Browser + Node.js)

    v0.7.0中的新worker模型被划分为文档中的“字段”(1 worker = 1字段索引)。通过这种方式,工人能够完全解决任务(子任务)。这种模式的缺点是,它们在存储内容时可能没有达到完美的平衡(字段可能有不同的内容长度)。另一方面,没有迹象表明平衡存储会带来任何好处(它们总共需要相同的数量)。

    当使用文档索引时,只需应用选项"worker":

    const index = new Document({
        index: ["tag", "name", "title", "text"],
        worker: true
    });
    
    index.add({ 
        id: 1, tag: "cat", name: "Tom", title: "some", text: "some" 
    }).add({
        id: 2, tag: "dog", name: "Ben", title: "title", text: "content" 
    }).add({ 
        id: 3, tag: "cat", name: "Max", title: "to", text: "to" 
    }).add({ 
        id: 4, tag: "dog", name: "Tim", title: "index", text: "index" 
    });
    
    Worker 1: { 1: "cat", 2: "dog", 3: "cat", 4: "dog" }
    Worker 2: { 1: "Tom", 2: "Ben", 3: "Max", 4: "Tim" }
    Worker 3: { 1: "some", 2: "title", 3: "to", 4: "index" }
    Worker 4: { 1: "some", 2: "content", 3: "to", 4: "index" }
    

    当您在所有字段中执行字段搜索时,该任务将在所有worker中得到完美的平衡,这些worker可以独立地解决它们的子任务。

    worker 索引

    上面我们已经看到,文档将自动为每个字段创建worker。您还可以直接创建WorkerIndex(类似于使用Index而不是Document)。

    作为ES6模块使用:

    import WorkerIndex from "./worker/index.js";
    const index = new WorkerIndex(options);
    index.add(1, "some")
         .add(2, "content")
         .add(3, "to")
         .add(4, "index");
    

    或者当使用绑定版本时:

    var index = new FlexSearch.Worker(options);
    index.add(1, "some")
         .add(2, "content")
         .add(3, "to")
         .add(4, "index");
    

    这样的WorkerIndex的工作原理与创建的Index实例几乎相同。

    WorkerIndex只支持所有方法的异步变体。这意味着当你在WorkerIndex上调用index.search()时,它也会以与index.searchAsync()相同的方式在async中执行。

    工作线程(node . js)

    Node.js的工作线程模型基于“工作线程”,工作方式完全相同:

    const { Document } = require("flexsearch");
    
    const index = new Document({
        index: ["tag", "name", "title", "text"],
        worker: true
    });
    

    或者为非文档索引创建单个worker实例:

    const { Worker } = require("flexsearch");
    const index = new Worker({ options });
    

    Worker异步模型(最佳实践)

    一个worker将总是作为async执行。在一个查询方法调用中,你总是应该处理返回的promise(例如,使用await)或传递一个回调函数作为最后一个参数。

    const index = new Document({
        index: ["tag", "name", "title", "text"],
        worker: true
    });
    

    所有的请求和子任务将并行运行(将“所有完成的任务”按优先级排序):

    index.searchAsync(query, callback);
    index.searchAsync(query, callback);
    index.searchAsync(query, callback);
    

    同样(将“所有完成的任务”按优先级排序):

    index.searchAsync(query).then(callback);
    index.searchAsync(query).then(callback);
    index.searchAsync(query).then(callback);
    

    或者当你只有一个回调,当所有的请求都完成时,简单地使用' Promise.all() ',它也会优先考虑“所有完成的任务”:

    Promise.all([
        index.searchAsync(query),
        index.searchAsync(query),
        index.searchAsync(query)
    ]).then(callback);
    

    在Promise.all()的回调函数中,您还将获得一个结果数组,作为您输入的每个查询的第一个参数。

    当使用await时,你可以对顺序进行优先级排序(优先级为“第一个任务完成”),逐个解决请求,只是并行处理子任务:

    await index.searchAsync(query);
    await index.searchAsync(query);
    await index.searchAsync(query);
    

    对index.add()、index.append()、index.remove()或index.update()也一样。这里有一个库没有禁用的特殊情况,但是在使用Workers时需要记住。

    当你在工作索引上调用“synchronized”版本时:

    index.add(doc);
    index.add(doc);
    index.add(doc);
    // contents aren't indexed yet,
    // they just queued on the message channel 
    

    当然,您可以这样做,但请记住主线程没有用于分布式工作任务的额外队列。在长循环中运行这些函数会在内部通过worker.postMessage()向消息通道大量发送内容。幸运的是,浏览器和Node.js会自动为你处理这些传入任务(只要有足够的空闲RAM可用)。当在工作索引上使用“synchronized”版本时,内容不会在下一行被索引,因为默认情况下所有调用都被视为async。

    当向索引中添加/更新/删除大量内容(或高频率)时,建议使用async版本和async/await,以在长进程中保持低内存占用。

    Export / Import

    Export

    出口略有变化。现在出口的产品包括几个较小的部件,而不是一大块。你需要传递一个有两个参数"key"和"data"的回调函数。这个回调函数被每个部分调用,例如:

    index.export(function(key, data){ 
        
        // you need to store both the key and the data!
        // e.g. use the key for the filename and save your data
        
        localStorage.setItem(key, data);
    });
    

    将数据导出到localStorage并不是一个很好的实践,但是如果不考虑大小,可以选择使用它。导出主要用于在Node.js中使用,或者存储希望从服务器委托给客户端的索引。

    导出的大小对应于库的内存消耗。为了减少导出的大小,您必须使用内存占用更少的配置(使用底部的表来获取关于配置及其内存分配的信息)。

    当你的保存程序异步运行时,你必须返回一个Promise:

    index.export(function(key, data){ 
        
        return new Promise(function(resolve){
            
            // do the saving as async
    
            resolve();
        });
    });
    

    您不能为“fastupdate”特性导出额外的表。这些表存在引用,当存储时,它们完全序列化,变得太大。lib将自动为您处理这些问题。当导入数据时,索引自动禁用“fastupdate”。

    Import

    在导入数据之前,需要首先创建索引。对于文档索引,提供与导出数据时使用的文档描述符相同的文档描述符。此配置不会存储在导出中。

    var index = new Index({ ... });
    

    要导入数据,只需传递一个键和数据:

    index.import(key, localStorage.getItem(key));
    

    您需要导入每个密钥!否则,索引不能工作。您需要存储导出中的键,并将此键用于导入(键的顺序可能不同)。

    这只是演示,不推荐,因为你可能有其他键在你的localStorage不支持作为导入:

    var keys = Object.keys(localStorage);
    for(let i = 0, key; i < keys.length (>); i++){    
        key = keys[i];
        index.import(key, localStorage.getItem(key));
    }
    

    Best Practices

    使用数字id 在向索引添加内容时,建议使用数字id值作为参考。传递的id的字节长度会显著影响内存消耗。如果这是不可能的,您应该考虑使用索引表并将id映射为索引,这就变得非常重要,特别是在对大量内容使用上下文索引时。

    分割的复杂性 当你可以的时候,试着将内容按类别划分,并将它们添加到自己的索引中,例如:

    var action = new FlexSearch();
    var adventure = new FlexSearch();
    var comedy = new FlexSearch();
    

    这样,您还可以为每个类别提供不同的设置。这实际上是执行模糊搜索的最快方法。

    为了使这个解决方案更可扩展,你可以使用一个简短的助手:

    var index = {};
    
    function add(id, cat, content){
        (index[cat] || (
            index[cat] = new FlexSearch
        )).add(id, content);
    }
    
    function search(cat, query){
        return index[cat] ?
            index[cat].search(query) : [];
    }
    

    添加内容到索引:

    add(1, "action", "Movie Title");
    add(2, "adventure", "Movie Title");
    add(3, "comedy", "Movie Title");
    

    执行查询:

    var results = search("action", "movie title"); // --> [1]
    

    按类别划分索引可以显著提高性能。