概述:字符正規化是指在分詞之前把繁體轉成簡體、大寫轉成小寫等,在自然語言處理中這是必不可以的一個步驟!在hanlp中的實現方法是基于詞典的,也就是正規則字符對照表。就是“data/dictionary/other/CharTable.txt” 這個詞典,打開后是下面這個樣子的!
?=《
「=“
」=”
『=‘
』=’
【=《
〗="
〝="
〞="
と=之
ふ=子
ル=兒
ㄖ=日
丟=丟
在java程序中如何實現呢,相信大部分人會想用到用HashMap緩存起來不就可以了嗎!當然,這個方法是可行的,但是HashMap在數據量比較大時,時間復雜度是接近O(n)的。這也是為什么加載詞典用trie樹,而不是直接用HashMap的原因了,當然內存也是一個方面,本篇文章不會討論!下面我們來看下hanlp代碼里的具體實現。
在hanlp中,是采用一維數據實現的,下面一步步來看源碼的實現!源碼位于com.hankcs.hanlp.HanLP包下的CharTable類中,這個類主是要加把 CharTable.txt加載到一維數組中。為了方便閱讀,下面直接在代碼中加入注釋!
在分詞之前會首化調用正規化接口(在啟用正規化的情況下)
? ? public List
? ? {
? ? ? ? assert text != null;
? ? ? ? if (HanLP.Config.Normalization)
? ? ? ? {
? ? ? ? ? ? CharTable.normalization(text);//執行正規化
? ? ? ? }
? ? ? ? return segSentence(text);
? ? }
下面來看下CharTable.normalization(text);這個函數的實現:這個函數極其簡單,就是對text中的每個字符查詢一維數據COVERT,看到這里應該就能明白,正規化最重要的就是加載txt文件到CONVERT數組中
? ? public static void normalization(char[] charArray)
? ? {
? ? ? ? assert charArray != null;
? ? ? ? for (int i = 0; i < charArray.length; i++)
? ? ? ? {
? ? ? ? ? ? charArray[i] = CONVERT[charArray[i]];
? ? ? ? }
? ? }
下面看具本的代碼,敝人在代碼中都加入了注釋,此處不再另行講解
/**
?* 字符正規化表
?* @author hankcs
?*/
public class CharTable
{
? ? /**
? ? ?* 正規化使用的對應表
? ? ?* 存儲原理是CONVERT[line.charAt(0)] = CONVERT[line.charAt(2)];
? ? ?* line.charAt(0)是詞典中的源始字符(如①),line.charAt(2)是正規化后的字符(如一)
? ? ?* ①=一
* ②=二
* ④=四
* ⑤=伍
* 這樣以來在正規化時直接 charArray[i] = CONVERT[charArray[i]];就可以了,時間復雜度是O(1)
? ? ?*/
? ? public static char[] CONVERT;
?
? ? static
? ? {
? ? ? ? long start = System.currentTimeMillis();
? ? ? ? if (!load(HanLP.Config.CharTablePath))//通過static語句塊加載詞典,hanlp中所有的詞典都是這種方法加載的
? ? ? ? {
? ? ? ? ? ? logger.severe("字符正規化表加載失敗");
? ? ? ? ? ? System.exit(-1);
? ? ? ? }
? ? ? ? logger.info("字符正規化表加載成功:" + (System.currentTimeMillis() - start) + " ms");
? ? }
?
? ? /**
? ? ?* 首先償試加載CharTable.txt.bin序列化詞典,首次編譯好詞典會序列化到CharTable.txt.bin中
? ? ?* 如果CharTable.txt.bin不存在,則加載CharTable.txt文件
? ? ?* 對于這個詞典來說加載CharTable.txt.bin和CharTable.txt在效率上基本上是沒有區別的,因為不存在編譯的過程
? ? ?* 便CoreNatureDictionary.txt這類詞典因為要編譯成trie樹,是需要一定時間的
? ? ?* @param path
? ? ?* @return
? ? ?*/
? ? private static boolean load(String path)
? ? {
? ? ? ? String binPath = path + Predefine.BIN_EXT;
? ? ? ? if (loadBin(binPath)) return true;//二進制的詞典存在直接讀入到CONVERT數組中即可
? ? ? ? CONVERT = new char[Character.MAX_VALUE + 1];
? ? ? ? for (int i = 0; i < CONVERT.length; i++)//這個循環用來初始化數組,避免在使用時出現null的情況
? ? ? ? {
? ? ? ? ? ? CONVERT[i] = (char) i;
? ? ? ? }
? ? ? ? IOUtil.LineIterator iterator = new IOUtil.LineIterator(path);//讀入txt對照表
? ? ? ? while (iterator.hasNext())
? ? ? ? {
? ? ? ? ? ? String line = iterator.next();
? ? ? ? ? ? if (line == null) return false;
? ? ? ? ? ? if (line.length() != 3) continue;
? ? ? ? ? ? CONVERT[line.charAt(0)] = CONVERT[line.charAt(2)];//這個其實就是正規化時的對照表,雖然簡單的一條語句就實現了, 但是這種思考問題的方式和編碼風格還是非常值和得學習的
? ? ? ? }
? ? ? ? logger.info("正在緩存字符正規化表到" + binPath);
? ? ? ? IOUtil.saveObjectTo(CONVERT, binPath);
?
? ? ? ? return true;
? ? }
?
? ? /**
? ? ?* 這個函數主要用來加載.bin對照表到CONVERT數組中
? ? ?* @param path
? ? ?* @return
? ? ?*/
? ? private static boolean loadBin(String path)
? ? {
? ? ? ? try
? ? ? ? {
? ? ? ? ? ? ObjectInputStream in = new ObjectInputStream(IOUtil.newInputStream(path));
? ? ? ? ? ? CONVERT = (char[]) in.readObject();
? ? ? ? ? ? in.close();
? ? ? ? }
? ? ? ? catch (Exception e)
? ? ? ? {
? ? ? ? ? ? logger.warning("字符正規化表緩存加載失敗,原因如下:" + e);
? ? ? ? ? ? return false;
? ? ? ? }
?
? ? ? ? return true;
? ? }
?
? ? /**
? ? ?* 將一個字符正規化
? ? ?* @param c 字符
? ? ?* @return 正規化后的字符
? ? ?*/
? ? public static char convert(char c)
? ? {
? ? ? ? return CONVERT[c];
? ? }
?
? ? public static char[] convert(char[] charArray)
? ? {
? ? ? ? char[] result = new char[charArray.length];
? ? ? ? for (int i = 0; i < charArray.length; i++)
? ? ? ? {
? ? ? ? ? ? result[i] = CONVERT[charArray[i]];
? ? ? ? }
?
? ? ? ? return result;
? ? }
?
? ? public static String convert(String charArray)
? ? {
? ? ? ? assert charArray != null;
? ? ? ? char[] result = new char[charArray.length()];
? ? ? ? for (int i = 0; i < charArray.length(); i++)
? ? ? ? {
? ? ? ? ? ? result[i] = CONVERT[charArray.charAt(i)];
? ? ? ? }
?
? ? ? ? return new String(result);
? ? }
?
? ? /**
? ? ?* 正規化一些字符(原地正規化)
? ? ?* @param charArray 字符
? ? ?*/
? ? public static void normalization(char[] charArray)
? ? {
? ? ? ? assert charArray != null;
? ? ? ? for (int i = 0; i < charArray.length; i++)
? ? ? ? {
? ? ? ? ? ? charArray[i] = CONVERT[charArray[i]];
? ? ? ? }
? ? }
}
文章來源于亞當-adam的博客
評論