结巴分词JAVA版本源码解析(一)
2018-01-09 01:20
1581 查看
话不多说,有了之前的铺垫直接上代码,代码我加了很多自己的注释,应该是比较清楚了,基本分为三个层次来讲,这一部分设计到第一步,即为,预处理,包括初始化等,是分词的第一步,基本包括词典词库的载入,字典树词典的初始化以及填充等工作,生成字典树,还有一点我的代码顺序基本是按照调用顺序来的,重点理解的地方我会标红
1.初始化调用不用多讲,通过初始化调用,及构造方法中的调用,载入用户和搜狗细胞词库,这里值得注意一点的是,实例的产生作者用了线程安全的单例模式,这也就是说在多线程调用的情况下不会出现线程安全的问题
protected void setUp() throws Exception {
WordDictionary.getInstance().init(Paths.get("conf"));
}
//构造方法加载词典
private WordDictionary() {
this.loadDict();
}
3.由于要进行排序,作者重写了比较器public int compareTo(DictSegment o) {
// 对当前节点存储的char进行比较
return this.nodeChar.compareTo(o.nodeChar);
}
这样第一步基本完成了,我们加载了细胞词库,用户词库,作者训练的语料库,并生成了对应的字典树结构,字典树中,采用两种方式存储分支节点,第一步大功告成还是比较简单的
1.初始化调用不用多讲,通过初始化调用,及构造方法中的调用,载入用户和搜狗细胞词库,这里值得注意一点的是,实例的产生作者用了线程安全的单例模式,这也就是说在多线程调用的情况下不会出现线程安全的问题
protected void setUp() throws Exception {
WordDictionary.getInstance().init(Paths.get("conf"));
}
public void init(Path configFile) { String abspath = configFile.toAbsolutePath().toString(); System.out.println("initialize user dictionary:" + abspath); synchronized (WordDictionary.class) { if (loadedPath.contains(abspath)) return; DirectoryStream<Path> stream; try { stream = Files.newDirectoryStream(configFile, String.format(Locale.getDefault(), "*%s", USER_DICT_SUFFIX)); for (Path path: stream){ System.err.println(String.format(Locale.getDefault(), "loading dict %s", path.toString())); singleton.loadUserDict(path); } loadedPath.add(abspath); } catch (IOException e) { // TODO Auto-generated catch block // e.printStackTrace(); System.err.println(String.format(Locale.getDefault(), "%s: load user dict failure!", configFile.toString())); } } }
//构造方法加载词典
private WordDictionary() {
this.loadDict();
}
public static WordDictionary getInstance() { if (singleton == null) { synchronized (WordDictionary.class) { if (singleton == null) { singleton = new WordDictionary(); return singleton; } } } return singleton; }
public void loadUserDict(Path userDict) { loadUserDict(userDict, StandardCharsets.UTF_8); } //加载用户词典和细胞词库,初始化时完成 public void loadUserDict(Path userDict, Charset charset) { try { BufferedReader br = Files.newBufferedReader(userDict, charset); long s = System.currentTimeMillis(); int count = 0; while (br.ready()) { String line = br.readLine(); String[] tokens = line.split("[\t ]+");//匹配制表符号一次或多次,然后每行切分 if (tokens.length < 1) { // Ignore empty line continue; } String word = tokens[0];//前一位置为词,后一个位置是词的频度 double freq = 3.0d;//默认为3 if (tokens.length == 2) freq = Double.valueOf(tokens[1]); word = addWord(word); freqs.put(word, Math.log(freq / total));//对频率取log,放在map结构中 count++; } System.out.println(String.format(Locale.getDefault(), "user dict %s load finished, tot words:%d, time elapsed:%dms", userDict.toString(), count, System.currentTimeMillis() - s)); br.close(); } catch (IOException e) { System.err.println(String.format(Locale.getDefault(), "%s: load user dict failure!", userDict.toString())); } }2.填词,Dictsegment表示字典树的一个分支,也可以理解为一个节点,存储字符,节点的子节点采用数组(DictSegment[])或map(Map(Character, DictSegment))存储,选用标准根据子节点的数量而定。如果子节点的数量小于等于ARRAY_LENGTH_LIMIT,采用数组存储;如果子节点的数量大于ARRAY_LENGTH_LIMIT,采用Map存储。ARRAY_LENGTH_LIMIT默认为3。这样做好处在于,节省内存空间。因为HashMap的方式的方式,肯定是需要预先分配内存的,就可能会存在浪费的现象,但如果全都采用数组去存组(后续采用二分的方式查找),你就无法获得O(1)的算法复杂度。所以这里采用了两者方式,当子节点数很少时,用数组存储,当子结点数较多时候,则全部迁至hashMap中去。在构建过程中,会将每个词一步步地加入到字典树中,这是一个递归的过程
private String addWord(String word) { if (null != word && !"".equals(word.trim())) { String key = word.trim().toLowerCase(Locale.getDefault()); _dict.fillSegment(key.toCharArray()); return key; } else return null; }
private synchronized void fillSegment(char[] charArray, int begin, int length, int enabled){ // 获取字典表中的汉字对象 Character beginChar = new Character(charArray[begin]); Character keyChar = charMap.get(beginChar);//用以检验字典表中是否含有该中文字符 // 字典中没有该字,则将其添加入字典 if (keyChar == null) {//填入词中keychar和beginchar相同 charMap.put(beginChar, beginChar); keyChar = beginChar; } // 搜索当前节点的存储,查询对应keyChar的keyChar,如果没有则创建 DictSegment ds = lookforSegment(keyChar, enabled); if (ds != null) { // 处理keyChar对应的segment if (length > 1) { // 词元还没有完全加入词典树 ds.fillSegment(charArray, begin + 1, length - 1, enabled); } else if (length == 1) { // 已经是词元的最后一个char,设置当前节点状态为enabled, // enabled=1表明一个完整的词,enabled=0表示从词典中屏蔽当前词 ds.nodeState = enabled; } } }
private DictSegment lookforSegment(Character keyChar, int create) {//在分支中查找 DictSegment ds = null; if (this.storeSize <= ARRAY_LENGTH_LIMIT) { // 获取数组容器,如果数组未创建则创建数组 DictSegment[] segmentArray = getChildrenArray(); // 搜寻数组 DictSegment keySegment = new DictSegment(keyChar); //由于二分查找,数组需要有序从而下面排序 int position = Arrays.binarySearch(segmentArray, 0, this.storeSize, keySegment); if (position >= 0) { ds = segmentArray[position]; } // 遍历数组后没有找到对应的segment if (ds == null && create == 1) { ds = keySegment; if (this.storeSize < ARRAY_LENGTH_LIMIT) { // 数组容量未满,使用数组存储 segmentArray[this.storeSize] = ds; // segment数目+1 this.storeSize++; Arrays.sort(segmentArray, 0, this.storeSize); } else { // 数组容量已满,切换Map存储 // 获取Map容器,如果Map未创建,则创建Map Map<Character, DictSegment> segmentMap = getChildrenMap(); // 将数组中的segment迁移到Map中 migrate(segmentArray, segmentMap); // 存储新的segment segmentMap.put(keyChar, ds); // segment数目+1 , 必须在释放数组前执行storeSize++ , 确保极端情况下,不会取到空的数组 this.storeSize++; // 释放当前的数组引用 this.childrenArray = null; } } } else { // 获取Map容器,如果Map未创建,则创建Map Map<Character, DictSegment> segmentMap = getChildrenMap(); // 搜索Map ds = (DictSegment) segmentMap.get(keyChar); if (ds == null && create == 1) { // 构造新的segment ds = new DictSegment(keyChar); segmentMap.put(keyChar, ds); // 当前节点存储segment数目+1 this.storeSize++; } } return ds; }
3.由于要进行排序,作者重写了比较器public int compareTo(DictSegment o) {
// 对当前节点存储的char进行比较
return this.nodeChar.compareTo(o.nodeChar);
}
这样第一步基本完成了,我们加载了细胞词库,用户词库,作者训练的语料库,并生成了对应的字典树结构,字典树中,采用两种方式存储分支节点,第一步大功告成还是比较简单的
相关文章推荐
- 结巴分词JAVA版本源码解析(三)
- 结巴分词JAVA版本源码解析(二)
- 优酷视频解析(16.5.30更新)Java代码版本
- 【转】Java 集合系列16之 HashSet详细介绍(源码解析)和使用示例--不错
- Java 集合系列03之 ArrayList详细介绍(源码解析)和使用示例
- Java 集合系列之 LinkedList详细介绍(源码解析)和使用示例
- 【转】Java HashMap 源码解析(好文章)
- Java源码解析 AbstractCollection<E>
- Java Thread 源码解析
- Java 1.6 ThreadPoolExecutor源码解析
- fabirc1.0商业正式版本源码解析2——peer命令结构
- fabirc1.0商业正式版本源码解析8——peer的System Chaincode
- Java 集合系列12之 TreeMap详细介绍(源码解析)和使用示例
- Java集合源码--ArrayList的解析
- Java HashMap 源码解析
- 菜鸟谈——java集合之Collection关系图谱及源码解析
- Java FutureTask 源码解析
- 源码解析:Hibernate 事务 设计 (版本 3.1.3)
- Java Collection Framework 之 LinkedList 源码解析
- Java集合---LinkedList源码解析