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