`
破晓初
  • 浏览: 3438 次
文章分类
社区版块
存档分类
最新评论

读IKAnalyzer源码之IK启动

阅读更多
IKAnalyzer非常流行的中文分词器,对中文切词有兴趣的朋友可以看看。

基本所有的框架都分两部分,一是:框架的初始化,也就是框架启动;二是:调用框架,让框架为我们做一些事。我们今天先来看看一下IK的初始化过程。
IKAnalyzer可以说一个非常流行的分词器了,但我觉得IKAnalyzer的代码写一般。

废话就不多说了,直接看源码吧。IKAnalyzer挂在Google上,直接到google下就好了,文档之类的都很全。

假设1,你用过IKAnalyzer,对IKAnalyzer有了解。或者,你想了解分词器。
假设2,你已经下到源码,建了一个Java SE的项目。

假如,如果你满足两个假设,那就一起来读读IKAnalyzer的源码吧。


新建一个类,主方法的内容如下:
public static void main(String[] args){  
    IKSegmenter seg = new IKSegmenter(new StringReader("中国人民"), true);  
    try {  
        Lexeme lex = seg.next();  
        while(lex != null){  
            System.out.println(lex);  
            lex = seg.next();  
        }  
    } catch (IOException e) {  
        e.printStackTrace();  
    }  
}  

PS:我们从IKSegment而不是从IKAnalyzer开始,如果从IKAnalyzer这个类开始,那么我们需要再加入Lucene3.1以上(Lucene4.0以下)的包。而且,基本都上Lucene的内容,暂时不关注,可以自行了解。


强调一下,IKAnalyzer的作者非常有心,看过源码的朋友应该知道,他注释很多。先赞一个。


从IKSegment的构造方法进来,
/** 
 * IK分词器构造函数 
 * @param input  
 * @param useSmart 为true,使用智能分词策略 
 *  
 * 非智能分词:细粒度输出所有可能的切分结果 
 * 智能分词: 合并数词和量词,对分词结果进行歧义判断 
 */  
public IKSegmenter(Reader input , boolean useSmart){  
    this.input = input;  
    this.cfg = DefaultConfig.getInstance();  
    this.cfg.setUseSmart(useSmart);  
    this.init();  
}  


首先看 DefaultConfig.getInstance() 里面是这样的:
/** 
 * 返回单例 
 * @return Configuration单例 
 */  
public static Configuration getInstance(){  
    return new DefaultConfig();  
}  

由于IKSegment的初始化实际上是IK自己做的,在IKTokenizer。因此,这种写法完全没有问题,它初始化次数绝对是串行。假如,我说假如哈。假如这个东西是并行,这个单例的写法就不合适了,会造成多次初始化。有兴趣的话要以比对着Dictionary.initial(this.cfg)看。当然,这不是我们今天关注的内容。
同时这个地方做资源初始化,也就是解析配置文件,有兴趣的朋友可以看看。


进到 this.init() 方法,代码也是中规中矩的没什么难点,但这是我们今天的关注点。从代码上可以看到,首先初始化了词典,然后初始化分词环境,……
/** 
 * 初始化 
 */  
private void init(){  
    //初始化词典单例  
    Dictionary.initial(this.cfg);  
    //初始化分词上下文  
    this.context = new AnalyzeContext(this.cfg);  
    //加载子分词器  
    this.segmenters = this.loadSegmenters();  
    //加载歧义裁决器  
    this.arbitrator = new IKArbitrator();  
} 

在词典的初始化过程,

   
/** 
     * 加载主词典及扩展词典 
     */  
    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 {  
            // 这里转码,原因是我们的词典都是UTF-8,但运行环境不一定。  
            BufferedReader br = new BufferedReader(new InputStreamReader(is , "UTF-8"), 512);  
            String theWord = null;  
            do {  
                //到了这里,我建议大家先看一目词典文件(/src/org/wltea/analyzer/dic/main2012.dic)。现在,我们基本了解词典文件是怎么样的了。因此,你也就知道,它为什么是一行一行读取的了。  
                theWord = br.readLine();  
                if (theWord != null && !"".equals(theWord.trim())) {  
                    // toLowerCase()是规范化,毕竟索引不分大小写。  
                    _MainDict.fillSegment(theWord.trim().toLowerCase().toCharArray());  
                }  
            } while (theWord != null);  
        }  
        ……  
    }   
 

我觉得这段,如果按下来这么写兴许更好一些,你们觉得呢。
String theWord = br.readLine();  
while(theWord != null){  
    if(!theWord.trim().isEmpty()){  
        _MainDict.fillSegment(theWord.trim().toLowerCase().toCharArray());  
    }  
}  

由_MainDict.fillSegment(theWord.trim().toLowerCase().toCharArray());进来,历经千辛万苦来到这里。下面的代码可以谓重中之得哈。有一个数据结构叫,字典树。不管你知不知道,它都在下面了。这里简单说一下吧,详情请登陆www.baidu.com。


字典树,跟什么B树啊、B+树啊、B-树都差不多,因此跟二叉树不大一样。它是这样的,它广度不定,小于字典字数(“一二三一”算三个字)。然后,节点带有一个状态,即是不是完整词、词性之类的。


最最重要的一点,它的字必须是重复引用的。即有一个拥有字典全部单字的集合。比如,对一个英文字词,它有一个集合放里26个字母,然后所有的节点的都从这个集合引用,从而降低内存的开销。


也就是说,它一棵广度不定、深度不定,并引用来自同一集合的对象的树。我能说的,这就这么多了,剩下的交给百度吧。


看一下下面的代码,也都比较简单,就是字典树的实现。
private synchronized void fillSegment(char[] charArray , int begin , int length , int enabled){  
    // 装箱,然后获取字典表中的文字对象  
    Character beginChar = new Character(charArray[begin]);  
    Character keyChar = charMap.get(beginChar);  
      
    //字典中没有该字,则将其添加入字典表  
    if(keyChar == null){  
        charMap.put(beginChar, beginChar);  
        keyChar = beginChar;  
    }   

     
    //搜索当前节点的存储,查询对应keyChar的keyChar,如果没有则创建。如:已有“中国”,再来一个“中国人”的时候,它首先发现“中”已存在,那就直接,一点也不客气。若没有,那只能创建了。 
   
DictSegment ds = lookforSegment(keyChar , enabled);  
    if(ds != null){  
        //处理keyChar对应的segment  
        if(length > 1){  
            //词元还没有完全加入词典树  
            ds.fillSegment(charArray, begin + 1, length - 1 , enabled);  
        }else if (length == 1){  
            //已经是词元的最后一个char,设置当前节点状态为enabled,  
            //enabled=1表明一个完整的词,enabled=0表示从词典中屏蔽当前词  
            ds.nodeState = enabled;  
        }  
    }  
} 


这段代码非常坑爹,但能提高效率。从这里可以知道,作者在效率方面下很大的功夫。有得有失,因此代码很复杂。中心思想是数组的效率比集合框架的效率高。具体是这样的,当前节点的宽度不大于ARRAY_LENGTH_LIMIT(默认是3)时,用数组来存;当宽度大于时,用HashMap来存。

/** 
* 查找本节点下对应的keyChar的segment    
*  
* @param keyChar 
* @param create  =1如果没有找到,则创建新的segment ; =0如果没有找到,不创建,返回null 
* @return 
*/  
rivate DictSegment lookforSegment(Character keyChar ,  int create){  
  
DictSegment ds = null;  
  
if(this.storeSize <= ARRAY_LENGTH_LIMIT){  
    //获取数组容器,如果数组未创建则创建数组  
    DictSegment[] segmentArray = getChildrenArray();              
    //搜寻数组  
    DictSegment keySegment = new DictSegment(keyChar);  
    int position = Arrays.binarySearch(segmentArray, 0 , this.storeSize, keySegment);  
    if(position >= 0){  
        ds = segmentArray[position];  
    }  
  
    //遍历数组后没有找到对应的segment  
    if(ds == null && create == 1){  
        ds = keySegment;  
        if(this.storeSize < ARRAY_LENGTH_LIMIT){  
            //数组容量未满,使用数组存储  
            segmentArray[this.storeSize] = ds;  
            //segment数目+1  
            this.storeSize++;  
            Arrays.sort(segmentArray , 0 , this.storeSize);  
              
        }else{  
            //数组容量已满,切换Map存储  
            //获取Map容器,如果Map未创建,则创建Map  
            Map<Character , DictSegment> segmentMap = getChildrenMap();  
            //将数组中的segment迁移到Map中  
            migrate(segmentArray ,  segmentMap);  
            //存储新的segment  
            segmentMap.put(keyChar, ds);  
            //segment数目+1 ,  必须在释放数组前执行storeSize++ , 确保极端情况下,不会取到空的数组  
            this.storeSize++;  
            //释放当前的数组引用  
            this.childrenArray = null;  
        }  
    }             
      
}else{  
    //获取Map容器,如果Map未创建,则创建Map  
    Map<Character , DictSegment> segmentMap = getChildrenMap();  
    //搜索Map  
    ds = (DictSegment)segmentMap.get(keyChar);  
    if(ds == null && create == 1){  
        //构造新的segment  
        ds = new DictSegment(keyChar);  
        segmentMap.put(keyChar , ds);  
        //当前节点存储segment数目+1  
        this.storeSize ++;  
    }  
}  
  
  
return ds; 


接下来,还会加载量词词典及扩展词典,内容基本一样,自己看看就好了。整个过程也就优化这个位置有复杂,其它都还好,主要是要知道作者为什么要用先数组再用集合,这个地方清楚了,也就没啥问题。
后续,作者在用数组装的时候,用了折半搜索。我个人觉得,你竟然用了“折半查找”,为什么要长度设为3呢?这样首配对元素1,再配对元素2,最后配对元素3,对吧?而我们顺序查找也是这样的,所以我觉得这个参数值不大合理。或许作者有其它想法,我还没有参悟。

字典加载完了,回到IKSegmenter#init()继续下一步,即是初始化上下文环境。这个环境,分词器分词的环境,它提供分词基本条件和记录着分词状态。比如,需要分词的东西、分好词的词片、分词进到哪个位置等等等
。对上下文有兴趣的可以看看。
1
0
分享到:
评论

相关推荐

    IKAnalyzer源码+配置+智能分词类

    **IKAnalyzer概述** IKAnalyzer是一款基于Java实现的开源中文分词器,主要应用于搜索引擎和文本分析领域。它是由尹国平(iK)发起并维护的项目,旨在提供一个高效且易用的中文分词解决方案。IKAnalyzer支持自定义...

    IKAnalyzer3.2.8 源码

    通过深入研究IKAnalyzer3.2.8的源码,开发者可以了解中文分词的基本原理,学习如何优化分词算法,以及如何在实际项目中与搜索引擎框架集成,提升信息检索系统的效能。同时,源码阅读也是提升Java编程技能和理解软件...

    IKAnalyzer 分词源码

    **IKAnalyzer分词源码详解** IKAnalyzer是一款广泛使用的开源中文分词器,它主要针对Java平台设计,具有良好的性能和扩展性。该分词器适用于各种文本处理场景,如搜索引擎构建、信息检索、文本挖掘等。其核心在于...

    IKAnalyzer源码

    **IKAnalyzer源码详解** **一、IKAnalyzer简介** IKAnalyzer是基于Java语言开发的一款开源的中文分词组件,主要用于解决在信息检索、文本挖掘等领域中的中文处理问题。它的全称为"Intelligent Chinese Analyzer ...

    IKAnalyzer源码解析

    通过对IKAnalyzer源码的分析,我们可以看到其设计精妙之处在于利用了Trie树这一高效的数据结构来加速词典的加载和词汇的匹配。此外,自定义词典的功能也极大地增强了其灵活性和适应性。在实际应用中,理解IKAnalyzer...

    IKAnalyzer2012源码

    **IKAnalyzer 2012源码分析** IKAnalyzer 是一个开源的、基于Java实现的中文分词器,主要用于提高中文信息处理的效率。这款工具广泛应用于搜索引擎、文本挖掘、信息检索等领域,其核心功能是对中文文本进行有效的...

    使用IK Analyzer实现中文分词之Java实现

    从 2006年 12 月推出 1.0 版开始, IKAnalyzer 已经推出了 4 个大版本。最初,它是以开源项目Luence 为应用主体的,结合词典分词和文法分析算法的中文分词组件。 从 3.0 版本开始,IK 发展为面向 Java 的公用分词...

    IK Analyzer 中文分词器下载

    IK Analyzer 是一个开源的、基于Java实现的中文分词器,专为全文检索或信息提取等任务设计。它由尹力(Wu Li)在2006年发起,最初是为了改善Lucene的中文处理能力。自那时起,IK Analyzer已经发展成为一个广泛使用的...

    IKAnalyzer2012_u6中文分词器以及手册正式版.zip

    IKAnalyzer2012_u6中文分词器以及手册正式版 Mode LastWriteTime Length Name ---- ------------- ------ ---- d----- 2017/10/29 1:41 doc -a---- 2017/10/29 1:41 414 IKAnalyzer.cfg.xml -a---- 2017/10/29 1...

    IKAnalyzer中文分词器 v3.2.0 源码包

    源码包中包含的主要文件夹"j_IKAnalyzer-Stable_src"可能包含了以下关键组成部分: 1. **源代码**:这里是IKAnalyzer的核心代码,包括分词算法、词典管理、扩展机制等。通过阅读源码,开发者可以深入了解分词器的...

    IKAnalyzer 中文分词 完整java项目demo

    IKAnalyzer非常易用的java分词工具。可以自定义扩展词汇。 这个是一个完整的java项目demo。直接可以用,不用再去google下载了。添加了几个自定义词汇,测试好用。 运行ika.java里的main方法即可

    IKAnalyzer

    ikanalyzer-2012_u6中文分词器,Maven配置 清除IKAnalyzer2012_u6所依赖的Lucene4.7.2相关jar包 引入IKAnalyzer2012_u6所依赖的Lucene5.5.4相关jar包

    IKAnalyzer分词器源码+可直接使用jar包

    3. 重新启动Solr,现在你的索引和查询操作都将使用IKAnalyzer进行中文分词。 总的来说,IKAnalyzer是一个强大的中文分词工具,通过源码我们可以深入了解其工作原理,而预编译的jar包则为我们提供了便捷的使用方式。...

    几种分词工具的jar包(IKAnalyzer、hanlp、mmseg4j-analysis)

    几种分词工具都试过,对于中文来说的的话个人觉得IKAnalyzer 最好用,不同版本的IKAnalyzer jar包,包括IKAnalyzer6.5.0、IKAnalyzer5.0、IKAnalyzer2012,IKAnalyzer6.5.0可完美兼容Lucene6+,IKAnalyzer5.0兼容...

    IKAnalyzer2012_FF_hf1.jar

    解决lucene4.0与IKAnalyzer的冲突。解决Exception in thread "main" java.lang.VerifyError: class org.wltea.analyzer.lucene.IKAnalyzer overrides final method ...本资源包含了IKAnalyzer2012_FF_hf1.jar及源码

    IK Analyzer 2012FF 分词器源码

    IK 分词器源码 此版本IK Analyzer 2012FF版,支持4Lucene.0以上 , 适合大家学习,开发自己的分词器,或参与该项目开发

    ikanalyzer2.0.2源码

    ikanalyzer2.0.2源码是针对中文分词任务设计的一个开源项目,它主要基于Java语言实现,广泛应用于搜索引擎、信息检索系统以及文本挖掘等领域。ikanalyzer的核心目标是为了解决中文在英文为主导的Lucene等全文检索...

    IKAnalyzer3[1].2.稳定版本源码

    对于熟悉Java的开发者,可以基于 IKAnalyzer 的源码进行定制化开发,例如添加新的分词策略、优化性能、集成到自己的系统中等。 7. **应用场景** - 搜索引擎:IKAnalyzer 可用于构建搜索引擎,提升搜索结果的...

    IKAnalyzer中文分词器 java

    - **IK Analyzer 2012FF_hf1_source.rar**:这是IKAnalyzer 2012版本的源码压缩包,开发人员可以深入研究其内部实现,了解分词算法和逻辑,也可以根据需要进行定制和二次开发。 - **IKAnalyzer2012_u6.zip**:这是...

    IKAnalyzer中文分词器

    IKAnalyzer是一款专为中文处理设计的开源分词器,它主要应用于搜索引擎、信息检索系统、文本挖掘等领域。这款工具能够高效地对中文文本进行分词,使得计算机可以更好地理解和处理中文信息。IKAnalyzer的名字来源于...

Global site tag (gtag.js) - Google Analytics