Clucene实现中文分词搜索

Clucene实现中文分词搜索(转载)2008年05月23日 星期五 17:42最近,一阵忙乎,终于在Clucene(版本0.9.16)中实现了中文分词搜索。


一些需要改动的地方如下:
     
             一、 把项目设置为Use Unicode Character Set。因为使用ANSI时,汉字存在与其他语言编码重叠的问题,不能准确判断是否为汉字。
  
             二、 \src\CLucene\util\Misc.cpp中有个Misc::_cpycharToWide函数,这个函数是CLucene中用来将char 字符串转换为wchar_t字符串的,但原代码中使用的是不考虑编码的转换,把汉字从ANSI编码转到UCS2编码会失效,所以需要在\src\ CLucene\StdHeader.h的宏定义中,改用windows API的MultiByteToWideChar函数。
line350 改为#define STRCPY_AtoW(target,src,len) MultiByteToWideChar(936,0,src,-1,target,len);


\src\CLucene\util\Misc.cpp,line67的Misc::_charToWide函数调用了_cpycharToWide函数,需修改。

            三、 要实现中文按词搜索必须按中文语法实现Analyzer接口。Clucene里面有一个StandardAnalyzer类,它本意是可以处理亚洲语言 (包括中日韩),但不完善。可以通过改写 \src\CLucene\analysis\standard\StandardTokenizer.cpp文件来达到目的。
           其中,有一个StandardTokenizer::next(),用来获取下一个token。Token可以简单看成是索引文件中的一个项。在原代码 中,先判断是否为ALPHA,再判断是否为_CJK,而在中文windows系统上,将一个UCS2编码的汉字(正好一个wchar_t)作为参数传给 iswalpha函数时,返回值是true。所以任何CJK字符都会被处理成ALPHA。因此,项目修改为使用Unicode编码后,ReadCJK函数 不会被调用到。所以需要将原代码中的if(_CJK)的判断分支放到if(ALPHA)前面,这样遇到CJK字符时就会调用 StandardTokenizer::ReadCJK。
            ReadCJK函数用于读取一个CJK的token。这个函数不能处理中文分词,它遇到CJK字符时一直向后读取,直到遇到非CJK字符或文件结束才跳出,把读取到的整个字符串作为索引中的一个项。所以需要重写这个函数。
           我在StandardAnalyzer类加入了一个公有类paticiple,这个paticiple类有一个方法 ParagraphProcessing(char *str,char *result),可以用来把一个char类型的中文字符串str进行分词,由result返回分词结果,词与词之间用空格格开。以下是我重写的 ReadCJK函数:

 
C++代码 复制代码  收藏代码
  1.    bool StandardTokenizer::ReadCJK(const TCHAR prev, Token* t)    
  2. {   
  3.              t->growBuffer(LUCENE_MAX_WORD_LEN+1);//make sure token can hold the next word   
  4.              StringBuffer str(t->_termText,t->bufferLength(),true);    
  5.              if ( str.len < LUCENE_MAX_WORD_LEN )   
  6.            {    
  7.                   str.appendChar(prev);   
  8.                   TCHAR ppl[10];    //临时存放从Tokenstream读出的不大于八个汉字的字符用于分词,因为中文词语最长为八    
  9.                    ppl[0]= prev;    
  10.                    int i;   
  11.                    int time=0;       //记录指针往前移动的位置    
  12.                    for(i=1;i<8;i++)        //读取八个CJK字符,若中间有非CJK字符则跳出,追加‘\0‘结尾形成字符串   
  13.                  {    
  14.                          ppl[i]=readChar();time++;   
  15.                          if       (ppl[i]==-1 || (!( (ppl[i]>=0x3040 && ppl[i]<=0x318f) ||       (ppl[i]>=0x3300 && ppl[i]<=0x337f) || \   
  16.                                                          (ppl[i]>=0x3400 && ppl[i]<=0x3d2d) ||       (ppl[i]>=0x4e00 && ppl[i]<=0x9fff) || \   
  17.                                                          (ppl[i]>=0xf900 && ppl[i]<=0xfaff) ||       (ppl[i]>=0xac00 && ppl[i]<=0xd7af) ) ) )   
  18.                       {   
  19.                              break;    
  20.                        }   
  21.                   }//for   
  22.                   ppl[i]=0;   
  23.                   for( ;time>0;time--)       //还原指针    
  24.                         unReadChar();   
  25.                //把PPL转换成CHAR类型ppltemp,利用paticiple类分词,把最终TCHAR型的分词结果存放在 pplnew里面    
  26.   
  27.                len=WideCharToMultiByte(936,0,ppl,-1,NULL,0,NULL,NULL);   
  28.                 char* ppltemp=new char[len+1];   
  29.                 WideCharToMultiByte(936,0,ppl,-1,ppltemp,len,NULL,NULL);   
  30.                 char* pplnewtemp=new char[2*len+1];   
  31.                 paticiple.ParagraphProcessing(ppltemp,pplnewtemp);   
  32.                 int length=MultiByteToWideChar(936,0,pplnewtemp,-1,NULL,0);   
  33.                 TCHAR pplnew[20];    
  34.                 MultiByteToWideChar(936,0,pplnewtemp,-1,pplnew,length);    
  35.                 delete[] ppltemp;   
  36.                 delete[] pplnewtemp;    
  37.                //改从pplnew中读取字符而不是从tokenstream中   
  38.                  int j=1;   
  39.                  while(true)   
  40.                 {   
  41.                       ch=pplnew[j];    
  42.                        if(!rd->Eos())   
  43.                      {   
  44.                            int k=readChar();        //使readChar()的指针与从pplnew[]中读出的字符保持同步   
  45.                       }   
  46.                        if (ch==-1 || (!(_CJK) || str.len >= LUCENE_MAX_WORD_LEN))   
  47.                      {    
  48.                        unReadChar();   
  49.                        break;    
  50.                      }    
  51.                       str.appendChar(ch);   
  52.                       j++;   
  53.                }//while   
  54.           }//if   
  55.           return setToken(t,&str,CL_NS2(analysis,standard)::CJK);   
  56. }  
  
             四、如果一个中文句子中引入了英文单词,则进入英文处理函数ReadAlphaNum()后,由于引用了宏CONSUME_WORD,对英文后面出现的中 文会继续作为Alpha字符处理,索引为一个项,直到遇到非Alpha字符为止。为了避免英中文连用(中间没有空格)时索引出错,还要修改 StandardTokenizer.cpp中的宏定义,
line 42,改为#define _CONSUME_AS_LONG_AS(conditionFails) while (true) { ch = readChar(); if (ch==-1 ||
(!(conditionFails) || str.len >= LUCENE_MAX_WORD_LEN) ||_CJK) { break; } str.appendChar(ch);}
这样,当当前字符为ALPHA,而下一个字符为CJK时,就可以从宏CONSUME_WORD 中跳出。
另外,StandardTokenizer::ReadAlphaNum()中有个switch(ch)语句,在最后一行加上default:unReadChar();这样可以保证不漏掉跳出后,英文单词后紧跟的第一个CJK字符。

             五、输入的查询语句在转化为QUERY对象时必须以标点符号结束,否则会被处理为一个不完整句子,最后的几个字符不能写入索引项。

ps:为了能显示wchar_t类型的字符串,还必须使用Wcout函数代替 printf函数。   ^_^

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值