lucene分词lucenelucene 6 中文分词词用什么方法最好

Lucene几种中文分词的总结_PHP教程_ThinkSAAS
Lucene几种中文分词的总结
Lucene几种中文分词的总结
内容来源: 网络
IK_CAnalyzer下载地址:/download.php?uid=ZrKcmJepZbOb4palZLKWlJiiZaycmps%3D4
目前最新版本的lucene自身提供的StandardAnalyzer已经具备中文分词的功能,但是不一定能够满足大多数应用的需要。
另外网友谈的比较多的中文分词器还有:
CJKAnalyzer
ChineseAnalyzer
IK_CAnalyzer(MIK_CAnalyzer)
还有一些热心网友自己写的比较不错的分词器在此就不说了,有兴趣的可以自己研究研究。
以上三个中文分词器并不是lucene2.2.jar里提供的。
CJKAnalyzer和ChineseAnalyzer分别是lucene-2.2.0目录下contrib目录下analyzers的lucene-analyzers-2.2.0.jar提供的。分别位于cn和cjk目录。
IK_CAnalyzer(MIK_CAnalyzer)是基于分词词典,目前最新的1.4版本是基于lucene2.0开发的。以上分词器各有优劣,比较如下:
import java.io.R
import java.io.StringR
import org.apache.lucene.analysis.A
import org.apache.lucene.analysis.StopF
import org.apache.lucene.analysis.T
import org.apache.lucene.analysis.TokenF
import org.apache.lucene.analysis.TokenS
import org.apache.lucene.analysis.cjk.CJKA
import org.apache..ChineseA
import org.apache.lucene.analysis.standard.StandardA
import org.mira.lucene.analysis.IK_CA
import org.mira.lucene.analysis.MIK_CA
public class All_Test {
private static String string ="中华人民共和国在1949年建立,从此开始了新中国的伟大篇章。";
public static void Standard_Analyzer(String str) throws Exception{
Analyzer analyzer = new StandardAnalyzer();
Reader r = new StringReader(str);
StopFilter sf = (StopFilter) analyzer.tokenStream("", r);
System.out.println("=====StandardAnalyzer====");
System.out.println("分析方法:默认没有词只有字(一元分词)");
while ((t = sf.next()) != null) {
System.out.println(t.termText());
public static void CJK_Analyzer(String str) throws Exception{
Analyzer analyzer = new CJKAnalyzer();
Reader r = new StringReader(str);
StopFilter sf = (StopFilter) analyzer.tokenStream("", r);
System.out.println("=====CJKAnalyzer====");
System.out.println("分析方法:交叉双字分割(二元分词)");
while ((t = sf.next()) != null) {
System.out.println(t.termText());
public static void Chiniese_Analyzer(String str) throws Exception{
Analyzer analyzer = new ChineseAnalyzer();
Reader r = new StringReader(str);
TokenFilter tf = (TokenFilter) analyzer.tokenStream("", r);
System.out.println("=====chinese analyzer====");
System.out.println("分析方法:基本等同StandardAnalyzer(一元分词)");
while ((t = tf.next()) != null) {
System.out.println(t.termText());
public static void ik_CAnalyzer(String str) throws Exception{
Analyzer analyzer = new MIK_CAnalyzer();
Analyzer analyzer = new IK_CAnalyzer();
Reader r = new StringReader(str);
TokenStream ts = (TokenStream)analyzer.tokenStream("", r);
System.out.println("=====IK_CAnalyzer====");
System.out.println("分析方法:字典分词,正反双向搜索");
while ((t = ts.next()) != null) {
System.out.println(t.termText());
public static void main(String[] args) throws Exception{
String str =
System.out.println("我们测试的字符串是:"+str);
Standard_Analyzer(str);
CJK_Analyzer(str);
Chiniese_Analyzer(str);
ik_CAnalyzer(str);
分词结果如下:
我们测试的字符串是:中华人民共和国在1949年建立,从此开始了新中国的伟大篇章。
=====StandardAnalyzer====
分析方法:默认没有词只有字(一元分词)
=====CJKAnalyzer====
分析方法:交叉双字分割(二元分词)
=====chinese analyzer====
分析方法:基本等同StandardAnalyzer(一元分词)
=====IK_CAnalyzer====
分析方法:字典分词,正反双向搜索
中华人民共和国
人民共和国
如果 ik_CAnalyzer(String str) 里采用
Analyzer analyzer = new MIK_CAnalyzer();
那么该方法的分词结果是:
中华人民共和国
可以看到各种分词结果各不相同,根据应用的需要可以选择合适的分词器。
关于IKAnalyzer的介绍可以参考:
http://blog.csdn.net/dbigbear/archive//1492380.aspx
IK_CAnalyzer下载地址:/download.php?uid=ZrKcmJepZbOb4palZLKWlJiiZaycmps%3D4
PHP开发框架
开发工具/编程工具
服务器环境
ThinkSAAS商业授权:
ThinkSAAS为用户提供有偿个性定制开发服务
ThinkSAAS将为商业授权用户提供二次开发指导和技术支持
让ThinkSAAS更好,把建议拿来。
开发客服微信7、自定义分词和中文分词(lucene笔记) - 简书
7、自定义分词和中文分词(lucene笔记)
一、自定义分词器
这里我们自定义一个停用分词器,也就是在进行分词的时候将某些词过滤掉。MyStopAnalyzer.java
package cn.itcast.
import java.io.R
import java.util.S
import org.apache.lucene.analysis.A
import org.apache.lucene.analysis.LetterT
import org.apache.lucene.analysis.LowerCaseF
import org.apache.lucene.analysis.StopA
import org.apache.lucene.analysis.StopF
import org.apache.lucene.analysis.TokenS
import org.apache.lucene.util.V
public class MyStopAnalyzer extends Analyzer {
@SuppressWarnings("rawtypes")
private S//用于存放分词信息
public MyStopAnalyzer() {
stops = StopAnalyzer.ENGLISH_STOP_WORDS_SET;//默认停用的语汇信息
//这里可以将通过数组产生分词对象
public MyStopAnalyzer(String[] sws) {
//System.out.println(StopAnalyzer.ENGLISH_STOP_WORDS_SET);
stops = StopFilter.makeStopSet(Version.LUCENE_35, sws, true);//最后的参数表示忽略大小写
stops.addAll(StopAnalyzer.ENGLISH_STOP_WORDS_SET);
public TokenStream tokenStream(String fieldName, Reader reader) {
//注意:在分词过程中会有一个过滤器链,最开始的过滤器接收一个Tokenizer,而最后一个接收一个Reader流
//这里我们看到我们可以在过滤器StopFilter中接收LowerCaseFilter,而LowerCaseFilter接收一个Tokenizer
//当然如果要添加更多的过滤器还可以继续添加
return new StopFilter(Version.LUCENE_35, new LowerCaseFilter(Version.LUCENE_35,
new LetterTokenizer(Version.LUCENE_35, reader)), stops);
这里我们定义一个Set集合用来存放分词信息,其中在无参构造器我们将默认停用分词器中停用的语汇单元赋给stops,这样我们就可以使用默认停用分词器中停用的语汇。而我们通过一个字符串数组将我们自己想要停用的词传递进来,同时stops不接受泛型,也就是说不能直接将字符串数组赋值给stops,而需要使用makeStopSet方法将需要停用的词转换为相应的语汇单元,然后再添加给stops进行存储。
自定义的分词器需要继承Analyzer接口,实现tokenStream方法,此方法接收三个参数,第一个是版本,最后一个是停用的语汇单元,这里是stops,而第二个参数是别的分词器,因为分词过程中是一个分词器链。
测试:TestAnalyzer.java
public void test04(){
//对中文分词不适用
Analyzer analyzer = new MyStopAnalyzer(new String[]{"I","you"});
Analyzer analyzer2 = new StopAnalyzer(Version.LUCENE_35);//停用分词器
String text = "how are you thank you I hate you";
System.out.println("************自定义分词器***************");
AnalyzerUtils.displayAllTokenInfo(text, analyzer);
System.out.println("************停用分词器***************");
AnalyzerUtils.displayAllTokenInfo(text, analyzer2);
说明:从测试结果中我们可以很容易看出自定义分词器和默认分词器之间的区别,自定义分词相比默认分词器多了我们自定义的词语。
二、中文分词器
这里我们使用MMSEG中文分词器,其分词信息使用的是搜狗词库。我们使用的是版本1.8.5.这个版本的包中有两个可用的jar包:
mmseg4j-all-1.8.5.jar
mmseg4j-all-1.8.5-with-dic.jar
其中第二个相比第一个多了相关的语汇信息,便于我们进行分词,当然我们可以使用第一个,但是这样便和默认分词器没有多大差别,我们在方法中直接测试:
public void test02(){
//对中文分词不适用
Analyzer analyzer1 = new StandardAnalyzer(Version.LUCENE_35);//标准分词器
Analyzer analyzer2 = new StopAnalyzer(Version.LUCENE_35);//停用分词器
Analyzer analyzer3 = new SimpleAnalyzer(Version.LUCENE_35);//简单分词器
Analyzer analyzer4 = new WhitespaceAnalyzer(Version.LUCENE_35);//空格分词器
Analyzer analyzer5 = new MMSegAnalyzer();
String text = "西安市雁塔区";
AnalyzerUtils.displayToken(text, analyzer1);
AnalyzerUtils.displayToken(text, analyzer2);
AnalyzerUtils.displayToken(text, analyzer3);
AnalyzerUtils.displayToken(text, analyzer4);
AnalyzerUtils.displayToken(text, analyzer5);
说明:此时我们直接使用MMSEG中文分词器,测试结果为:
我们看到和默认的分词器并无多大差别,当然我们也可以在方法中指定相关语汇信息存放的目录:
Analyzer analyzer5 = new MMSegAnalyzer(new File("E:/API/Lucene/mmseg/data"));
此时的测试结果为:
在目录E:/API/Lucene/mmseg/data中存在四个文件:
words-my.dic
这写文件便存放了相关的语汇单元,当然如果我们想停用某些词,可以在最后一个文件中直接进行添加。
三、同义词索引(1)
说明:首先我们需要使用MMSEG进行分词,之后我们自定义的分词器从同义词容器中取得相关的同义词,然后将同义词存储在同一个位置,我们在之前讲过,就是同一个偏移量可以有多个语汇单元。
3.2 自定义分词器
MySameAnalyzer.java
package cn.itcast.
import java.io.R
import org.apache.lucene.analysis.A
import org.apache.lucene.analysis.TokenS
import com.chenlb.mmseg4j.D
import com.chenlb.mmseg4j.MaxWordS
import com.chenlb.mmseg4j.analysis.MMSegT
public class MySameAnalyzer extends Analyzer {
public TokenStream tokenStream(String fieldName, Reader reader) {
Dictionary dic = Dictionary.getInstance("E:/API/Lucene/mmseg/data");
//我们首先使用MMSEG进行分词,将相关内容分成一个一个语汇单元
return new MySameTokenFilter(new MMSegTokenizer(new MaxWordSeg(dic), reader));
说明:和之前一样还是需要实现Analyzer接口。这里我们实例化Dictionary对象,此对象是单例的,用于保存相关的语汇信息。可以看到,首先是经过MMSEG分词器,将相关内容分成一个一个的语汇单元。
自定义同义词过滤器MySameTokenFilter.java
package cn.itcast.
import java.io.IOE
import java.util.HashM
import java.util.M
import org.apache.lucene.analysis.TokenF
import org.apache.lucene.analysis.TokenS
import org.apache.lucene.analysis.tokenattributes.CharTermA
public class MySameTokenFilter extends TokenFilter {
private CharTermAttribute cta =
protected MySameTokenFilter(TokenStream input) {
super(input);
cta = this.addAttribute(CharTermAttribute.class);
public boolean incrementToken() throws IOException {
if(!this.input.incrementToken()){//如果输入进来的内容中没有元素
//如果有,则需要进行相应的处理,进行同义词的判断处理
String[] sws = getSameWords(cta.toString());
if(sws != null){
for(String s : sws){
cta.setEmpty();
cta.append(s);
private String[] getSameWords(String name){
Map&String, String[]& maps = new HashMap&String, String[]&();
maps.put("中国", new String[]{"天朝", "大陆"});
maps.put("我", new String[]{"咱", "俺"});
return maps.get(name);
说明:这里我们需要定义一个CharTermAttribute 属性,在之前说过,这个类相当于在分词流中的一个标记。
相关方法AnalyzerUtils.java
public static void displayAllTokenInfo(String str, Analyzer analyzer){
TokenStream stream = analyzer.tokenStream("content", new StringReader(str));
PositionIncrementAttribute pia = stream.addAttribute(PositionIncrementAttribute.class);
OffsetAttribute oa = stream.addAttribute(OffsetAttribute.class);
CharTermAttribute cta = stream.addAttribute(CharTermAttribute.class);
TypeAttribute ta = stream.addAttribute(TypeAttribute.class);
while (stream.incrementToken()) {
System.out.print("位置增量: " + pia.getPositionIncrement());//词与词之间的空格
System.out.print(",单词: " + cta + "[" + oa.startOffset() + "," + oa.endOffset() + "]");
System.out.print(",类型: " + ta.type()) ;
System.out.println();
} catch (IOException e) {
e.printStackTrace();
public void test05(){
//对中文分词不适用
Analyzer analyzer = new MySameAnalyzer();
String text = "我来自中国西安市雁塔区";
System.out.println("************自定义分词器***************");
AnalyzerUtils.displayAllTokenInfo(text, analyzer);
说明:整个执行流程就是:
1.首先实例化一个自定义的分词器MySameAnalyzer,在此分词器中实例化一个MySameTokenFilter过滤器,而从过滤器中的参数中可以看到接收MMSEG分词器,而MySameTokenFilter的构造方法中接收一个分词流,然后将CharTermAttribute加入到此流中。
2.在displayAllTokenInfo方法中我们调用incrementToken方法时先是调用getSameWords方法查看分词流中有没有同义词,如果没有则直接返回,否则进行相关的处理。
3.在这里的处理方式中,先是使用方法setEmpty将原来的语汇单元清除,然后将此语汇单元同义词添加进去,但是这样就将原来的语汇单元删除了,这显然不符合要求。测试结果为:
可以看到将“我”换成了“俺”,将“中国”换成了“大陆”。也就是说我们使用同义词将原来的词语替换掉了。
解决方法我们之前说过,每个语汇单元都有一个位置,这个位置由PositionIncrTerm属性保存,如果两个语汇单元的位置相同,或者说距离为0,那么就表示是同义词了。而我们看到上面的测试结果中每个语汇单元的距离都为1,显然不是同义词。而对于上面例子中的问题,我们可以这样解决:MySameTokenFilter.java
package cn.itcast.
import java.io.IOE
import java.util.HashM
import java.util.M
import java.util.S
import org.apache.lucene.analysis.TokenF
import org.apache.lucene.analysis.TokenS
import org.apache.lucene.analysis.tokenattributes.CharTermA
import org.apache.lucene.analysis.tokenattributes.PositionIncrementA
import org.apache.lucene.util.AttributeS
public class MySameTokenFilter extends TokenFilter {
private CharTermAttribute cta =
private PositionIncrementAttribute pia =
private AttributeSource.S
private Stack&String& sames =
protected MySameTokenFilter(TokenStream input) {
super(input);
cta = this.addAttribute(CharTermAttribute.class);
pia = this.addAttribute(PositionIncrementAttribute.class);
sames = new Stack&String&();
public boolean incrementToken() throws IOException {
while(sames.size() & 0){
//将元素出栈,并且获取这个同义词
String str = sames.pop();
restoreState(current);//还原到原来的状态
cta.setEmpty();
cta.append(str);
//设置位置为0
pia.setPositionIncrement(0);
if(!this.input.incrementToken()){//如果输入进来的内容中没有元素
if(getSameWords(cta.toString())){
//如果有同义词,捕获当前的状态
current = captureState();
private boolean getSameWords(String name){
Map&String, String[]& maps = new HashMap&String, String[]&();
maps.put("中国", new String[]{"天朝", "大陆"});
maps.put("我", new String[]{"咱", "俺"});
String[] sws = maps.get(name);
if(sws != null){
for(String s : sws){
sames.push(s);
1.首先我们添加了三个属性PositionIncrementAttribute 、AttributeSource.State、Stack,分别是位置属性、当前状态、栈。其中栈用来保存同义词单元。在构造函数中初始化相关属性。
2.在调用incrementToken方法开始时我们先使用方法incrementToken,让标记CharTermAttribute 向后移动一个位置,同时将本位置(current )保留下来。而此时第一个语汇单元“我”已经写入到分词流中了,然后我们利用current在读取到同义词之后回到前一个位置进行添加同义词,其实就是将同义词的位置设置为0(同义词之间的位置为0),这样就将原始单元和同义词单元都写入到了分词流中了。这就将第一个单元的同义词设置好了,立即返回,进入到下一个语汇单元进行处理。
测试结果为:
下面我们编写一个测试方法进行同义词查询操作:
public void test06() throws CorruptIndexException, LockObtainFailedException, IOException{
//对中文分词不适用
Analyzer analyzer = new MySameAnalyzer();
String text = "我来自中国西安市雁塔区";
Directory dir = new RAMDirectory();
IndexWriter write = new IndexWriter(dir, new IndexWriterConfig(Version.LUCENE_35, analyzer));
Document doc = new Document();
doc.add(new Field("content", text, Field.Store.YES, Field.Index.ANALYZED));
write.addDocument(doc);
write.close();
IndexSearcher searcher = new IndexSearcher(IndexReader.open(dir));
//TopDocs tds = searcher.search(new TermQuery(new Term("content", "中国")), 10);
TopDocs tds = searcher.search(new TermQuery(new Term("content", "大陆")), 10);
Document d = searcher.doc(tds.scoreDocs[0].doc);
System.out.println(d.get("content"));
System.out.println("************自定义分词器***************");
AnalyzerUtils.displayAllTokenInfo(text, analyzer);
说明:我们在查询的时候可以使用“中国”的同义词“大陆”进行查询。但是这种方式并不好,因为将将同义词等信息都写死了,不便于管理。
四、同义词索引(2)
(工程lucene_analyzer02)这里我们专门创建一个类用来存放同义词:SamewordContext.java
package cn.itcast.
public interface SamewordContext {
public String[] getSamewords(String name);
实现SimpleSamewordContext.java
package cn.itcast.
import java.util.HashM
import java.util.M
public class SimpleSamewordContext implements SamewordContext {
private Map&String, String[]& maps = new HashMap&String, String[]&();
public SimpleSamewordContext() {
maps.put("中国", new String[]{"天朝", "大陆"});
maps.put("我", new String[]{"咱", "俺"});
public String[] getSamewords(String name) {
maps.get(name);
说明:这里我们只是简单的实现了接口,封装了一些同义词,之后我们在使用的时候便可以使用此类来获取同义词。测试我们需要改进相关的类:MySameTokenFilter.java
package cn.itcast.
import java.io.IOE
import java.util.HashM
import java.util.M
import java.util.S
import org.apache.lucene.analysis.TokenF
import org.apache.lucene.analysis.TokenS
import org.apache.lucene.analysis.tokenattributes.CharTermA
import org.apache.lucene.analysis.tokenattributes.PositionIncrementA
import org.apache.lucene.util.AttributeS
public class MySameTokenFilter extends TokenFilter {
private CharTermAttribute cta =
private PositionIncrementAttribute pia =
private AttributeSource.S
private Stack&String& sames =
private SamewordContext samewordC//用来存储同义词
protected MySameTokenFilter(TokenStream input, SamewordContext samewordContext) {
super(input);
cta = this.addAttribute(CharTermAttribute.class);
pia = this.addAttribute(PositionIncrementAttribute.class);
sames = new Stack&String&();
this.samewordContext = samewordC
public boolean incrementToken() throws IOException {
while(sames.size() & 0){
//将元素出栈,并且获取这个同义词
String str = sames.pop();
restoreState(current);//还原到原来的状态
cta.setEmpty();
cta.append(str);
//设置位置为0
pia.setPositionIncrement(0);
if(!this.input.incrementToken()){//如果输入进来的内容中没有元素
if(addSames(cta.toString())){
//如果有同义词,捕获当前的状态
current = captureState();
private boolean addSames(String name){
String[] sws = samewordContext.getSamewords(name);
if(sws != null){
for(String s : sws){
sames.push(s);
说明:在此类中我们太添加了一个属性SamewordContext,用来保存相关的同义词,在方法addSames中使用此类来获取相关的同义词。于是我们在后面使用MySameTokenFilter类的时候需要通过构造函数将此类传递进去。注意:这里需要面向接口编程,在后面我们需要想更换同义词存储类,只需要重现实现接口即可。
一名程序猿与Lucene 4.10配合的中文分词比较
Justin Wan
衡量每种分词的指标,内存消耗、CPU消耗,得到一个在Lucene中比较好的分词版本。
分词源代码介绍
paoding: 庖丁解牛最新版在
中最多支持Lucene 3.0,且最新提交的代码在 ,在svn中最新也是2010年提交,已经过时,不予考虑。
mmseg4j:最新版已从
,支持Lucene 4.10,且在github中最新提交代码是2014年6月,从09年~14年一共有:18个版本,也就是一年几乎有3个大小版本,有较大的活跃度,用了mmseg算法。
IK-analyzer: 最新版在/p/ik-analyzer/上,支持Lucene 4.10从2006年12月推出1.0版开始, IKAnalyzer已经推出了4个大版本。最初,它是以开源项目Luence为应用主体的,结合词典分词和文法分析算法的中文分词组件。从3.0版本开 始,IK发展为面向Java的公用分词组件,独立于Lucene项目,同时提供了对Lucene的默认优化实现。在2012版本中,IK实现了简单的分词 歧义排除算法,标志着IK分词器从单纯的词典分词向模拟语义分词衍化。 但是也就是2012年12月后没有在更新。
ansj_seg:最新版本在
tags仅有1.1版本,从2012年到2014年更新了大小6次,但是作者本人在日说明:“可能我以后没有精力来维护ansj_seg了”,现在由”nlp_china”管理。2014年11月有更新。并未说明是否支持Lucene,是一个由CRF(条件随机场)算法所做的分词算法。
imdict-chinese-analyzer:最新版在
, 最新更新也在2009年5月,下载源码,不支持Lucene 4.10 。是利用HMM(隐马尔科夫链)算法。
Jcseg:最新版本在git.oschina.net/lionsoul/jcseg,支持Lucene 4.10,作者有较高的活跃度。利用mmseg算法。
测试环境:
Ubuntu 14.04 64位, 内存 32GB,
CPU Intel(R) Core(TM) i7-4770K CPU @ 3.50GHz × 8
分词算法衡量指标及测试代码
黄金标准/Golden standard
评价一个分词器分词结果的好坏,必然要有一份“公认正确”的分词结果数据来作为参照。
SIGHAN(国际计算语言学会(ACL)中文语言处理小组)举办的国际中文语言处理竞赛Second International Chinese Word Segmentation Bakeoff(http://sighan.cs.uchicago.edu/bakeoff2005/)所提供的公开数据来评测,它包含了多个测试集以及对应的黄金标准分词结果。在所有分词器都使用同一标准来评测的情况下,也就会很公平,并不会影响到最终的结论,所以本文用此测评标准,并针对创建索引,做了些改动。
精度(Precision):精度表明了分词器分词的准确程度。
召回率(Recall):召回率也可认为是“查全率”。
F值(F-mesure):F值综合反映整体的指标。
错误率(Error Rate --ER)(带选项):分词器分词的错误程度。
公式参数说明
N:黄金标准分割的单词数; e:分词器错误标注的单词数; c:分词器正确标注的单词数.
总结:P、R、F越大越好,ER越小越好。一个完美的分词器的P、R、F值均为1,ER值为0。
正确及错误标注的计数算法
要先计算出e和c,才能计算出各指标值。e和c是按如下算法来统计的: 在“黄金标准”和“待评测的结果”中,理论上,除了分词后添加的空格之外,它们所有的文字都是相同的;唯一的不同就在于那些有差异的分词结果的位置上。例如,“计算机 是个 好东西”(黄金标准)与“计算机 是 个 好东西”(待评测的结果)的差异就在于“是个”与“是 个”的差异,其余分词结果都是相同的。因此,只需要找到这种差异的个数,就可以统计出分词器正确标注了多少个词、错误标注了多少个词。为了完成测试指标,同时,对应Lucene的检索实际需要对黄金标准的 *_test_gold和分词结果做了如下改动:
去掉标点符号
统一对一些虚词作停词处理
没有分开句子,结果都是一个比较集。
统一的perl处理代码
#!/usr/bin/perl
if (@ARGV != <span style="color: #) {
print "No param which will be read!";
open (FpStopDir, $ARGV[<span style="color: #]) or die "The stopping dictionary($ARGV[0]) cannot open!$!\n";
%dict = ();
while(&FpStopDir&){
s/^\s*//;#remove start space char
s/\s*$//;#remove the space char in the end of string
$dict{$_} = <span style="color: #;
close(FpStopDir);
open(FpDeal, $ARGV[<span style="color: #]) or die "The file ($ARGV[1]) which will be dealed cannot open! $!\n";
my@DealedWord;
while (&FpDeal&){
@Word = split /\s+/, $_;
foreach $AWord(@Word){
if(<span style="color: # != $dict{$AWord}){
print "$AWord ";
close(FpDeal);
Java测试代码
package com.hansight;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.analysis.cjk.CJKAnalyzer;
import org.apache.lucene.analysis.core.SimpleAnalyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
import org.lionsoul.jcseg.analyzer.JcsegAnalyzer4X;
import org.lionsoul.jcseg.core.*;
import org.wltea.analyzer.lucene.IKAnalyzer;
import com.chenlb.mmseg4j.analysis.ComplexAnalyzer;
import java.io.*;
public class TestChineseAnalyzer {
private static int PER_TIME_READ_LEN = <span style="color: #24;
//每次读入文件流长度
private TestChineseAnalyzer() {}
public static void printTerms(Analyzer analyzer, String content){
TokenStream ts = analyzer.tokenStream("content",
new StringReader(content));
CharTermAttribute term = ts.addAttribute(CharTermAttribute.class);
ts.reset();
StringBuffer buf = new StringBuffer();
while (ts.incrementToken()) {
buf.append(term.toString());
buf.append(" ");
System.out.println(buf.toString());
System.out.println(analyzer.getClass().getName() + " done\n");
}catch (IOException ex){
System.out.println("Segment word fail. " + ex.getMessage());
public static void main(String[] args){
if (<span style="color: # == args.length){
System.err.println("No Inputing param");
System.exit(<span style="color: #);
FileInputStream in = new FileInputStream(new File(args[<span style="color: #]));
byte[] perRead = new byte[PER_TIME_READ_LEN];
String strContent = " ";
int rst = in.read(perRead, <span style="color: #, PER_TIME_READ_LEN);
while (-<span style="color: # != rst){
strContent = strContent.concat(new String(perRead));
rst = in.read(perRead, <span style="color: #, PER_TIME_READ_LEN);
printTerms(new JcsegAnalyzer4X(JcsegTaskConfig.COMPLEX_MODE), strContent);
printTerms(new IKAnalyzer(true), strContent);
printTerms(new CJKAnalyzer(), strContent);
printTerms(new SimpleAnalyzer(), strContent);
printTerms(new StandardAnalyzer(), strContent);
printTerms(new ComplexAnalyzer(), strContent);
} catch (Exception ex) {
ex.printStackTrace();
运行Java通过重定向到一个txt文件,再将彼此分开,如上所示,没有看过Lucene本身的分词的烂,所以自己也查看了一下,果然很烂。
通过对结果的处理(用上面的Perl脚本,统一对标准和对结果的处理)。再利用 黄金标准中的Perl评分脚本。
Table 1. 评分结果
测试标准集
此结果并没有按照黄金标准正确用法来用(主要没有用黄金标准来训练,且评分本身是一句一句的评分,最后是综合得分。
而本文是所有内容一起评分,会有一定误差)。同时:现在的分词,比较而言更加智能,能将数量词等(一位,同志们)分在一起,是以前可能没能想过的。
虽然,有诸多误差,但是本文只是比较相对值,只要在统一的相对正确的标准下也就能达到效果了。
分词算法内存和cup测试
在一个大的语料库中,所有文档加入Lucene索引的时间,测试内存使用情况,就将索引建立在磁盘中;
若是测试CPU使用情况,就将所以建立的内存中减小IO读写对CPU的影响。利用VisualVM查看CPU利用率、内存利用率,得到他们的时间序列图。
Java程序如下
package com.hansight;
import org.apache.lucene.store.RAMDirectory;
import org.lionsoul.jcseg.analyzer.JcsegAnalyzer4X;
import com.chenlb.mmseg4j.analysis.ComplexAnalyzer;
import org.apache.lucene.document.*;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.Term;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.lionsoul.jcseg.core.JcsegTaskConfig;
import org.wltea.analyzer.lucene.IKAnalyzer;
import java.io.*;
import java.nio.charset.StandardCharsets;
public class FileIndexTest {
private FileIndexTest() {}
private IndexWriterConfig conf = null;
public FileIndexTest(IndexWriterConfig conf) {
this.conf = conf;
public void indexFilesInDir(String docsPath, String indexPath, boolean createIfNotExists){
final File docDir = new File(docsPath);
if (!docDir.exists() || !docDir.canRead()) {
System.out.println("Document directory '" +docDir.getAbsolutePath()+ "' does not exist or is not readable, please check the path");
System.exit(<span style="color: #);
long start = System.currentTimeMillis();
System.out.println("Indexing into directory '" + indexPath + "'...");
Directory dir = null != indexPath ? FSDirectory.open(new File(indexPath)): new RAMDirectory();
if (createIfNotExists) {
conf.setOpenMode(IndexWriterConfig.OpenMode.CREATE);
conf.setOpenMode(IndexWriterConfig.OpenMode.CREATE_OR_APPEND);
IndexWriter writer = new IndexWriter(dir, conf);
indexDocs(writer, docDir);
writer.close();
System.out.println(System.currentTimeMillis() - start + " total milliseconds");
} catch (IOException ex) {
ex.printStackTrace();
public static void indexDocs(IndexWriter writer, File file) throws IOException {
if (file.canRead()) {
if (file.isDirectory()) {
String[] files = file.list();
if (files != null) {
for (int i = <span style="color: #; i & files.length; i++) {
indexDocs(writer, new File(file, files[i]));
FileInputStream fis;
fis = new FileInputStream(file);
} catch (FileNotFoundException fnfe) {
Document doc = new Document();
Field pathField = new StringField("path", file.getPath(), Field.Store.YES);
doc.add(pathField);
doc.add(new LongField("modified", file.lastModified(), Field.Store.NO));
doc.add(new TextField("contents", new BufferedReader(new InputStreamReader(fis, StandardCharsets.UTF_8))));
if (writer.getConfig().getOpenMode() == IndexWriterConfig.OpenMode.CREATE) {
// New index, so we just add the document (no old document can be there):
System.out.println("adding " + file);
writer.addDocument(doc);
System.out.println("updating " + file);
writer.updateDocument(new Term("path", file.getPath()), doc);
} finally {
fis.close();
public static void main(String[] args) {
String usage = "java org.apache.lucene.demo.IndexFiles"
+ " [-index INDEX_PATH] [-docs DOCS_PATH] [-update]\n\n"
+ "This indexes the documents in DOCS_PATH, creating a Lucene index"
+ "in INDEX_PATH that can be searched with SearchFiles";
String indexPath = null;
String docsPath = null;
boolean create = true;
for (int i = <span style="color: #; i & args.length; i++) {
if ("-index".equals(args[i])) {
indexPath = args[i + <span style="color: #];
} else if ("-docs".equals(args[i])) {
docsPath = args[i + <span style="color: #];
} else if ("-update".equals(args[i])) {
create = false;
if (docsPath == null) {
System.err.println("Usage: " + usage);
System.exit(<span style="color: #);
/*IKAnalyzer*/
FileIndexTest test = new FileIndexTest(new IndexWriterConfig(Version.LUCENE_4_10_2, new IKAnalyzer()));
test.indexFilesInDir(docsPath, indexPath, create);
FileIndexTest test1 = new FileIndexTest(new IndexWriterConfig(Version.LUCENE_4_10_2, new JcsegAnalyzer4X(JcsegTaskConfig.COMPLEX_MODE)));
test1.indexFilesInDir(docsPath, indexPath, create);
FileIndexTest test2 = new FileIndexTest(new IndexWriterConfig(Version.LUCENE_4_10_2, new ComplexAnalyzer()));
test2.indexFilesInDir(docsPath, indexPath, create);
如上所示:IK-analyzer、Jcseg、mmseg4j都是用统一接口,测试,就将其他两个给注释掉。 同时:当测试内存消耗量时,
我们需要将索引建立在磁盘中测试jar包的命令例子如下:
java -jar indexFile.jar -docs ~/resource/ -index ~/index/
当测试CPU消耗时,我们尽量减小IO的消耗,那么可以将索引建立在内存中,测试jar包的命令例子如下:
java -jar indexFile.jar -docs ~/resource/s
得到如下面所有图所示的结果:
Figure 1. IK-Analyzer分词消耗内存
Figure 2. Jcseg分词消耗内存
Figure 3. mmseg4j分词消耗内存
Figure 4. IK-Analyzer分词CPU使用率
Figure 5. Jcseg分词CPU使用率
Figure 6. mmseg4j分词CPU使用率
从几个指标对比来看:IK-analyzer的准确度稍差,Jcseg的时间消耗稍差
时间消耗上:在索引创建1,003,057 items, totalling 2.8 GB的文件:
将其索引放入磁盘
Jcseg + Lucene建索引消耗:
516971 total milliseconds
mmseg4j + Lucene建索引消耗:
256805 total milliseconds
IK-Analyzer + Lucene建索引消耗:
445591 total milliseconds
Standard + Lucene建索引消耗:
184717 total milliseconds 内存消耗最大不过650M多 CPU消耗减小不大 (磁盘数据仅仅增加0.2G~0.3G左右)
将索引放在内存中
Jcseg + Lucene 建索引消耗:
510146 total milliseconds
mmseg4j + Lucene建索引消耗:
262682 total milliseconds
IK-Analyzer + Lucene建索引消耗:
436900 total milliseconds
Standard + Lucene建索引消耗:
183271 total milliseconds CUP的高峰值频率明显增多
综上所有因素:
准确率为:Jcseg > mmseg4j > IK-Analyzer。
内存消耗和CPU使用率上,几个都在一个数量级上,很难分出胜负。
但是在时间消耗上明显mmseg4j的优势非常突出。
从活跃度来看,mmseg4j的活跃度也是非常可喜的。
业务咨询: 400-816-1670
总部:北京市 海淀区 东北旺西路8号 中关村软件园9号楼2区306A
Phone: (+86 10)
微信公众号: 瀚思安信
HanSight瀚思致力于用大数据分析解决企业庞杂、分立的安全问题。
瀚思 - 数据驱动安全 Data Driven Security}

我要回帖

更多关于 lucene 5.5 中文分词 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信