博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
基于IKAnalyzer实现一个Elasticsearch中文分词插件
阅读量:7001 次
发布时间:2019-06-27

本文共 6857 字,大约阅读时间需要 22 分钟。

hot3.png

本文只是技术上的学习。没有对原作者不尊重之意。

虽然Elasticsearch有原生的中文插件elasticsearch-analysis-smartcn(实际上是lucence的org.apache.lucene.analysis.cn.smart.SmartChineseAnalyzer),但它似乎没能满足我的要求。比如我希望对文档中的“林夕”不分词(就是不要把它当成“林”,“夕”两个字索引),smartcn没法做到。

然后我找到了,以及。elasticsearch-analysis-ik已经有些时候没人维护了。而且它使用的httpclient来获取分词词典。总之各种纠结。

最后,我决定还是自己写一个吧。

原来IKAnalyzer的目录结构

├── IKAnalyzer.cfg.xml	├── ext.dic	├── org	│   └── wltea	│       └── analyzer	│           ├── cfg	│           │   ├── Configuration.java	│           │   └── DefaultConfig.java	│           ├── core	│           │   ├── AnalyzeContext.java	│           │   ├── CJKSegmenter.java	│           │   ├── CN_QuantifierSegmenter.java	│           │   ├── CharacterUtil.java	│           │   ├── IKArbitrator.java	│           │   ├── IKSegmenter.java	│           │   ├── ISegmenter.java	│           │   ├── LetterSegmenter.java	│           │   ├── Lexeme.java	│           │   ├── LexemePath.java	│           │   └── QuickSortSet.java	│           ├── dic	│           │   ├── DictSegment.java	│           │   ├── Dictionary.java	│           │   ├── Hit.java	│           │   ├── main2012.dic	│           │   └── quantifier.dic	│           ├── lucene	│           │   ├── IKAnalyzer.java	│           │   └── IKTokenizer.java	│           ├── query	│           │   ├── IKQueryExpressionParser.java	│           │   └── SWMCQueryBuilder.java	│           ├── sample	│           │   └── IKAnalyzerDemo.java	│           └── solr	│               └── IKTokenizerFactory.java	└── stopword.dic

加入构建脚本

我发现没有使用任何的构建工具。我不是说不使用构建工具就是不好,而是我已经习惯了使用构建工具,不用就没有安全感。所以,我第一步是给它加构建脚本。

同时,我把原来的IKAnalyzerDemo.java改成两个测试类。最后运行测试,确保我的修改没有破坏原有逻辑

└── src	    ├── main	    │   ├── java	    │   │   └── ......	    │   └── resources	    │       ├── IKAnalyzer.cfg.xml	    │       ├── main2012.dic	    │       ├── quantifier.dic	    │       └── stopword.dic	    └── test	        ├── java	        │   └── org	        │       └── wltea	        │           └── analyzer	        │               ├── IKAnalzyerTest.java	        │               └── LuceneIndexAndSearchTest.java	        └── resources	            ├── IKAnalyzer.cfg.xml	            ├── main2012.dic	            ├── quantifier.dic	            └── stopword.dic
build.gradle
apply plugin: 'java'				//apply plugin: 'checkstyle'		apply plugin: 'idea'				sourceCompatibility = 1.7		version = '1.0'				repositories {		    mavenCentral()		}				dependencies {		    compile(		            'org.apache.lucene:lucene-core:4.10.4',		            'org.apache.lucene:lucene-queryparser:4.10.4',		            'org.apache.lucene:lucene-analyzers-common:4.10.4'		    )				    testCompile group: 'junit', name: 'junit', version: '4.11'		}

将项目拆成core和lucence两个子项目

我发现IK实际上由两部分组成:真正的分词逻辑和扩展Lucence分析器的逻辑。可以想象得到

  1. 我们需要支持不同版本的Lucence
  2. 我们可以把IK的分词逻辑应用到其它的搜索引擎上

基于这两点,我决定把原有的项目分成两个子项目。并加上测试:

├── build.gradle		├── ik-analyzer-core		│   ├── build.gradle		│   └── src		│       ├── main		│       │   ├── java		│       │   │   └── .....		│       │   └── resources		│       └── test		├── ik-analyzer-lucence		│   ├── build.gradle		│   └── src		│       ├── main		│       │   └── java		│       │       └── org		│       │           └── wltea		│       │               └── analyzer		│       │                   ├── lucene		│       │                   │   ├── IKAnalyzer.java		│       │                   │   └── IKTokenizer.java		│       │                   └── query		│       │                       ├── IKQueryExpressionParser.java		│       │                       └── SWMCQueryBuilder.java		│       └── test		│           ├── java		│           │   └── .....		└── settings.gradle

创建Elasticsearch插件

一开始,我还想让Elasticsearch插件只依赖core子项目就好了。谁知道要实现Elasticsearch的插件还需要依赖Lucence。所以Elasticsearch插件需要依赖lucence子项目。

实现的过程发现Elasticsearch的版本之间有些不同,你可以对比下: 和

目前,Elasticsearch文档中,关于它的插件的概念和原理说的都非常少!

├── build.gradle	├── ik-analyzer-core	│   ├── ......	├── ik-analyzer-elasticseaarch-plugin	│   ├── build.gradle	│   └── src	│       └── main	│           ├── java	│           │   └── org	│           │       └── elasticsearch	│           │           └── plugin	│           │               └── ikanalyzer	│           │                   ├── IKAnalyzerComponent.java	│           │                   ├── IKAnalyzerModule.java	│           │                   └── IKAnalyzerPlugin.java	│           └── resources	│               └── es-plugin.properties	├── ik-analyzer-lucence	│   ├── .....	└── settings.gradle

######## es-plugin.properties plugin=org.elasticsearch.plugin.ikanalyzer.IKAnalyzerPlugin

重构Core子项目

目前IK还有一个问题没有解决:灵活扩展现有的词典。比如我希望将“林夕”加入词典,从而使其不分被索引成“林”,“夕”。这样的应用场景非常多的。以至于自己实现从远程读取词典的功能:

但是我觉得这样还是够好。比如,我期望从本地的sqlite中读取词典呢?所以,我将IK原有的关于配置的读取的逻辑抽取出来:

/**         * 加载主词典及扩展词典         */        private void loadMainDict(){                //建立一个主词典实例                _MainDict = new DictSegment((char)0);                //读取主词典文件        InputStream is = this.getClass().getClassLoader().getResourceAsStream(cfg.getMainDictionary());        if(is == null){                throw new RuntimeException("Main Dictionary not found!!!");        }                        try {                        BufferedReader br = new BufferedReader(new InputStreamReader(is , "UTF-8"), 512);                        String theWord = null;                        do {                                theWord = br.readLine();                                if (theWord != null && !"".equals(theWord.trim())) {                                        _MainDict.fillSegment(theWord.trim().toLowerCase().toCharArray());                                }                        } while (theWord != null);                                        } catch (IOException ioe) {                        System.err.println("Main Dictionary loading exception.");                        ioe.printStackTrace();                                        }finally{                        try {                                if(is != null){                    is.close();                    is = null;                                }                        } catch (IOException e) {                                e.printStackTrace();                        }                }                //加载扩展词典                this.loadExtDict();        }

其中cfg.getMainDictionary(),cfg是一个接口Configuration的实例,但是Dictionary假设getMainDictionary返回的一个文件的路径。所以,我认为这个接口的设计是没有意义的。

我们为什么不让cfg.getMainDictionary()直接返回Dictionary要求的词典内容呢,像这样:

/**	 * 加载主词典及扩展词典	 */	private void loadMainDict() {        //建立一个主词典实例        _MainDict = new DictSegment((char) 0);        for (char[] segment : cfg.loadMainDictionary()) {            _MainDict.fillSegment(segment);        }    }

这样,我们就可以实现像FileConfigurationHttpConfiguraionSqliteConfiguration,RedisConfiguration等任何你期望的扩展词典方式了。

但是,目前,我还没有实现任何的一种 :P

小结

实际上的重构和本文写的相差无几。还算比较顺利,要感谢原作者。这里,我还有一个问题没想通,就是如何打包才能让大家都方便用,比如方便在Elasticsearch中安装。希望大家能多给建议。

重构后的项目地址是:

转载于:https://my.oschina.net/zjzhai/blog/425484

你可能感兴趣的文章
无线网卡与本地连接不能同时使用&一机多网络的优先级设置
查看>>
解决Apache长时间占用内存大的问题,Apache 内存优化方法
查看>>
PHP运行环境之IIS FastCGI 进程意外退出解决办法
查看>>
单元测试
查看>>
JetBrains Rider 2018.1 汉化
查看>>
sql server 带有OUTPUT的INSERT,DELETE,UPDATE
查看>>
nginx rewrite语法格式
查看>>
文件系统管理 之 实例解说 fdisk 使用方法
查看>>
常见HTTP状态码
查看>>
[网摘学习]Git版本恢复命令reset
查看>>
操作hadoop的经验积累
查看>>
nginx中的break与last指令区别
查看>>
Android -- Options Menu,Context Menu,Popup Menu
查看>>
sublime2/3自总结经常使用快捷键(2的居多)
查看>>
微信企业号验证
查看>>
对象内存布局 (7)
查看>>
定时从一个数据库表中的数据存储到另外一个数据库中的表,而且怎么处理重复的数据?...
查看>>
请问set JAVA_OPTS的各项參数是什么意思?
查看>>
centOS使用.htaccess
查看>>
Linux安装JDK
查看>>