在上一篇文章http://blog.youkuaiyun.com/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集就出来了,下面一篇文章我会利用我们的项目定制化的这个方法来进行讲解。