由于工作中需要做同义词,今天看了看solr的实现以及源码,记个笔记。我看的solr的版本是5.5.3.
在solr的schema.xml中(5.x的版本是managed-schema文件)已经有实例了,截图如下:
<fieldType name="text_general" class="solr.TextField" positionIncrementGap="100"> <!-- 切记,只有TextField中才可以设置type=index或者type=query的两个analyzer,我看过源码,其他的时候不起作用--> <analyzer type="index"> <tokenizer class="solr.StandardTokenizerFactory"/> <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" /> <!-- in this example, we will only use synonyms at query time <filter class="solr.SynonymFilterFactory" synonyms="index_synonyms.txt" ignoreCase="true" expand="false"/> --> <filter class="solr.LowerCaseFilterFactory"/> </analyzer> <analyzer type="query"> <tokenizer class="solr.StandardTokenizerFactory"/> <!--使用的分词器的工厂--> <filter class="solr.StopFilterFactory" ignoreCase="true" words="stopwords.txt" /> <!--添加停止词过滤器工厂--> <filter class="solr.SynonymFilterFactory" synonyms="synonyms.txt" ignoreCase="true" expand="true"/> <!-- 添加同义词过滤器工厂 --> <filter class="solr.LowerCaseFilterFactory"/> <!--添加转化为小写的过滤器工厂--> </analyzer> </fieldType>
关键就是配置的SynonyFilterFactory,我们看看他的源码:
SynonymFilterFactory类时继承自TokenFilterFactory类,后者为所有的TokenFilter工厂的抽象类,上图中的LowerCaseFilterFactory也是继承自这个类。TokenFilterFactory这个抽象类最关键的是create(TokenStream)方法,即根据tokenizer的操作继续添加操作,这个倒是很容易理解,在TokenizerFactory中也有个create方法,不过是没有参数的,因为此时还没有生成tokenStream。
明白了TokenFilterFactory之后,再看一下SynonymFilterFactory类的结构,直接看他的构造方法吧:
public SynonymFilterFactory(Map<String,String> args) {//map即在配置中的参数,比如上面的synonyms,ignoreCase,expand super(args); ignoreCase = getBoolean(args, "ignoreCase", false);//ignoreCase表示再分词匹配的时候要不要忽略大小写 synonyms = require(args, "synonyms");//同义词词典的位置 format = get(args, "format");//解析同义词词典的时候使用的格式化对象,即怎么从同义词词典中解读同义词 expand = getBoolean(args, "expand", true);//这个也是在解读同义词词典的时候的参数,用语言不好描述,等用到再说 analyzerName = get(args, "analyzer");//这个是在词典表中读取一个字符串要进行分词,这个指定使用的分词器 tokenizerFactory = get(args, "tokenizerFactory");//这个和上面的意思一样,只不过使用的是工厂模式 if (analyzerName != null && tokenizerFactory != null) { throw new IllegalArgumentException("Analyzer and TokenizerFactory can't be specified both: " + analyzerName + " and " + tokenizerFactory); } 。。。//忽略不重要的参数 }
然后我们看看对同义词词典的加载,在org.apache.lucene.analysis.synonym.SynonymFilterFactory.inform(ResourceLoader)方法中有个loadSynonyms方法,顾名思义,就是加载同义词词典表的方法
protected SynonymMap loadSynonyms(ResourceLoader loader, String cname, boolean dedup, Analyzer analyzer) throws IOException, ParseException {//第二个参数是使用的格式化对象的名字,第三个是加载表的时候要不要排除重复的,第四个是使用的分词器 CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder() .onMalformedInput(CodingErrorAction.REPORT) .onUnmappableCharacter(CodingErrorAction.REPORT); SynonymMap.Parser parser; Class<? extends SynonymMap.Parser> clazz = loader.findClass(cname, SynonymMap.Parser.class); try { parser = clazz.getConstructor(boolean.class, boolean.class, Analyzer.class).newInstance(dedup, expand, analyzer);//这个是用来生成最终的SynonyMap,里面最关键的是一个FST和一个类似于HashMap的BytesRefHash. } catch (Exception e) { throw new RuntimeException(e); } List<String> files = splitFileNames(synonyms);//可以传多个同义词词典文件 for (String file : files) { decoder.reset(); try (final Reader isr = new InputStreamReader(loader.openResource(file), decoder)) {//读取同义词词典文件 parser.parse(isr);//解析,将同义词进入fst,最关键的就是这个方法。 } } return parser.build();//建造一个SynonymMap }
现在最关键的就是一个parser.parse方法了,这里的parser是SolrSynonymParser类,继承自Builder类,用于构造fst
public void parse(Reader in) throws IOException, ParseException { LineNumberReader br = new LineNumberReader(in);//读取一行 try { addInternal(br);//调用addInternal方法 。。。//去掉无用代码 } private void addInternal(BufferedReader in) throws IOException { String line = null; while ((line = in.readLine()) != null) { if (line.length() == 0 || line.charAt(0) == '#') {//注释的一行 continue; // ignore empty lines and comments } // TODO: we could process this more efficiently. String sides[] = split(line, "=>");//根据=>分开, if (sides.length > 1) { // 如果当前根据=>之后的个数大于1,即aa=>bb格式的同义词词典 if (sides.length != 2) { throw new IllegalArgumentException("more than one explicit mapping specified on the same line"); } String inputStrings[] = split(sides[0], ",");//左边的部分,用,分开, CharsRef[] inputs = new CharsRef[inputStrings.length]; for (int i = 0; i < inputs.length; i++) { inputs[i] = analyze(unescape(inputStrings[i]).trim(), new CharsRefBuilder());//对左边的部分使用置顶的分词器处理 } //对右边的部分做和左边同样的操作 String outputStrings[] = split(sides[1], ","); CharsRef[] outputs = new CharsRef[outputStrings.length]; for (int i = 0; i < outputs.length; i++) { outputs[i] = analyze(unescape(outputStrings[i]).trim(), new CharsRefBuilder()); } // these mappings are explicit and never preserve original for (int i = 0; i < inputs.length; i++) {//循环左边的词,每一个词都添加所有的右边的词作为同义词,即如果配置的是a=>b,c,添加到synonymMap的是a->b,a->c,但是不会添加b->c,b->a,c->a。 for (int j = 0; j < outputs.length; j++) { add(inputs[i], outputs[j], false); } } } else {//这个是没有=>的形式,仅仅有a,b,c,这些组成同义词 String inputStrings[] = split(line, ","); CharsRef[] inputs = new CharsRef[inputStrings.length]; for (int i = 0; i < inputs.length; i++) { inputs[i] = analyze(unescape(inputStrings[i]).trim(), new CharsRefBuilder()); } if (expand) {//通过if-else的对比expand的意思是要不要反方向的添加,比如a,b,c,如果不是expand,就会只记录b->a,c->a,不会记录a->b,a->c,b->c,c->b。 // all pairs for (int i = 0; i < inputs.length; i++) {//这两个for循环,将形成所有的同义词对,比如上面的 for (int j = 0; j < inputs.length; j++) { if (i != j) { add(inputs[i], inputs[j], true);//add是添加到BytesRefHash的数组里面返回在数组中的位置,并记录到fst中,这样从fst中根据term获得时先获得的是在bytesRefHash的位置(可以是多个),然后再根据位置获得同义词, } } } } else { // all subsequent inputs map to first one; we also add inputs[0] here // so that we "effectively" (because we remove the original input and // add back a synonym with the same text) change that token's type to // SYNONYM (matching legacy behavior): for (int i = 0; i < inputs.length; i++) { add(inputs[i], inputs[0], false);//如果不是expand,那么只会将同义词映射到第一个词上。 } } } } }
至此已经搞懂了同义词的用法,不过对于fst还是没有涉及到,不过已经可以使用了 同义词了。还有问题,如何更新同义词词典呢,总不能要更新后重启吧,还有就是词典保留在solr中修改起来特别麻烦,如何能更加方便的动态修改词典呢,这个留在下一个博客中,只要稍微做些修改,就能实现动态的添加词典,动态的做同义词。
相关推荐
solr的同义词库,文档是从其他地方拿的,放到这里供大家下载,共2万1千条
源码中包含了丰富的注释和示例,帮助开发者深入理解Solr的设计思想和实现细节。 总结来说,Solr 6.2.0是一个强大的全文搜索引擎,它的分布式特性、实时性以及丰富的功能使得它成为企业级搜索应用的理想选择。通过...
在这个"solr软件包扩展词典可停词配置学习和开发文档"中,我们将深入探讨Solr在Linux环境下的安装、扩展词典的使用以及停词配置的关键知识点。 **1. Solr的Linux环境安装** 在Linux环境下安装Solr,首先确保系统...
在处理中文文本时,一个关键的组件就是中文分词器,而IK(Intelligent Chinese)分词器是Solr中常用的中文分词工具之一。本文将深入探讨"solr5的ik中文分词器源码"的相关知识点。 1. **IK分词器概述**: IK分词器...
这里我们将围绕"solr-9.0.0-src.tgz"这个源码包,详细探讨其主要组成部分、核心功能以及开发过程中的关键知识点。 1. **Solr架构** Solr的架构基于Lucene,一个强大的全文搜索引擎库。它提供了分布式、可扩展、高...
描述中提到“直接修改web.xml设置一下solr core路径”,这是Solr中的关键配置步骤。web.xml文件是Tomcat的部署描述符,用于配置应用的行为。在Solr中,每个独立的搜索实例被称为一个"Core",每个Core有自己的配置...
通过深入研究 Solr 6.6.0 的源码,开发者可以了解其内部工作原理,掌握如何优化索引构建、查询性能,以及如何利用其高级特性,如分面搜索、地理位置搜索、复杂查询语法等。这有助于开发出更加高效、定制化的搜索引擎...
5. **配置日志**:Solr支持日志记录,可以在`conf/log4j.properties`文件中配置日志级别和输出方式。 6. **配置安全**:为了提高安全性,可以配置身份验证和授权机制。具体配置方法参见官方文档。 #### 三、Solr...
- **synonyms.txt**:同义词配置。 - **spellings.txt**:拼写检查配置。 #### 八、测试与验证 完成配置后,我们需要进行一些测试操作来验证Solr是否能够正确地从MySQL中导入数据,并提供准确的搜索结果。 1. **...
确保提交的文件名与之前创建的分词文档相匹配,并且索引字段的名称在 Solr 配置和分词文档中一致。 完成以上步骤后,你就可以通过 Solr 的 Web 界面进行搜索测试,验证分词配置是否生效。如果你遇到乱码问题,检查 ...
在Solr中,mmseg4j 可以通过添加相应的 jar 包来集成,以便对中文内容进行索引和搜索。 配置 Solr 5.4 开发环境的第一步是下载 Solr 的发行包。访问 Apache 官方网站获取最新版本的 Solr 5.4,解压缩到你选择的目录...
在Solr中,配置文件是至关重要的,它们决定了Solr的行为和性能。以下将详细介绍`solr所需配置文件`及其作用。 1. **solrconfig.xml**: 这是Solr的核心配置文件,定义了索引和查询的处理方式。例如,它包含了索引...
在Solr中,Analyzer是处理文本输入的组件,它负责将输入的文本转换为可搜索的术语。对于中文,我们需要一个能理解并处理中文语法和词汇的分词器。IK Analyzer是一个流行的中文分词工具,专为Java设计,适用于Lucene...
在 Solr 8 中,为了确保系统的安全性和数据的隐私性,配置用户登录验证是非常重要的步骤。本文将详细介绍如何对手动配置 Solr 8 的用户登录验证。 首先,我们需要了解 Solr 的安全组件——Jetty 容器。Solr 默认...
在Solr中,通常会使用第三方分词库,如ikanalyzer、jieba分词或者pkuseg等,这些库能处理中文的复杂性,如多音字、成语和词语的组合。 在配置Solr的中文分词时,有以下几个关键步骤: 1. **安装分词库**:下载并...
在本压缩包中,包含了Solr运行所需的一些关键组件,包括Tomcat服务器、IK分词器以及Solr自身的配置文件。下面将详细介绍这些组件及其在Solr中的作用。 首先,Tomcat是一个流行的Java应用服务器,常被用作部署...
当前资源为Apache官方提供的solr-5.3.0-src.tgz源码,包括Lucene和solrj的源码,方便大家学习使用!此外还整理了一套有关solr-5.3.0的window和linux版包以及官方说明文档apache-solr-ref-guide-5.3.pdf,有需要的...