一、hanlp本地词典加载源码分析

hanlp在调用提供的函数处理文本时会先初始化本地词典,加载词典进入内存中

以中文分词接口为例子

1.调用分词函数入口

   public class DemoAtFirstSight
{public static void main(String[] args){System.out.println("首次编译运行时,HanLP会自动构建词典缓存,请稍候……");
//        HanLP.Config.enableDebug();         // 为了避免你等得无聊,开启调试模式说点什么:-)
//调用分词函数
System.out.println(HanLP.segment("我也不知道你好一个心眼儿啊,一半天欢迎使用HanLP汉语处理包!" +"接下来请从其他Demo中体验HanLP丰富的功能~"));}
}/**跟下去* 分词** @param text 文本* @return 切分后的单词*/public static List<Term> segment(String text){return StandardTokenizer.segment(text.toCharArray());}/**继续走* 分词* @param text 文本* @return 分词结果*/public static List<Term> segment(char[] text){return SEGMENT.seg(text);}/**还要跟进* 分词** @param text 待分词文本* @return 单词列表*/public List<Term> seg(char[] text){assert text != null;//是否执行字符串正规化,例如繁体转换成简体,当然这不是主要的if (HanLP.Config.Normalization){CharTable.normalization(text);}return segSentence(text);}/**继续前进* 给一个句子分词,可以看出这是一个抽象类,具体有子类来实现** @param sentence 待分词句子* @return 单词列表*/protected abstract List<Term> segSentence(char[] sentence);

2.具体功能由ViterbiSegment子类来实现

3.分词器

/*** Viterbi分词器<br>* 也是最短路分词,最短路求解采用Viterbi算法** @author hankcs*/
public class ViterbiSegment extends WordBasedSegment
{@Overrideprotected List<Term> segSentence(char[] sentence){//        long start = System.currentTimeMillis();//1.进入加载核心词典/*** 核心词典路径*///  public static String CoreDictionaryPath = "data/dictionary/CoreNatureDictionary.txt";WordNet wordNetAll = new WordNet(sentence);生成词网generateWordNet(wordNetAll);///生成词图
//        System.out.println("构图:" + (System.currentTimeMillis() - start));if (HanLP.Config.DEBUG){System.out.printf("粗分词网:\n%s\n", wordNetAll);}
//        start = System.currentTimeMillis();List<Vertex> vertexList = viterbi(wordNetAll);
//        System.out.println("最短路:" + (System.currentTimeMillis() - start));//2.这里加载的是自定义词典if (config.useCustomDictionary){if (config.indexMode > 0)combineByCustomDictionary(vertexList, wordNetAll);else combineByCustomDictionary(vertexList);}if (HanLP.Config.DEBUG){System.out.println("粗分结果" + convert(vertexList, false));}// 数字识别if (config.numberQuantifierRecognize){mergeNumberQuantifier(vertexList, wordNetAll, config);}// 实体命名识别if (config.ner){WordNet wordNetOptimum = new WordNet(sentence, vertexList);int preSize = wordNetOptimum.size();if (config.nameRecognize){PersonRecognition.recognition(vertexList, wordNetOptimum, wordNetAll);}if (config.translatedNameRecognize){TranslatedPersonRecognition.recognition(vertexList, wordNetOptimum, wordNetAll);}if (config.japaneseNameRecognize){JapanesePersonRecognition.recognition(vertexList, wordNetOptimum, wordNetAll);}if (config.placeRecognize){PlaceRecognition.recognition(vertexList, wordNetOptimum, wordNetAll);}if (config.organizationRecognize){// 层叠隐马模型——生成输出作为下一级隐马输入wordNetOptimum.clean();vertexList = viterbi(wordNetOptimum);wordNetOptimum.clear();wordNetOptimum.addAll(vertexList);preSize = wordNetOptimum.size();OrganizationRecognition.recognition(vertexList, wordNetOptimum, wordNetAll);}if (wordNetOptimum.size() != preSize){vertexList = viterbi(wordNetOptimum);if (HanLP.Config.DEBUG){System.out.printf("细分词网:\n%s\n", wordNetOptimum);}}}// 如果是索引模式则全切分if (config.indexMode > 0){return decorateResultForIndexMode(vertexList, wordNetAll);}// 是否标注词性if (config.speechTagging){speechTagging(vertexList);}return convert(vertexList, config.offset);}

4.核心词典加载

 //1.由此接口进入生成词网generateWordNet(wordNetAll);/*** 生成一元词网** @param wordNetStorage*/protected void generateWordNet(final WordNet wordNetStorage){final char[] charArray = wordNetStorage.charArray;// 核心词典查询,点击进入查看核心词典加载入内存中DoubleArrayTrie<CoreDictionary.Attribute>.Searcher searcher = CoreDictionary.trie.getSearcher(charArray, 0);while (searcher.next()){wordNetStorage.add(searcher.begin + 1, new Vertex(new String(charArray, searcher.begin, searcher.length), searcher.value, searcher.index));}// 强制用户词典查询,是否优先使用用户自定义词典if (config.forceCustomDictionary){CustomDictionary.parseText(charArray, new AhoCorasickDoubleArrayTrie.IHit<CoreDictionary.Attribute>(){@Overridepublic void hit(int begin, int end, CoreDictionary.Attribute value){wordNetStorage.add(begin + 1, new Vertex(new String(charArray, begin, end - begin), value));}});}// 原子分词,保证图连通LinkedList<Vertex>[] vertexes = wordNetStorage.getVertexes();for (int i = 1; i < vertexes.length; ){if (vertexes[i].isEmpty()){int j = i + 1;for (; j < vertexes.length - 1; ++j){if (!vertexes[j].isEmpty()) break;}wordNetStorage.add(i, quickAtomSegment(charArray, i - 1, j - 1));i = j;}else i += vertexes[i].getLast().realWord.length();}}//CoreDictionary 核心词典加载---先确定是否有缓存文件,有则先加载缓存文件,无则生成缓存文件同时加载进入内存中,需要注意的是用户自定义词典添加词汇后,没有新生成缓存文件则词汇无法识别,缓存文件在词典文件同一目录下 .bin结尾的文件//以下是核心词典类CoreDictionary中的一些主要属性******************
//加载词典在内存中的容器public static DoubleArrayTrie<Attribute> trie = new DoubleArrayTrie<Attribute>();//核心词典位置public final static String path = HanLP.Config.CoreDictionaryPath;public static final int totalFrequency = 221894;// 自动加载词典static{long start = System.currentTimeMillis();if (!load(path))//进入load函数{throw new IllegalArgumentException("核心词典" + path + "加载失败");}else{logger.info(path + "加载成功," + trie.size() + "个词条,耗时" + (System.currentTimeMillis() - start) + "ms");}}

5.加载词典函数

//load函数中源码中作者已有注释这里不再多写。private static boolean load(String path){logger.info("核心词典开始加载:" + path);//这个函数判断是否有缓存文件 bin 存在,有则直接加载缓存文件if (loadDat(path)) return true;TreeMap<String, CoreDictionary.Attribute> map = new TreeMap<String, Attribute>();BufferedReader br = null;try{br = new BufferedReader(new InputStreamReader(IOUtil.newInputStream(path), "UTF-8"));String line;int MAX_FREQUENCY = 0;long start = System.currentTimeMillis();while ((line = br.readLine()) != null){String param[] = line.split("\\s");int natureCount = (param.length - 1) / 2;CoreDictionary.Attribute attribute = new CoreDictionary.Attribute(natureCount);for (int i = 0; i < natureCount; ++i){attribute.nature[i] = Nature.create(param[1 + 2 * i]);attribute.frequency[i] = Integer.parseInt(param[2 + 2 * i]);attribute.totalFrequency += attribute.frequency[i];}map.put(param[0], attribute);MAX_FREQUENCY += attribute.totalFrequency;}logger.info("核心词典读入词条" + map.size() + " 全部频次" + MAX_FREQUENCY + ",耗时" + (System.currentTimeMillis() - start) + "ms");br.close();trie.build(map);logger.info("核心词典加载成功:" + trie.size() + "个词条,下面将写入缓存……");try{DataOutputStream out = new DataOutputStream(IOUtil.newOutputStream(path + Predefine.BIN_EXT));Collection<CoreDictionary.Attribute> attributeList = map.values();out.writeInt(attributeList.size());for (CoreDictionary.Attribute attribute : attributeList){out.writeInt(attribute.totalFrequency);out.writeInt(attribute.nature.length);for (int i = 0; i < attribute.nature.length; ++i){out.writeInt(attribute.nature[i].ordinal());out.writeInt(attribute.frequency[i]);}}trie.save(out);out.close();}catch (Exception e){logger.warning("保存失败" + e);return false;}}catch (FileNotFoundException e){logger.warning("核心词典" + path + "不存在!" + e);return false;}catch (IOException e){logger.warning("核心词典" + path + "读取错误!" + e);return false;}return true;}

6.加载核心词典缓存文件

    /*** 从磁盘加载双数组** @param path* @return*/static boolean loadDat(String path){try{//public final static String BIN_EXT = ".bin"; 这里可以看出加载的是缓存文件ByteArray byteArray = ByteArray.createByteArray(path + Predefine.BIN_EXT);if (byteArray == null) return false;int size = byteArray.nextInt();CoreDictionary.Attribute[] attributes = new CoreDictionary.Attribute[size];final Nature[] natureIndexArray = Nature.values();for (int i = 0; i < size; ++i){// 第一个是全部频次,第二个是词性个数int currentTotalFrequency = byteArray.nextInt();int length = byteArray.nextInt();attributes[i] = new CoreDictionary.Attribute(length);attributes[i].totalFrequency = currentTotalFrequency;for (int j = 0; j < length; ++j){attributes[i].nature[j] = natureIndexArray[byteArray.nextInt()];attributes[i].frequency[j] = byteArray.nextInt();}}if (!trie.load(byteArray, attributes) || byteArray.hasMore()) return false;}catch (Exception e){logger.warning("读取失败,问题发生在" + e);return false;}return true;}

上述过程是核心词典的加载过程,用户词典大致相同
此图可以看出bin结尾的就是词典的缓存文件。

二、自定义词汇添加

过程分析
1.添加新词需要确定无缓存文件,否则无法使用成功,因为词典会优先加载缓存文件
2.再确认缓存文件不在时,打开本地词典按照格式添加自定义词汇。
3.调用分词函数重新生成缓存文件,这时会报一个找不到缓存文件的异常,不用管,因为加载词典进入内存是会优先加载缓存,缓存不在当然会报异常,然后加载词典生成缓存文件,最后处理字符进行分词就会发现新添加的词汇可以进行分词了。

操作过程图解:

1.有缓存文件的情况下

 System.out.println(HanLP.segment("张三丰在一起我也不知道你好一个心眼儿啊,一半天欢迎使用HanLP汉语处理包!" +"接下来请从其他Demo中体验HanLP丰富的功能~"))//首次编译运行时,HanLP会自动构建词典缓存,请稍候……
//[张/q, 三丰/nz, 在/p, 一起/s, 我/rr, 也/d, 不/d, 知道/v, 你好/vl, 一个心眼儿/nz, 啊/y, ,/w, 一半天/nz, 欢迎/v, 使用/v, HanLP/nx, 汉语/gi, 处理/vn, 包/v, !/w, 接下来/vl, 请/v, 从/p, 其他/rzv, Demo/nx, 中/f, 体验/v, HanLP/nx, 丰富/a, 的/ude1, 功能/n, ~/nx]

2.打开用户词典–添加 ‘张三丰在一起’ 为一个 nz词性的新词

2.2 原始缓存文件下运行–会发现不成功,没有把 ‘张三丰在一起’ 分词一个nz词汇

 System.out.println(HanLP.segment("张三丰在一起我也不知道你好一个心眼儿啊,一半天欢迎使用HanLP汉语处理包!" +"接下来请从其他Demo中体验HanLP丰富的功能~"))//首次编译运行时,HanLP会自动构建词典缓存,请稍候……
//[张/q, 三丰/nz, 在/p, 一起/s, 我/rr, 也/d, 不/d, 知道/v, 你好/vl, 一个心眼儿/nz, 啊/y, ,/w, 一半天/nz, 欢迎/v, 使用/v, HanLP/nx, 汉语/gi, 处理/vn, 包/v, !/w, 接下来/vl, 请/v, 从/p, 其他/rzv, Demo/nx, 中/f, 体验/v, HanLP/nx, 丰富/a, 的/ude1, 功能/n, ~/nx]

3.1 删除缓存文件 bin

3.2 再次运行程序,此时会报错—无法找到缓存文件

 System.out.println(HanLP.segment("张三丰在一起我也不知道你好一个心眼儿啊,一半天欢迎使用HanLP汉语处理包!" +"接下来请从其他Demo中体验HanLP丰富的功能~"));/**首次编译运行时,HanLP会自动构建词典缓存,请稍候……
十月 19, 2018 6:12:49 下午 com.hankcs.hanlp.corpus.io.IOUtil readBytes
WARNING: 读取D:/datacjy/hanlp/data/dictionary/custom/CustomDictionary.txt.bin时发生异常java.io.FileNotFoundException: D:\datacjy\hanlp\data\dictionary\custom\CustomDictionary.txt.bin (系统找不到指定的文件。)   找不到缓存文件[张三丰在一起/nz, 我/rr, 也/d, 不/d, 知道/v, 你好/vl, 一个心眼儿/nz, 啊/y, ,/w, 一半天/nz, 欢迎/v, 使用/v, HanLP/nx, 汉语/gi, 处理/vn, 包/v, !/w, 接下来/vl, 请/v, 从/p, 其他/rzv, Demo/nx, 中/f, 体验/v, HanLP/nx, 丰富/a, 的/ude1, 功能/n, ~/nx]*/

hanlp中文语言处理--词典加载源码过程分析及自定义用户词汇添加相关推荐

  1. 插件式换肤框架搭建 - 资源加载源码分析

    资源加载源码分析 1.首先我们来看一下ImageView是如何加载资源的: public ImageView(Context context, @Nullable AttributeSet attrs ...

  2. 【easyui】treegrid逐级加载源码

    当初看这源码的目的是: 1.treegrid是怎么实现逐级加载树结构的. 解: 见demo,主要就是点击节点的时候会请求后台. 2.treegrid加载后,第二次展开节点会不会再次请求后台. 解:第二 ...

  3. Cocos Creator2.4.8 资源加载源码阅读

    最近用到资源加载的问题:加载的资源进度条倒退问题,现在只是用临时的解决方案 - 加载进度如果出现会倒退的问题还是用原来的数值.抽时间看了下cocos creator 资源加载的源码,整理了一下脑图 一 ...

  4. Kettle — Spoon加载源码解析

    在Kettle中,我们知道Spoon是其中最重要的一个组件.它可以让我们以图形化的方式开发转换和作业等工作. 在spoon中Kettle采用了Xul界面技术和Swt相结合的方式进行图形界面的开发. 启 ...

  5. Android UI滑动加载源码

    2019独角兽企业重金招聘Python工程师标准>>> android UI 往右滑动,滑动到最后一页就自动加载数据并显示 如图: Java代码 package cn.anycall ...

  6. springboot环境变量(environment)加载源码分析

    代码入口 //SpringApplication run 环境变量初始化入口 prepareEnvironment(listeners, bootstrapContext, applicationAr ...

  7. spring boot实战(第十篇)Spring boot Bean加载源码分析

    前言 前面的文章描述了Application对应Bean的创建,本篇将阐述spring boot中bean的创建过程 refresh 首先来看SpringApplication#run方法中refre ...

  8. NLP:词典加载及切分算法

    文章目录 一.词典加载 二.切分算法 1.简述 2.完全切分 3.正向最长匹配 4.逆向最长匹配 5.双向最长匹配 一.词典加载 词典分词时最简单最常用的分词算法. 使用词典分词我们需要一部词典和一套 ...

  9. nuget.org 无法加载源 https://api.nuget.org/v3/index.json 的服务索引

    今天添加新项目想添加几个工具包,打开NuGet就这样了  发生错误如下: [nuget.org] 无法加载源 https://api.nuget.org/v3/index.json 的服务索引. 响应 ...

最新文章

  1. 微软极品Sysinternals Suite工具包使用指南
  2. Java Web整合开发读书笔记
  3. 亲试虚拟机为REDHAT5装VM-tool
  4. AngularJS 学习笔记 - $http.post 跟后台交互
  5. 史上最简单的 SpringCloud 教程 | 第一篇: 服务的注册与发现(Eureka)
  6. php扩展 waf,基于PHP扩展的WAF实现
  7. 11月4日,上海开源基础设施峰会,不见不散!
  8. 聚数引智,承德大数据产业对接交流会将于2019中国国际数字经济博览会期间召开...
  9. 交换机命令行配置与VLAN
  10. 使用Speech SDK 5.1文字转音频
  11. 还在为保研和研究生毕业发愁吗?呐,给你推荐最近的几个保底的会议~
  12. java 打印字母塔_打印字母塔
  13. 485通讯( 详解 )
  14. -exec rm 与 xargs rm -rf 深度剖析
  15. python调用pyd_使用python pyd时出错
  16. 人人都道RAZ好,我读了400多本之后,才明白哪里好
  17. 电话号码的字母组合(Java)
  18. C#删除文件和文件夹到回收站
  19. windows快速生成ssh key
  20. 初学者如何学好编程?

热门文章

  1. 提取住房公积金有什么影响
  2. python matplotlib自定义colorbar颜色条-以及matplotlib中的内置色条
  3. 借名买房规避限购政策的,合同应认定为无效
  4. switch case 穿透
  5. 2020.1.13 C语言学习 结构体+结构体数组+结构体指针
  6. 基于STM32F030、MAX30102血氧心率监测仪的设计(一)
  7. 前端开发中常用的英语单词短语总结
  8. LeetCode第172场周赛:5322. 工作计划的最低难度(动态规划)
  9. 关于 node 环境升级到 v8^ 以上,node-sass 报错的解决方法
  10. `Computer-Algorithm` 二分图BipartiteGraph,最大匹配,最小点覆盖,最大独立集