1、首先内存中建立词典树。
包括:主词典树、 停止词词典树 、量词词典树
数据结构:树 (或说字典树) ,子节点<=3时,数组存储DictSegment[] childrenArray; >3时迁移到hashMap Map<Character , DictSegment> childrenMap;
根:DictSegment _MainDict = new DictSegment((char)0);
字典树中的每个节点用DictSegmenter表示,每个节点的下一级节点分支使用Array或者Map来存储
主词典的子节点(也是子节点最多的节点)
segmentMapHashMap<K,V> (id=76)
entrySetHashMap$EntrySet (id=81)
keySetnull
loadFactor0.8
modCount5860
size5860
tableHashMap$Entry<K,V>[8192] (id=85)
threshold6553
valuesnull
/**
* 词典管理类,单例模式
*/
public class Dictionary {
/*
* 词典单子实例
*/
private static Dictionary singleton;
/*
* 主词典对象
*/
private DictSegment _MainDict;
/*
* 停止词词典
*/
private DictSegment _StopWordDict;
/*
* 量词词典
*/
private DictSegment _QuantifierDict;
/**
* 配置对象
*/
private Configuration cfg;
。。。
}
2、遍历子分词器分词
对每个字符分别让三个分词器处理,如果符合字符类型等条件,将匹配到的词元加入结果集。
依次遍历三个分词器:LetterSegmenter(字母分词),CN_QuantifierSegmenter(数、量词分词),CJKSegmenter(中文分词)
CN_QuantifierSegmenter分出中文数字”一“,量词”个“
LetterSegmenter:连续的英文字母作为一个词元,如:can
第一次调用l = context.getNextLexeme()) == null,会分词,取词时候不为空。
/**
* 分词,获取下一个词元
* @return Lexeme 词元对象
* @throws IOException
*/
public synchronized Lexeme next()throws IOException{
Lexeme l = null;
while((l = context.getNextLexeme()) == null ){
/*
* 从reader中读取数据,填充buffer
* 如果reader是分次读入buffer的,那么buffer要 进行移位处理
* 移位处理上次读入的但未处理的数据
*/
int available = context.fillBuffer(this.input);
if(available <= 0){
//reader已经读完
context.reset();
return null;
}else{
//初始化指针
context.initCursor();
do{
//遍历子分词器
for(ISegmenter segmenter : segmenters){
segmenter.analyze(context);
}
//字符缓冲区接近读完,需要读入新的字符
if(context.needRefillBuffer()){
break;
}
//向前移动指针
}while(context.moveCursor());
//重置子分词器,为下轮循环进行初始化
for(ISegmenter segmenter : segmenters){
segmenter.reset();
}
}
//对分词进行歧义处理
this.arbitrator.process(context, this.cfg.useSmart());
//将分词结果输出到结果集,并处理未切分的单个CJK字符
context.outputToResult();
//记录本次分词的缓冲区位移
context.markBufferOffset();
}
return l;
}
3、中文分词器analyz函数。
从主词典树查到第一个字符节点DictSegment,如果是完全匹配(单字词)加入结果集,如果是词的前缀,加入临时命中集tmpHits,下一个字符优先从tmpHits中匹配。
如果完全匹配(节点没有叶子),把词(路径)加入结果集,如果不匹配,从tmpHits移除。tmpHits匹配完后,还得去主词典再查是否为单字词或下一个词的前缀。
public void analyze(AnalyzeContext context) {
if(CharacterUtil.CHAR_USELESS != context.getCurrentCharType()){ //zw:当前字符类型不是 无类型
//优先处理tmpHits中的hit
if(!this.tmpHits.isEmpty()){
//处理词段队列
Hit[] tmpArray = this.tmpHits.toArray(new Hit[this.tmpHits.size()]);
for(Hit hit : tmpArray){
hit = Dictionary.getSingleton().matchWithHit(context.getSegmentBuff(), context.getCursor() , hit);
if(hit.isMatch()){
//输出当前的词
Lexeme newLexeme = new Lexeme(context.getBufferOffset() , hit.getBegin() , context.getCursor() - hit.getBegin() + 1 , Lexeme.TYPE_CNWORD);
context.addLexeme(newLexeme);
if(!hit.isPrefix()){//不是词前缀,hit不需要继续匹配,移除
this.tmpHits.remove(hit);
}
}else if(hit.isUnmatch()){
//hit不是词,移除
this.tmpHits.remove(hit);
}
}
}
//*********************************
//再对当前指针位置的字符进行单字匹配
Hit singleCharHit = Dictionary.getSingleton().matchInMainDict(context.getSegmentBuff(), context.getCursor(), 1); //zw:主词典匹配singleton._MainDict
if(singleCharHit.isMatch()){//首字成词
//输出当前的词
Lexeme newLexeme = new Lexeme(context.getBufferOffset() , context.getCursor() , 1 , Lexeme.TYPE_CNWORD);
context.addLexeme(newLexeme);
//同时也是词前缀
if(singleCharHit.isPrefix()){
//前缀匹配则放入hit列表
this.tmpHits.add(singleCharHit);
}
}else if(singleCharHit.isPrefix()){//首字为词前缀
//前缀匹配则放入hit列表
this.tmpHits.add(singleCharHit);
}
}else{
//遇到CHAR_USELESS字符
//清空队列
this.tmpHits.clear();
}
//判断缓冲区是否已经读完
if(context.isBufferConsumed()){
//清空队列
this.tmpHits.clear();
}
//判断是否锁定缓冲区
if(this.tmpHits.size() == 0){
context.unlockBuffer(SEGMENTER_NAME);
}else{
context.lockBuffer(SEGMENTER_NAME);
}
}
/**
* 表示一次词典匹配的命中
*/
public class Hit {
//Hit不匹配
private static final int UNMATCH = 0x00000000;
//Hit完全匹配
private static final int MATCH = 0x00000001;
//Hit前缀匹配
private static final int PREFIX = 0x00000010;
//该HIT当前状态,默认未匹配
private int hitState = UNMATCH;
//记录词典匹配过程中,当前匹配到的词典分支节点
private DictSegment matchedDictSegment;
/*
* 词段开始位置
*/
private int begin;
/*
* 词段的结束位置
*/
private int end;
。。。
}
**
* IK词元对象
*/
public class Lexeme implements Comparable<Lexeme>{
//lexemeType常量
//未知
public static final int TYPE_UNKNOWN = 0;
//英文
public static final int TYPE_ENGLISH = 1;
//数字
public static final int TYPE_ARABIC = 2;
//英文数字混合
public static final int TYPE_LETTER = 3;
//中文词元
public static final int TYPE_CNWORD = 4;
//中文单字
public static final int TYPE_CNCHAR = 64;
//日韩文字
public static final int TYPE_OTHER_CJK = 8;
//中文数词
public static final int TYPE_CNUM = 16;
//中文量词
public static final int TYPE_COUNT = 32;
//中文数量词
public static final int TYPE_CQUAN = 48;
//词元的起始位移
private int offset;
//词元的相对起始位置
private int begin;
//词元的长度
private int length;
//词元文本
private String lexemeText;
//词元类型
private int lexemeType;
。。。
}
4、匹配到后向链表集合添加词元
/**
* 向链表集合添加词元
* @param lexeme
*/
boolean addLexeme(Lexeme lexeme){
Cell newCell = new Cell(lexeme);
if(this.size == 0){
this.head = newCell;
this.tail = newCell;
this.size++;
return true;
}else{
if(this.tail.compareTo(newCell) == 0){//词元与尾部词元相同,不放入集合
return false;
}else if(this.tail.compareTo(newCell) < 0){//词元接入链表尾部
this.tail.next = newCell;
newCell.prev = this.tail;
this.tail = newCell;
this.size++;
return true;
}else if(this.head.compareTo(newCell) > 0){//词元接入链表头部
this.head.prev = newCell;
newCell.next = this.head;
this.head = newCell;
this.size++;
return true;
}else{
//从尾部上逆
Cell index = this.tail;
while(index != null && index.compareTo(newCell) > 0){
index = index.prev;
}
if(index.compareTo(newCell) == 0){//词元与集合中的词元重复,不放入集合
return false;
}else if(index.compareTo(newCell) < 0){//词元插入链表中的某个位置
newCell.prev = index;
newCell.next = index.next;
index.next.prev = newCell;
index.next = newCell;
this.size++;
return true;
}
}
}
return false;
}
//分词结果集数据结构
/**
* IK分词器专用的Lexem快速排序集合
*/
class QuickSortSet {
//链表头
private Cell head;
//链表尾
private Cell tail;
//链表的实际大小
private int size;
//内部类,QuickSortSet集合单元
class Cell implements Comparable<Cell>{
private Cell prev;
private Cell next;
private Lexeme lexeme;
。。。
}
。。。
}
5、取词
过滤停止词,设置词元文本。
/** * 返回lexeme * * 同时处理合并 * @return */ Lexeme getNextLexeme(){ //从结果集取出,并移除第一个Lexme Lexeme result = this.results.pollFirst(); while(result != null){ //数量词合并 this.compound(result); if(Dictionary.getSingleton().isStopWord(this.segmentBuff , result.getBegin() , result.getLength())){ //是停止词继续取列表的下一个 result = this.results.pollFirst(); }else{ //不是停止词, 生成lexeme的词元文本,输出 result.setLexemeText(String.valueOf(segmentBuff , result.getBegin() , result.getLength())); break; } } return result; }
6、例子
对"这是一个中文分词的例子,IKAnalyer can analysis english text too"分词结果如下:
0 - 2 : 这是 | CN_WORD //0表示词的开始位置,2表示结束位置,这是 表示词的内容,CN_WORD表示中文
2 - 4 : 一个 | CN_WORD
2 - 3 : 一 | TYPE_CNUM //TYPE_CNUM 表示中文数词
3 - 5 : 个中 | CN_WORD
3 - 4 : 个 | COUNT //COUNT 表示 中文量词
4 - 6 : 中文 | CN_WORD
6 - 8 : 分词 | CN_WORD
9 - 11 : 例子 | CN_WORD
12 - 21 : ikanalyer | ENGLISH
22 - 25 : can | ENGLISH
26 - 34 : analysis | ENGLISH
35 - 42 : english | ENGLISH
43 - 47 : text | ENGLISH
48 - 51 : too | ENGLISH
这个例子使用非智能分词:细粒度输出所有可能的切分结果 。 智能分词: 合并数词和量词,对分词结果进行歧义判断
相关推荐
源码分析 - `org.ansj.domain`: 定义了分词过程中涉及的实体类,如Term(词语)、Nature(词性)等。 - `org.ansj.splitWord`: 包含了分词的主要实现,如`Analysis`接口和`Analyzer`类。 - `org.ansj.util`: 提供...
本资源包包含了Elasticsearch 6.4.3在Windows平台上的完整安装包,源码,以及与之配套的IK分词器和Kibana可视化工具。 1. **Elasticsearch 6.4.3**: 这是主要的搜索和分析引擎,提供了分布式、近实时的搜索和分析...
分词流程通常包括创建`TokenStream`,迭代`TokenStream`获取分词,以及关闭`TokenStream`。 ```java TokenStream tokenStream = analyzer.tokenStream("content", new StringReader(text)); TermAttribute termAttr...
3. **分析器接口**:IKAnalyzer提供了Analyzer接口,开发者可以通过实现这个接口来定制自己的分词逻辑。 4. **热更新**:IKAnalyzer支持在运行时动态更新词典,无需重启服务,这在应对新词汇快速涌现的场景下非常...
总之,ES-IK分词器7.4版本与Apache Maven的结合使用,大大简化了分词器的开发、测试和部署流程。通过Maven的依赖管理功能,我们可以轻松地追踪和管理项目的依赖,确保分词器与Elasticsearch的兼容性。同时,Maven的...
接下来,我们将详细探讨IKAnalyzer的核心特性、工作流程以及如何利用源码进行二次开发。 IKAnalyzer的主要目标是提高中文分词的准确性和效率。它采用了基于词典的分词策略,通过大量的词汇库来识别和分割中文词汇。...
其次,IKAnalyzer 的工作流程主要包括预处理、分词和后处理三个阶段。预处理阶段主要是对输入文本进行编码转换和特殊字符处理;分词阶段通过扫描字典并运用正向最大匹配和逆向最大匹配算法进行初步分词;后处理阶段...
在源码中,`org.wltea.analyzer.core.IKSegmenter`类是整个分词流程的入口,它负责初始化词典和各种分词策略。词典数据存储在`org.wltea.analyzer.dic.Dictionary`类中,词典加载部分是性能优化的关键。IK Analyzer...
**IKAnalyzer 2012源码分析** IKAnalyzer 是一个开源的、基于Java实现的中文分词器,主要用于提高中文信息处理的效率。这款工具广泛应用于搜索引擎、文本挖掘、信息检索等领域,其核心功能是对中文文本进行有效的...
源码分析 源码包中包含的主要文件有: - `src/main/java`:存放IKAnalyzer的Java源代码,如分词器、过滤器、词典管理等类。 - `src/test/java`:测试用例,用于验证分词效果。 - `src/main/resources`:资源...
现在我们来关注IK分词器的源码分析。源码位于"elasticsearch-analysis-ik-8.5.0"目录下,主要包含以下几个部分: 1. **Analyzer**:这是IK分词器的核心类,负责对输入文本进行分词。它继承自Elasticsearch的`org....
**Elasticsearch中文分词器IK的源码分析** Elasticsearch是一款强大的开源搜索引擎,广泛应用于数据检索、分析和管理。在处理中文文本时,一个关键的组件是中文分词器,它负责将中文句子拆分成有意义的词汇,以便...
《Spark大数据中文分词统计Scala语言工程源码详解》 在大数据处理领域,Apache Spark以其高效、易用的特性成为了众多开发者的首选工具。而针对中文数据,分词是进行文本分析的重要步骤,尤其在诸如情感分析、关键词...
1. **安装分词器**:首先需要在项目中引入分词器的NuGet包或源码。 2. **配置分词器**:在Lucene.NET的索引创建阶段,需要配置Analyzer类,指定使用特定的分词器。例如,使用IK Analyzer可以创建`IKAnalyzer ...
IK Analyzer 是一个开源的、基于Java实现的中文分词器,专为Java开发人员设计,广泛应用于搜索引擎、信息检索系统、日志分析等领域。这款工具的主要目标是提供一个轻量级、高性能且易于扩展的中文分词解决方案。2012...
- 尽管IKAnalyzer的源代码通常可以在GitHub上找到,但这个压缩包包含了已编译的jar包,用户无需自行下载源码并编译,可以直接使用,简化了部署流程。 总之,IKAnalyzer2012_u6是中文分词领域的一个强大工具,它...
3. **分词流程** - 加载词典:在启动时,IKAnalyzer 会加载词典文件,创建词汇表。 - 初始化:设置分词参数,如是否开启模糊匹配等。 - 分词:对输入的文本进行正向最大匹配,遇到未知词时,可能启用模糊搜索或...
标题中的“Solr java分词器”指的是Apache Solr中使用的Java实现的分词工具,主要负责对输入的文本进行词汇分析,以便于索引和搜索。Solr是一款基于Lucene的开源搜索引擎,它提供了更高级别的API和配置选项,包括...
以上就是使用Lucene进行中文分词搜索的基本流程。在实际应用中,还需要考虑诸如性能优化、近实时搜索、多线程索引和搜索等问题。此外,LuceneDB.java可能包含了与数据库交互的部分,将数据库中的数据导入到Lucene...
通过对GJSearchDemo的分析,你可以更直观地了解Lucene的工作流程。 总结来说,Lucene 3.6提供了一套完整的全文检索解决方案,从文本预处理、索引构建到查询执行,再到结果展示,涵盖了搜索引擎的各个环节。熟练掌握...