欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久

打開(kāi)APP
userphoto
未登錄

開(kāi)通VIP,暢享免費電子書(shū)等14項超值服

開(kāi)通VIP
構建基于詞典的Lucene分析器

構建基于詞典的Lucene分析器

發(fā)布日期:2006年09月03日,更新日期:2006年10月03日
Lucene是Apache的一個(gè)基于Java的開(kāi)放源代碼的搜索軟件包,也是目前最為流行的搜索軟件包。但是對于絕大多數中文用戶(hù)來(lái)說(shuō)其提供的兩個(gè)中文分析器(ChineseAnalyzer和CJKAnalyzer)的能力又太弱了,因此我們有必要開(kāi)發(fā)適合自己的中文分析器。這篇文章中給出了一個(gè)基于詞典的簡(jiǎn)單的實(shí)現。

實(shí)現這個(gè)中文分析器的過(guò)程就像是一場(chǎng)精彩的賽事。好了,讓我們馬上開(kāi)始。

冗長(cháng)的代碼常常是復雜性的標志,會(huì )導致代碼難以測試和維護。

這是我在近期的文章中隨便找來(lái)的一句話(huà),將用它來(lái)闡明我們將要做什么和做到什么程度。

既然是比賽嘛就不能沒(méi)有對手!我們的兩個(gè)對手分別是ChineseAnalyzer和CJKAnalyzer。兵法云:知己知彼,百戰不殆。那就讓我們一起來(lái)了解一下我們的這兩位對手。

ChineseAnalyzer和CJKAnalyzer都源于A(yíng)pache,但它們不是Lucene的核心組件而是Sandbox組件。它們都繼承自Analyzer,這是Lucene的核心組件之一,負責完成分詞任務(wù)。我們將要完成的基于詞典的分析器MMChineseAnalyzer也繼承了Analyzer,這是實(shí)現Lucene分析器所必須的。

了解了我們對手的名門(mén)血統之后,該看看他們可以做什么了。為了更好的進(jìn)行描述,我們規定:如果沒(méi)有特別的指出,那么Ci將表示一個(gè)字符。這樣一個(gè)句子就可以表示成Ci的序列,即C1C2...Ci...Cn。

ChineseAnalyzer

如果用ChineseAnalyzer來(lái)切分句子C1C2...Ci...Cn,那么切分的結果為C1,C2,...,Ci,...,Cn。

您可以運行下面的代碼來(lái)查看分詞的效果:

清單 1. 測試ChineseAnalyzer分析器的分詞效果

public static void main(String[] args) {Analyzer analyzer = new ChineseAnalyzer();QueryParser queryParser = new QueryParser( "field", analyzer);queryParser.setDefaultOperator(QueryParser.AND_OPERATOR);Query query = null;try {String test = "冗長(cháng)的代碼常常是復雜性的標志,會(huì )導致代碼難以測試和維護。";query = queryParser.parse(test);System.out.println(query.toString("field"));} catch (ParseException e) {e.printStackTrace();}}

清單 1 中的代碼輸出的結果為,“冗 長(cháng) 的 代 碼 常 常 是 復 雜 性 的 標 志 會(huì ) 導 致 代 碼 難 以 測 試 和 維 護”。

這段代碼和清單 2、清單 9中的代碼都是為了測試分析器的分詞效果而特別構建的,它們的不同僅僅在于使用了不同的分析器,因此只在這里做一些解釋?zhuān)院缶筒辉谫樖隽?。代碼中的QueryParser使用相應的分析器analyzer將指定的字符串test進(jìn)行切分,為了比較切分的效果我們只是簡(jiǎn)單的進(jìn)行了輸出。

CJKAnalyzer

如果用CJKAnalyzer來(lái)切分句子C1C2...Ci...Cn,那么切分的結果為C1C2,C2C3,...,Ci-1Ci,CiCi+1,...,Cn-1Cn。

您可以運行下面的代碼來(lái)查看分詞的效果:

清單 2. 測試CJKAnalyzer分析器的分詞效果

public static void main(String[] args) {Analyzer analyzer = new CJKAnalyzer();QueryParser queryParser = new QueryParser( "field", analyzer);queryParser.setDefaultOperator(QueryParser.AND_OPERATOR);Query query = null;try {String test = "冗長(cháng)的代碼常常是復雜性的標志,會(huì )導致代碼難以測試和維護。";query = queryParser.parse(test);System.out.println(query.toString("field"));} catch (ParseException e) {e.printStackTrace();}}

清單 2 中的代碼輸出的結果為,“冗長(cháng) 長(cháng)的 的代 代碼 碼常 常常 常是 是復 復雜 雜性 性的 的標 標志 會(huì )導 導致 致代 代碼 碼難 難以 以測 測試 試和 和維 維護”。這段代碼和清單 1 和 9 中的代碼類(lèi)似。

MMChineseAnalyzer

這是我們即將要實(shí)現的基于詞典的中文分析器,使用它可以得到更好一些的分詞效果,不信,您可以查看下面的輸出結果(稍后我將給出完整的實(shí)現代碼):

“冗長(cháng) 的 代碼 常常 是 復雜性 的 標志 會(huì ) 導致 代碼 難以 測試 和 維護”。

起跑

哎,等了這么久,隊員們總算站到起跑線(xiàn)上了。比賽就要開(kāi)始了 ...

文中的討論和實(shí)現都是采用Unicode編碼并且只處理了CJK Unified Ideographs和Basic Latin兩個(gè)子集中的字符。

CJK Unified Ideographs中主要是象形文字字符,編碼的范圍(以十六進(jìn)制表示的)從4E00開(kāi)始到9FBF結束,共有20928個(gè)字符,這在Unicode編碼中算是一個(gè)比較大的子集。您可以通過(guò)圖示 1 來(lái)大概的了解一下該子集中都包含一些什么字符,這里不可能展示的很全。如果您對更加詳細的內容感興趣,請參閱 參考資料 ,在那里可以找到一個(gè)包含該子集中全部字符的PDF文檔。

圖示 1. CJK Unified Ideographs

 

Basic Latin中主要是ASCII碼字符,編碼的范圍(以十六進(jìn)制表示的)從0000開(kāi)始到007F結束,共有128個(gè)字符。您可以通過(guò)圖示 2 來(lái)大概的了解一下該子集中都包含一些什么字符,這里不可能展示的很全。如果您對更加詳細的內容感興趣,請參閱 參考資料 ,在那里可以找到一個(gè)包含該子集中全部字符的PDF文檔。

圖示 2. Basic Latin

 

這里說(shuō)了這么多,不過(guò)看起來(lái)好象和今天的賽事沒(méi)有什么太大的關(guān)系。其實(shí)不然,我們在掃描待分析的句子時(shí)將會(huì )判斷每一個(gè)取到的字是否在CJK Unified Ideographs和Basic Latin字符集中。如果不在我們就簡(jiǎn)單的丟掉這個(gè)字符,換句話(huà)講,我們只處理這兩個(gè)字符集中的字符。這在大多數場(chǎng)景中已經(jīng)完全可以滿(mǎn)足需要了。如果您覺(jué)得這些還不夠,則可以自己擴充需要的字符集。這些工作比較簡(jiǎn)單,您不妨親自試一試。

細心的您可能發(fā)現還有一個(gè)問(wèn)題我們沒(méi)有提到。對,就是如果兩個(gè)連續的字符分別屬于不同的字符集時(shí)怎么處理。在這里提到的算法中我們將兩個(gè)字符進(jìn)行切分,具體的實(shí)現我們將在實(shí)現中詳細描述。

加速

比賽已經(jīng)開(kāi)始了,隊員們跑得越來(lái)越快 ...

詞典結構對于基于詞典的分析器就像是跑鞋對于隊員們一樣的重要,能夠為他們的獲勝助一臂之力。我們要使用的詞典結構比較簡(jiǎn)單,是基于文本的。每個(gè)詞占用一行并且可以使用#進(jìn)行注釋?zhuān)⑨尩膬热輰?huì )被忽略。詞典中也不會(huì )收錄單字詞。

清單 3. 詞典結構

#雙字詞C1C2#三字詞C3C4C5#四字詞C6C7C8C9::

依次類(lèi)推,n字詞可以表示為C1C2...Cn,注意這里不要和句子的表示混淆了。

在實(shí)際中我們的詞典看起來(lái)是下面的這個(gè)樣子,被存儲在一個(gè)文本文件中,使用UTF-8編碼。如果您想用類(lèi)記事本的編輯器打開(kāi)請注意編碼問(wèn)題。

清單 4. 以文本形式存在的實(shí)際的詞典結構

代碼冗長(cháng)復雜性導致常常標志測試維護難以::

有了這樣的一個(gè)基于文本的詞典,接下來(lái)就要考慮以什么樣的規則將它裝載到內存中更利于算法的實(shí)現了。

首先,我們先把n(n > 2)字詞本身裝載到內存中中,然后在把C1C2,C1C2C3,...,一直到C1C2...Cn-1這n-1個(gè)字串也裝載到內存中,注意拆分后的這n-1個(gè)字串可能并不是詞。如果n=2時(shí)我們直接裝載C1C2,這時(shí)不進(jìn)行拆分裝載。因為我們沒(méi)有收錄單字詞所以n不可能為1。

我們使用一個(gè)四字的詞如一舉成名來(lái)演示一下裝載的過(guò)程。首先將一舉成名裝載到內存中,之后在將一舉、一舉成裝載到內存中。按照這個(gè)裝載規則,我們有:

清單 5. 裝載之前的詞典

:復雜性:

清單 6. 裝載之后的詞典

:復雜性復雜:

我們之所以采用這樣的裝載規則是和算法的實(shí)現相關(guān)的,它可以使得算法的實(shí)現更加簡(jiǎn)潔。如果您讀到這里還不太了解如此裝載的目的,沒(méi)有關(guān)系,您只要記住這種方式就可以了。相信在讀完算法描述后會(huì )有豁然開(kāi)朗的感覺(jué),繼續往下進(jìn)行吧。

我們所采用的算法,簡(jiǎn)單的說(shuō),就是將句子從左到右掃描一遍,遇到詞典中有的詞,就把它切分出來(lái)。如果遇到如樸素大方這樣的復合詞就按最長(cháng)規則取樸素大方進(jìn)行匹配,而不是切分成樸素和大方兩個(gè)詞。如果有字串不能被識別,就把它切分成單字詞。這樣一個(gè)簡(jiǎn)單的分詞算法就完成了。

下面我們具體的來(lái)描述一下這個(gè)算法:

1. 使用一個(gè)變量word來(lái)保存切分過(guò)程中的字符序列

2. 開(kāi)始掃描句子C1C2...Ci...Cn,取出字符Ci

2.1 如果word的長(cháng)度為0,那么將Ci附加到word中,將i增1,返回到步驟 2

2.2 如果word的長(cháng)度大于0,那么將Ci和word連接到一起,并將連接后的字符序列與詞典匹配(裝載之后的形式)

2.2.1 如果匹配成功,則將Ci附加到word中同時(shí)將i增1,返回到步驟 2

2.2.2 如果匹配不成功,說(shuō)明word中的字符序列是可匹配的最長(cháng)的詞,進(jìn)行切分,同時(shí)將word清空(即長(cháng)度為0)但保持i不變,返回到步驟 2

3. 當取完句子中的所有字符時(shí)結束

這只是對于算法的一個(gè)輪廓性的描述,并且只適合句子中只有漢字的情況。如果是漢字和拉丁文混合的情況,那么還要添加一些處理的邏輯。細節可以參閱完整的源代碼,這里不在贅述。

沖刺

最后的精彩時(shí)刻來(lái)臨了,隊員們開(kāi)始沖刺了 ...

清單 7. MMChineseAnalyzer分析器的完整源代碼

package org.solol.lucene.analysis;import java.io.Reader;import java.util.Set;import org.apache.lucene.analysis.Analyzer;import org.apache.lucene.analysis.StopFilter;import org.apache.lucene.analysis.TokenStream;/*** @author solo L**/public class MMChineseAnalyzer extends Analyzer {public final static String[] STOP_WORDS = {};private Set stopTable;public MMChineseAnalyzer() {stopTable = StopFilter.makeStopSet(STOP_WORDS);}public TokenStream tokenStream(String fieldName, Reader reader) {return new StopFilter(new MMChineseTokenizer(reader), stopTable);}}

清單 7 中的類(lèi)MMChineseAnalyzer是文中要實(shí)現的基于詞典的分析器的主類(lèi),它沒(méi)有直接實(shí)現分詞的具體邏輯,而是將這些邏輯代理給了清單 8 中的MMChineseTokenizer類(lèi)。其中的STOP_WORDS用來(lái)指定在切分的過(guò)程中要忽略的一些在中文中沒(méi)有太大意義的字或詞,比如的、地、得、這等等。

清單 8. MMChineseTokenizer的完整源代碼

package org.solol.lucene.analysis;import java.io.BufferedReader;import java.io.FileInputStream;import java.io.IOException;import java.io.InputStream;import java.io.InputStreamReader;import java.io.Reader;import java.util.TreeMap;import org.apache.lucene.analysis.Token;import org.apache.lucene.analysis.Tokenizer;/*** @author solo L**/public class MMChineseTokenizer extends Tokenizer {//我們沒(méi)有處理5字或5字以上的次,如果您需要處理可以修改這里private static final int WORD_MAX_LENGTH = 4;private static TreeMap<String, String> dictionary = null;private static final int IO_BUFFER_SIZE = 2048;private int bufferIndex = 0;private int dataLength = 0;private int offset = 0;private final char[] ioBuffer = new char[IO_BUFFER_SIZE];private String tokenType = "word";public MMChineseTokenizer(Reader input) {this.input = input;}public Token next() throws IOException {//裝載詞典loadWords();StringBuffer word = new StringBuffer();while (true) {char c;char nextChar;Character.UnicodeBlock cUnicodeBlock;Character.UnicodeBlock nextCharUnicodeBlock;offset++;if (bufferIndex >= dataLength) {dataLength = input.read(ioBuffer);bufferIndex = 0;}if (dataLength == -1) {if (word.length() == 0) {return null;} else {break;}}c = ioBuffer[bufferIndex++];cUnicodeBlock = Character.UnicodeBlock.of(c);nextChar = ioBuffer[bufferIndex];nextCharUnicodeBlock = Character.UnicodeBlock.of(nextChar);boolean isSameUnicodeBlock = cUnicodeBlock.toString().equalsIgnoreCase(nextCharUnicodeBlock.toString());if (cUnicodeBlock == Character.UnicodeBlock.CJK_UNIFIED_IDEOGRAPHS) {tokenType = "double";if (word.length() == 0) {word.append(c);// 增強部分--開(kāi)始if (word.length() != 0 && (!isSameUnicodeBlock)) {break;}// 增強部分--結束} else {String temp = (word.toString() + c).intern();if (dictionary.containsKey(temp)) {word.append(c);// 增強部分--開(kāi)始if (word.length() != 0 && (!isSameUnicodeBlock)) {break;}// 增強部分--結束} else {bufferIndex--;offset--;break;}}} else if (cUnicodeBlock == Character.UnicodeBlock.BASIC_LATIN) {tokenType = "single";if (Character.isWhitespace(c)) {if (word.length() != 0)break;} else {word.append(c);// 增強部分--開(kāi)始if (word.length() != 0 && (!isSameUnicodeBlock)) {break;}// 增強部分--結束}}}Token token = new Token(word.toString(), offset- word.length(), offset, tokenType);word.setLength(0);return token;}public void loadWords() {if (dictionary == null) {dictionary = new TreeMap<String, String>();InputStream is = null;InputStreamReader isr = null;BufferedReader br = null;try {is = new FileInputStream("dictionary.txt");isr = new InputStreamReader(is, "UTF-8");br = new BufferedReader(isr);String word = null;while ((word = br.readLine()) != null) {int wordLength = word.length();if ((word.indexOf("#") == -1) && (wordLength <= WORD_MAX_LENGTH)) {dictionary.put(word.intern(), "1");int i = wordLength-1;while(i >= 2){String temp = word.substring(0, i).intern();if (!dictionary.containsKey(temp)) {dictionary.put(temp,"2");}i--;}}}} catch (IOException e) {e.printStackTrace();}finally{try {if(br!=null){br.close();}if(isr!=null){isr.close();}if(is!=null){is.close();}} catch (IOException e) {e.printStackTrace();}}}}}

清單 8 中的代碼有兩處需要注意一下,分別是next()方法和loadWords()方法。在next()方法的開(kāi)始處,我們調用了loadWords()方法,它依照文章前部描述的規則將詞典裝載到內存中。為了提高算法的效率這里使用了Singleton模式,這保證詞典只被裝載一次。另外,還專(zhuān)門(mén)選擇了TreeMap這個(gè)數據結構,這有利于查找。

在裝載了詞典之后,我們就可以進(jìn)行分詞了。先將input輸入流中的內容讀到ioBuffer緩沖區中,下面將逐個(gè)掃描ioBuffer緩沖區中的字符,處理過(guò)程依照文章前部描述的算法過(guò)程,不在贅述。

在上面描述算法使用的兩個(gè)字符集時(shí),提到過(guò)如果有兩個(gè)連續的字符分別屬于不同的字符集時(shí),我們也要把它們切分開(kāi)。這體現在實(shí)現的代碼中就是boolean isSameUnicodeBlock = cUnicodeBlock.toString().equalsIgnoreCase(nextCharUnicodeBlock.toString()),也就是我們同時(shí)也掃描了當前字符的下一個(gè)字符,并判斷這兩個(gè)字符是否處于同一個(gè)字符集。要是不處于同一字符集則將他們分開(kāi)。

完成了清單 8 中的代碼,我們的基于詞典的分析器也就初步完成了。如果您要將它用于實(shí)際,我們希望您能自己進(jìn)行一些測試,以確??煽啃?。

獲勝

終于撞線(xiàn)了 ...

我們給出了完整的源代碼,您可以在這里下載。所有代碼都是在 eclipse 3.2 中完成的,您可以立即把它作為項目導入并運行它。

清單 9. 測試MMChineseAnalyzer分析器的分詞效果

public static void main(String[] args) {Analyzer analyzer = new MMChineseAnalyzer();QueryParser queryParser = new QueryParser( "field", analyzer);queryParser.setDefaultOperator(QueryParser.AND_OPERATOR);Query query = null;try {String test = "冗長(cháng)的代碼常常是復雜性的標志,會(huì )導致代碼難以測試和維護。";query = queryParser.parse(test);System.out.println(query.toString("field"));} catch (ParseException e) {e.printStackTrace();}}

清單 9 給出了測試MMChineseAnalyzer分析器的代碼,這和清單 1 清單 2 類(lèi)似。您可以查看它的輸出,這樣就可以確信我們完成了任務(wù)。

結束語(yǔ)

分詞是一項很有挑戰性的任務(wù),這里的實(shí)現還相當的初級,要想獲得更佳的分詞效果,還有很長(cháng)的路要走。

如果您對分詞非常的感興趣,還想進(jìn)一步深入的研究,我們建議您不妨從研究分詞技術(shù)的發(fā)展歷史開(kāi)始,了解一下前輩們的智慧結晶。

參考資料
  • 如果您還沒(méi)有安裝Eclipse3.2,那么可以到eclipse.org下載。雖然這不是必須的但是它可以使您更加容易的運行文中提到的代碼。
  • 要運行文中的代碼,您還需要到這里下載 Lucene。
  • The Unicode Standard,這里是Unicode編碼的官方主頁(yè)。
  • CJK Unified Ideographs 包含全部CJK Unified Ideographs子集字符的PDF文檔。
  • Basic Latin 包含全部Basic Latin子集字符的PDF文檔。
  • Lucene 的官方主頁(yè)
  • Lucene Sandbox
solo L 一位有些理想主義的軟件工程師,創(chuàng )建了solol.org。他常常在這里發(fā)表一些對技術(shù)的見(jiàn)解。
本站僅提供存儲服務(wù),所有內容均由用戶(hù)發(fā)布,如發(fā)現有害或侵權內容,請點(diǎn)擊舉報。
打開(kāi)APP,閱讀全文并永久保存 查看更多類(lèi)似文章
猜你喜歡
類(lèi)似文章
Web-第二十八天 Lucene&solr使用一【悟空教程】
Solr:文本分析
Analysis分析器(三)
Lucene3.0正式發(fā)布了
中文分詞技術(shù)(中文分詞原理)
lucene、lucene.NET詳細使用與優(yōu)化詳解
更多類(lèi)似文章 >>
生活服務(wù)
分享 收藏 導長(cháng)圖 關(guān)注 下載文章
綁定賬號成功
后續可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點(diǎn)擊這里聯(lián)系客服!

聯(lián)系客服

欧美性猛交XXXX免费看蜜桃,成人网18免费韩国,亚洲国产成人精品区综合,欧美日韩一区二区三区高清不卡,亚洲综合一区二区精品久久