在上一篇文章http://blog.csdn.net/jj380382856/article/details/52174225裏我們對ansj的流程做了簡單的分析,下面我們主要來看ansj中graph的構造以及應用過程。
先貼出上一篇文章中分析過的函數:
private void analysisStr(String temp) {
Graph gp = new Graph(temp);//構造分詞圖
int startOffe = 0;
if (this.ambiguityForest != null) //將近義詞加入圖
GetWord gw = new GetWord(this.ambiguityForest, gp.chars);
String[] params = null;
while ((gw.getFrontWords()) != null) {
if (gw.offe > startOffe) {
analysis(gp, startOffe, gw.offe);
}
params = gw.getParams();
startOffe = gw.offe;
for (int i = 0; i < params.length; i += 2) {
gp.addTerm(new Term(params[i], startOffe, new TermNatures(new TermNature(params[i + 1], 1))));
startOffe += params[i].length();
}
}
}
if (startOffe < gp.chars.length - 1) {
analysis(gp, startOffe, gp.chars.length);
}
List<Term> result = this.getResult(gp);<span> </span>//根據圖得到分詞後的term集合
terms.addAll(result);<span> </span>//將temp分出的所有term加入
}
Graph gp = new Graph(temp);這個函數主要是做了一些初始化操作。analysis(gp, startOffe, gp.chars.length);函數纔是處理的入口
public void analysis(Graph gp, int startOffe, int endOffe) {
int start = 0;
int end = 0;
char[] chars = gp.chars;
String str = null;
char c = 0;
for (int i = startOffe; i < endOffe; i++) {
int status = status(chars[i]); //判斷當前詞的狀態
if (chars[i] == '.') { // 如果當前是小數點並且不是個小數,則將狀態設置爲3
if (i == 0 || !Character.isDigit(chars[i - 1])) {
status = 3;
}
}
switch (status) { // 判斷當前詞的狀態
case 0: //不在詞典中
gp.addTerm(new Term(String.valueOf(chars[i]), i,
TermNatures.NULL));
break;
case 4: // 半角英文字符
start = i;
end = 1;
while (++i < endOffe && status(chars[i]) == 4) {
end++;
}
str = WordAlert.alertEnglish(chars, start, end);
gp.addTerm(new Term(str, start, TermNatures.EN));
i--;
break;
case 5: // 數字,小數點,百分號
start = i;
end = 1;
while (++i < endOffe && status(chars[i]) == 5) {
end++;
}
str = WordAlert.alertNumber(chars, start, end);
gp.addTerm(new Term(str, start, TermNatures.M));
i--;
break;
default: //其他情況則進入雙數組自動機繼續分詞
start = i;
end = i;
c = chars[start];
while (IN_SYSTEM[c] > 0) {
end++;
if (++i >= endOffe)
break;
c = chars[i];
}
if (start == end) {
gp.addTerm(new Term(String.valueOf(c), i, TermNatures.NULL));
continue;
}
gwi.setChars(chars, start, end);
while ((str = gwi.allWords()) != null) {
gp.addTermBase(new Term(str, gwi.offe, gwi.getItem()));
}
/**
* 如果未分出詞.以未知字符加入到gp中
*/
if (IN_SYSTEM[c] > 0 || status(c) > 3) {
i -= 1;
} else {
gp.addTerm(new Term(String.valueOf(c), i, TermNatures.NULL));
}
break;
}
}
}
public static int status(char c) {
Item item = (AnsjItem) DAT.getDAT()[c];
if (item == null) {
return 0;
}
return item.status;
}
可以看出,這裏面用到了DAT,這就是雙數組樹的縮寫。它是在類被加載時初始化的。
private static DoubleArrayTire loadDAT() {
long start = System.currentTimeMillis();
try {
DoubleArrayTire dat = DoubleArrayTire.loadText(DicReader.getInputStream("core.dic"), AnsjItem.class);//加載核心詞典
/**
* 人名識別必備的
*/
personNameFull(dat);
/**
* 記錄詞典中的詞語,並且清除部分數據
*/
for (Item item : dat.getDAT()) {
if (item == null || item.name == null) {
continue;
}
if (item.status < 4) {
for (int i = 0; i < item.name.length(); i++) {
IN_SYSTEM[item.name.charAt(i)] = item.name.charAt(i);
}
}
if (item.status < 2) {
item.name = null;
continue;
}
}
// 特殊字符標準化
IN_SYSTEM['%'] = '%';
MyStaticValue.LIBRARYLOG.info("init core library ok use time :" + (System.currentTimeMillis() - start));
return dat;
} 。。。。。
下面來看看core.dic長什麼樣,第一行是詞典行數
下面每一行是一個詞的信息。每一列具體代表什麼,請看代碼:
@Override
public void initValue(String[] split) {
index = Integer.parseInt(split[0]);
base = Integer.parseInt(split[2]);
check = Integer.parseInt(split[3]);
status = Byte.parseByte(split[4]);
// if (split[1].equals(" ")) {
// System.out.println(split.toString());
// }
if (status > 1) {
name = split[1];
termNatures = new TermNatures(
TermNature.setNatureStrToArray(split[5]), index);
} else {
termNatures = new TermNatures(TermNature.NULL);
}
}
具體的base,index,check,status請百度雙數組的結構,後續我也會寫寫。回到analysis,這個函數就是利用核心詞典的雙數組樹來對當前的待分詞串進行分詞。
case 4以及case 5主要對連續的英文和數字進行收集,default裏面對中文進行分詞,其中重要的是gwi.allWords()函數。下面是代碼:
public String allWords() {
for (; i < charsLength; i++) {
strName = strName + String.valueOf(chars[i]);
charHashCode = chars[i];
end++;
switch (getStatement()) {
case 0:
if (baseValue == chars[i]) {
str = String.valueOf(chars[i]);
offe = i;
start = ++i;
end = 0;
baseValue = 0;
strName = "";
tmpstrName = strName;
tempBaseValue = baseValue;
return str;
} else { //如果當前詞在詞典不存在,則去除前綴重新判定
i = start;
start++;
end = 0;
baseValue = 0;
strName = "";
break;
}
case 2: //當前的strName是一個詞還可以繼續
i++;
offe = start;
tempBaseValue = baseValue;
tmpstrName = strName;
return tmpstrName;
case 3: //當前的strName是一個詞,並且結束直接返回
offe = start;
start++;
i = start;
end = 0;
tempBaseValue = baseValue;
baseValue = 0;
tmpstrName = strName;
strName = "";
return tmpstrName;
}
}
if (start++ != i) {
i = start;
baseValue = 0;
strName = "";
return allWords();
}
end = 0;
baseValue = 0;
strName = "";
i = 0;
return null;
}
這裏面就是利用雙數組的狀態轉移來對句子進行分詞構建graph。構建完成後繼續調用List<Term> result = this.getResult(gp);函數
protected abstract List<Term> getResult(Graph graph);可以看出這是個抽象方法,用戶可以在這裏面定義自己的最優路徑選擇方法,
最優路徑找到後,分詞後的trems集就出來了,下面一篇文章我會利用我們的項目定製化的這個方法來進行講解。