ELK专栏之IK分词器和Java api操作索引--05
Posted 热爱编程的大忽悠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ELK专栏之IK分词器和Java api操作索引--05相关的知识,希望对你有一定的参考价值。
ELK专栏之IK分词器和Java api操作索引--05
中文分词器之IK分词器
IK分词器的安装和使用
- 默认的standard分词器,仅适用于英文。
GET /_analyze
"analyzer": "standard",
"text": ["中华人民共和国人民大会堂"]
返回:
"tokens" : [
"token" : "中",
"start_offset" : 0,
"end_offset" : 1,
"type" : "<IDEOGRAPHIC>",
"position" : 0
,
"token" : "华",
"start_offset" : 1,
"end_offset" : 2,
"type" : "<IDEOGRAPHIC>",
"position" : 1
,
"token" : "人",
"start_offset" : 2,
"end_offset" : 3,
"type" : "<IDEOGRAPHIC>",
"position" : 2
,
"token" : "民",
"start_offset" : 3,
"end_offset" : 4,
"type" : "<IDEOGRAPHIC>",
"position" : 3
,
"token" : "共",
"start_offset" : 4,
"end_offset" : 5,
"type" : "<IDEOGRAPHIC>",
"position" : 4
,
"token" : "和",
"start_offset" : 5,
"end_offset" : 6,
"type" : "<IDEOGRAPHIC>",
"position" : 5
,
"token" : "国",
"start_offset" : 6,
"end_offset" : 7,
"type" : "<IDEOGRAPHIC>",
"position" : 6
,
"token" : "人",
"start_offset" : 7,
"end_offset" : 8,
"type" : "<IDEOGRAPHIC>",
"position" : 7
,
"token" : "民",
"start_offset" : 8,
"end_offset" : 9,
"type" : "<IDEOGRAPHIC>",
"position" : 8
,
"token" : "大",
"start_offset" : 9,
"end_offset" : 10,
"type" : "<IDEOGRAPHIC>",
"position" : 9
,
"token" : "会",
"start_offset" : 10,
"end_offset" : 11,
"type" : "<IDEOGRAPHIC>",
"position" : 10
,
"token" : "堂",
"start_offset" : 11,
"end_offset" : 12,
"type" : "<IDEOGRAPHIC>",
"position" : 11
]
● 我们想要的效果是:中华人民共和国,人民大会堂。而standard分词器不能满足我们的要求。
● IK分词器是目前最流行的ES中文分词器。
IK分词器的安装
IK分词器的基本知识
● ik_smart:会做最粗颗粒度的拆分,比如会将“中华人民共和国人民大会堂”拆分为“中华人民共和国“和“人民大会堂”。
● ik_max_word:会将文本做最细粒度的拆分,比如会将“中华人民共和国人民大会堂”拆分为“中华人民共和国”、“中华人民”、“中华”、“华人”、“人民共和国”、“人民大会堂”、“人民大会”、“大会堂”,会穷尽各种可能的组合;
IK分词器的使用
示例:
● 创建索引,存储的时候,使用ik_max_word,搜索的时候,使用ik_smart
PUT /my_index
"mappings":
"properties":
"name":
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_smart"
插入数据:
PUT /my_index/_doc/1
"name":"中华人民共和国人民大会堂"
搜索数据:
GET /my_index/_search?q=共和国
IK配置文件
IK分词器配置文件
IK分词器配置文件地址:ES/plugins/ik/config目录。
● IKAnalyzer.cfg.xml:用来配置自定义词库。
● main.dic(重要):IK原生内置的中文词库,总共有27万多条,只要是这些单词,都会被分在一起。
● preposition.dic:介词。
● quantifier.dic:放了一些单位相关的词,量词。
● suffix.dic:放了一些后缀。
● surname.dic:中国的姓氏。
● stopword.dic(重要):英文停用词。
自定义词库
自己建立词库:
○ 每年都会涌现一些特殊的流行的词,比如网红、蓝瘦香菇、喊麦等,一般不会出现在原生词典中。
○ 步骤:
①创建mydict.dic文件,补充最新的词语。
②IKAnalyzer.cfg.xml文件中,配置mydict.dic。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict">mydict.dic</entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords"></entry>
<!--用户可以在这里配置远程扩展字典 -->
<!-- <entry key="remote_ext_dict">words_location</entry> -->
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
③重启ES。
自己建立停用词:
○ 比如了、的、地、得等,我们可能并不想去建立索引,让别人搜索。
○ 步骤:
①创建ext_stopword.dic,补充常见的中文停用词。
②IKAnalyzer.cfg.xml文件中,配置ext_stopword.dic。
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict">mydict.dic</entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords">ext_stopword.dic</entry>
<!--用户可以在这里配置远程扩展字典 -->
<!-- <entry key="remote_ext_dict">words_location</entry> -->
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>
③重启ES。
使用mysql热更新词库
热更新
● 每次都是在ES的扩展词典中,手动添加新词语,很坑:
○ 每次添加完,都要重启ES,才能生效,非常麻烦。
○ ES是分布式的,可能有数百个节点,我们不可能每次都是一个一个节点去修改。
● 热更新:ES不停机,我们直接在外部的某个地方添加新的词语,ES就立即加载到这些新的词语。
● 热更新的方案:
○ 基于IK分词器的原生支持的热更新方案,部署一个web服务器,提供一个http接口,通过modified和tag两个http响应头,来提供词语的热更新。
○ 修改IK分词器的源码,然后手动支持从MySQL中每隔一段时间,自动加载新的词库,推荐方案。
步骤
- 下载源码
- 修改源码:
①创建HotDictReloadThread线程,不断的去调用Dictionary.getSingleton().reLoadMainDict()。
package org.wltea.analyzer.dic;
import org.apache.logging.log4j.Logger;
import org.wltea.analyzer.help.ESPluginLoggerFactory;
/**
* 加载字典线程
*
* @author 许大仙
* @version 1.0
* @since 2020-12-15 14:02
*/
public class HotDictReloadThread implements Runnable
private static Logger logger = ESPluginLoggerFactory.getLogger(HotDictReloadThread.class.getName());
@Override
public void run()
while (true)
logger.info("----------reload hot dict from mysql--------------");
Dictionary.getSingleton().reLoadMainDict();
②在pom.xml中添加mysql的驱动依赖:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
</dependency>
③数据库中新增es数据库以及对应的表的脚本:
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for hot_stopwords
-- ----------------------------
DROP TABLE IF EXISTS `hot_stopwords`;
CREATE TABLE `hot_stopwords` (
`stopword` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`id` bigint(20) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for hot_words
-- ----------------------------
DROP TABLE IF EXISTS `hot_words`;
CREATE TABLE `hot_words` (
`word` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
`id` bigint(20) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;
SET FOREIGN_KEY_CHECKS = 1;
④在项目的config目录下新建jdbc-reload.properties文件:
jdbc.url=jdbc:mysql://localhost:3306/es?useUnicode=true&characterEncoding=UTF-8&autoReconnect=true&useSSL=false&serverTimezone=GMT%2B8&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true
jdbc.user=root
jdbc.password=123456
jdbc.reload.sql=select word from hot_words
jdbc.reload.stopword.sql=select stopword as word from hot_stopwords
jdbc.reload.interval=5000
⑤修改Dictionary中的initial()方法:
/**
* 词典初始化 由于IK Analyzer的词典采用Dictionary类的静态方法进行词典初始化
* 只有当Dictionary类被实际调用时,才会开始载入词典, 这将延长首次分词操作的时间 该方法提供了一个在应用加载阶段就初始化字典的手段
*
* @return Dictionary
*/
public static synchronized void initial(Configuration cfg)
if (singleton == null)
synchronized (Dictionary.class)
if (singleton == null)
singleton = new Dictionary(cfg);
singleton.loadMainDict();
singleton.loadSurnameDict();
singleton.loadQuantifierDict();
singleton.loadSuffixDict();
singleton.loadPrepDict();
singleton.loadStopWordDict();
//*********mysql监控线程*********
new Thread(new HotDictReloadThread()).start();
if (cfg.isEnableRemoteDict())
// 建立监控线程
for (String location : singleton.getRemoteExtDictionarys())
// 10 秒是初始延迟可以修改的 60是间隔时间 单位秒
pool.scheduleAtFixedRate(new Monitor(location), 10, 60, TimeUnit.SECONDS);
for (String location : singleton.getRemoteExtStopWordDictionarys())
pool.scheduleAtFixedRate(new Monitor(location), 10, 60, TimeUnit.SECONDS);
⑥修改Dictionary中的loadMainDict()方法:
/**
* 加载主词典及扩展词典
*/
private void loadMainDict()
// 建立一个主词典实例
_MainDict = new DictSegment((char) 0);
// 读取主词典文件
Path file = PathUtils.get(getDictRoot(), Dictionary.PATH_DIC_MAIN);
loadDictFile(_MainDict, file, false, "Main Dict");
// 加载扩展词典
this.loadExtDict();
// 加载远程自定义词库
this.loadRemoteExtDict();
// ***********从MySQL中加载词典***********
this.loadMySQLExtDict();
⑥修改Dictionary中的loadMainDict()方法:
/**
* 加载主词典及扩展词典
*/
private void loadMainDict()
// 建立一个主词典实例
_MainDict = new DictSegment((char) 0);
// 读取主词典文件
Path file = PathUtils.get(getDictRoot(), Dictionary.PATH_DIC_MAIN);
loadDictFile(_MainDict, file, false, "Main Dict");
// 加载扩展词典
this.loadExtDict();
// 加载远程自定义词库
this.loadRemoteExtDict();
// ***********从MySQL中加载词典***********
this.loadMySQLExtDict();
private static Properties prop = new Properties();
static
try
Class.forName("com.mysql.jdbc.Driver");
catch (ClassNotFoundException ex)
logger.error("mysql driver not found exception", ex);
/**
* 从mysql加载热更新词典
*/
private void loadMySQLExtDict()
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try
Path file = PathUtils.get(getDictRoot(), "jdbc-reload.properties");
prop.load(new FileInputStream(file.toFile()));
logger.info("[==========]jdbc-reload.properties");
for(Object key : prop.keySet())
logger.info("[==========]" + key + "=" + prop.getProperty(String.valueOf(key)));
logger.info("[==========]query hot dict from mysql, " + prop.getProperty("jdbc.reload.sql") + "......");
conn = DriverManager.getConnection(
prop.getProperty("jdbc.url"),
prop.getProperty("jdbc.user"),
prop.getProperty("jdbc.password"));
stmt = conn.createStatement();
rs = stmt.executeQuery(prop.getProperty("jdbc.reload.sql"));
while(rs.next())
String theWord = rs.getString("word");
logger.info("[==========]hot word from mysql: " + theWord);
_MainDict.fillSegment(theWord.trim().toCharArray());
Thread.sleep(Integer.valueOf(String.valueOf(prop.get("jdbc.reload.interval"))));
catch (Exception e)
logger.error("erorr", e);
finally
if(rs != null)
try
rs.close();
catch (SQLException e)
logger.error("error", e);
if(stmt != null)
try
stmt.close();
catch (SQLException e)
logger.error("error", e);
if(conn != null)
try
conn.close();
catch以上是关于ELK专栏之IK分词器和Java api操作索引--05的主要内容,如果未能解决你的问题,请参考以下文章