全書以零基礎的讀者自學完成一個中文分詞系統作為目標。從Java基礎語法開始,然后到文本處理相關的數據結構和算法,最后實現文本切分和詞性標注。本書是少有的介紹業界熱門的Java開發中文分詞的書籍。本書選取相關領域的經典內容深入理解和挖掘,也綜合了實踐性強的創新想法。適用于對軟件開發感興趣的青少年或者大學生。
“前門到了,請在后門下車。”把“前門”標注成地名就容易理解這句話了。從種地到買菜、買房、養生保健以及投資理財等,都可以用到中文分詞等文本信息挖掘技術。
各行業都在構建越來越復雜的軟件系統,很多系統都會用到文本處理技術。但是即使在計算機專業,也有很多人對文本信息處理相關技術不太了解。其實,學習相關技術的門檻并不高。而本書就是為了普及相關開發而做的一次新的嘗試,其中也結合了作者自己的研究成果,希望為推動相關應用的發展做出貢獻。
本書借助計算機語言Java實現中文文本信息處理,試圖通過恰當的數據結構和算法來應對一些常見的文本處理任務。相關代碼可以從清華大學出版社的網站下載。
本書的第1章到第3章介紹了相關的Java開發基礎。第4章介紹處理文本所用到的有限狀態機基本概念和具體實現。第5章介紹相關的基礎數據結構。第6章到第9章介紹中文分詞原理與實現。
書中的很多內容來源于作者的開發和教學實踐。作者的實踐經驗還體現在相關的其他書中,如《自己動手寫搜索引擎》、《自然語言處理原理與技術實現》、《自己動手寫網絡爬蟲》、《使用C#開發搜索引擎》、《解密搜索引擎技術實戰》等。相對于作者編寫的其他書籍,本書更加注意零基礎入門。
學習是個循序漸進的過程。可以在讀者群中共同學習。群體往往比單個人有更多的智慧產出。為了構建出更好的技術群體,請加讀者QQ群(453406621)交流。希望快速入門的讀者也可以參加相關培訓。這本書最開始是為一位從蘇州專門來北京現場學習的學員入門中文分詞而編寫。感謝他為編寫本書提供的幫助。
也希望通過本書能結識更多的同行。有您真誠的建議,我們會發展得更好。例如,通過與同行的交流,讓我們的數量、日期等量化信息的提取工具更加成熟。當前,語義分析等文本處理技術仍然需要更深入的發展,來更好地支持各行業的智能軟件開發。
本書由羅剛、張子憲、崔智杰編著,參與本書編寫的還有石天盈、張繼紅、童曉軍,在此一并表示感謝。感謝開源軟件和我們的家人、關心我們的老師和朋友、創業伙伴,以及選擇獵兔自然語言處理軟件的客戶多年來的支持。
編 者
羅剛,計算機軟件碩士,畢業于吉林工業大學。2005年創立北京盈智星科技發展有限公司,2008年聯合創立上海數聚軟件公司。獵兔搜索創始人,當前獵兔搜索在北京和上海以及石家莊均設有研發部。帶領獵兔搜索技術開發團隊先后開發出獵兔中文分詞系統、獵兔文本挖掘系統,智能垂直搜索系統以及網絡信息監測系統等,實現互聯網信息的采集、過濾、搜索和實時監測,其開發的搜索軟件日用戶訪問量達萬次以上。
第4章 處理文本
網上聊天時,可能會遇到過找錯對象的尷尬事情。程序應該可以幫助判斷聊天對象是否正確。
XML和JSON這樣的文本格式很流行,因為不僅程序可以讀,人也是可以讀懂的。這樣的文本格式也需要解析。
4.1 字符串操作
經常需要分割字符串。例如IP地址127.0.0.1按.分割。可以先用String類中的indexOf方法來查找子串“.”,然后再截取子串。例如:
String inputIP = "127.0.0.1"; //本機IP地址
int p = inputIP.indexOf('.'); //返回位置3
這里的‘.’在字符串“127.0.0.1”中出現了多次。因為是從頭開始找起,所以返回第一次出現的位置3。
如果沒有找到子串,則indexOf返回-1。例如要判斷虛擬機是否為64位的:
//當在32位虛擬機時,將返回32;而在64位虛擬機時,返回64
String x = System.getProperty("sun.arch.data.model");
System.out.println(x); //在32位虛擬機中輸出32
System.out.println(x.indexOf("64")); //輸出-1
如果找到了,則返回的值不小于0。所以可以這樣寫:
if (x.indexOf("64") < 0) {
System.out.println("32位虛擬機");
}
indexOf(String str, int fromIndex)從指定位置開始查找。例如:
String inputIP = "127.0.0.1";
System.out.println(inputIP.indexOf('.', 4)); //輸出5,也就是第二個.所在的位置
從字符串inputIP里尋找點“.”的位置,但尋找的時候,要從inputIP的索引為4的位置開始,這就是第二個參數4的作用,由于索引是從0開始的,這樣,實際尋找的時候是從字符0開始的,所以輸出5,也就是第二個點“.”所在的位置。
String.subString取得原字符串其中的一段,也就是子串。傳入兩個參數:開始位置和結束位置。例如:
String inputIP = "127.0.0.1";
int p = inputIP.indexOf('.');
int q = inputIP.indexOf('.', p+1);
String IPsection1 = inputIP.substring(0, p); //得到"127"
String IPsection2 = inputIP.substring(p+1, q); //得到"0"
StringTokenizer類專門用來按指定字符分割字符串。StringTokenizer的nextToken()方法取得下一段字符串。
hasMoreElements()方法判斷是否還有字符串可以讀出。可以在StringTokenizer的構造方法中指定用來分隔字符串的字符。
例如分割IP地址:
String inputIP = "127.0.0.1";
StringTokenizer token =
new StringTokenizer(inputIP, "."); //用.分割IP地址串
while(token.hasMoreElements()) { //有更多的子串
System.out.print(token.nextToken() + " "); //輸出下一個子串
}
StringTokenizer默認按空格分割字符串。例如翻譯英文句子:
HashMap ecMap = new HashMap();
ecMap.put("I", "我"); //放入一個鍵/值對
ecMap.put("love", "愛");
ecMap.put("you", "你");
String english = "I love you";
StringTokenizer tokenizer =
new StringTokenizer(english); //用空格分割英文句子
while(tokenizer.hasMoreElements()) { //有更多的詞沒遍歷完
System.out.print(ecMap.get(tokenizer.nextToken())); //輸出:我愛你
}
StringTokenizer有幾個構造方法,其中最復雜的構造方法是:
StringTokenizer(String str, String delim, boolean returnDelims)
如果最后這個參數returnDelims標記是false,則分隔字符只作為分隔詞使用,一個返回的詞是不包括分隔符號的最長序列。如果最后一個參數標記是true,則返回的詞可以是分隔字符。默認是false,也就是不返回分隔字符。
如果需要把字符串存入二進制文件。可能會用到字符串和字節數組間的互相轉換。首先看一下如何從字符串得到字節數組:
String word = "的";
byte[] validBytes = word.getBytes("utf-8"); //字符串轉換成字節數組
System.out.println(validBytes.length); //輸出長度是3
可以直接調用Charset.encode實現字符串轉字節數組:
Charset charset = Charset.forName("utf-8"); //得到字符集
CharBuffer data = CharBuffer.wrap("數據".toCharArray());
ByteBuffer bb = charset.encode(data);
System.out.println(bb.limit()); //輸出數據的實際長度6
Charset.decode把字節數組轉回字符串:
byte[] validBytes = "程序設計".getBytes("utf-8"); //字節數組
//對字節數組賦值
Charset charset = Charset.forName("utf-8"); //得到字符集
//字節數組轉換成字符
CharBuffer buffer = charset.decode(ByteBuffer.wrap(validBytes));
System.out.println(buffer); //輸出結果