在使用hanlp的过程中可能会遇到需要重新加载词典的方法,
在HanLP的Git Issue中有相关问题的解答
如何在程序中重新加载自定义的词典
hanlp 能否加入一个远程词典更新的功能
在hanlp 能否加入一个远程词典更新的功能中提到了一个接口 自定义词典支持热更新
CustomDictionary.java提供了一个reload方法,
/**
* 热更新(重新加载)<br>
* 集群环境(或其他IOAdapter)需要自行删除缓存文件(路径 = HanLP.Config.CustomDictionaryPath[0] + Predefine.BIN_EXT)
* @return 是否加载成功
*/
public static boolean reload()
{
String path[] = HanLP.Config.CustomDictionaryPath; // Tips : 读取HanLP的配置文件中CustomDIctionaryPath
if (path == null || path.length == 0) return false;
new File(path[0] + Predefine.BIN_EXT).delete(); // 删掉缓存
return loadMainDictionary(path[0]);
}
其中调用了loadMainDictionary方法.
private static boolean loadMainDictionary(String mainPath)
{
logger.info("自定义词典开始加载:" + mainPath);
if (loadDat(mainPath)) return true;
dat = new DoubleArrayTrie<CoreDictionary.Attribute>();
TreeMap<String, CoreDictionary.Attribute> map = new TreeMap<String, CoreDictionary.Attribute>();
LinkedHashSet<Nature> customNatureCollector = new LinkedHashSet<Nature>();
try
{
String path[] = HanLP.Config.CustomDictionaryPath;
for (String p : path)
{
Nature defaultNature = Nature.n;
int cut = p.indexOf(' ');
if (cut > 0)
{
// 有默认词性
String nature = p.substring(cut + 1);
p = p.substring(0, cut);
try
{
defaultNature = LexiconUtility.convertStringToNature(nature, customNatureCollector);
}
catch (Exception e)
{
logger.severe("配置文件【" + p + "】写错了!" + e);
continue;
}
}
logger.info("以默认词性[" + defaultNature + "]加载自定义词典" + p + "中……");
boolean success = load(p, defaultNature, map, customNatureCollector);
if (!success) logger.warning("失败:" + p);
}
if (map.size() == 0)
{
logger.warning("没有加载到任何词条");
map.put(Predefine.TAG_OTHER, null); // 当作空白占位符
}
logger.info("正在构建DoubleArrayTrie……");
dat.build(map);
// 缓存成dat文件,下次加载会快很多
logger.info("正在缓存词典为dat文件……");
// 缓存值文件
List<CoreDictionary.Attribute> attributeList = new LinkedList<CoreDictionary.Attribute>();
for (Map.Entry<String, CoreDictionary.Attribute> entry : map.entrySet())
{
attributeList.add(entry.getValue());
}
DataOutputStream out = new DataOutputStream(IOUtil.newOutputStream(mainPath + Predefine.BIN_EXT));
// 缓存用户词性
IOUtil.writeCustomNature(out, customNatureCollector);
// 缓存正文
out.writeInt(attributeList.size());
for (CoreDictionary.Attribute attribute : attributeList)
{
attribute.save(out);
}
dat.save(out);
out.close();
}
catch (FileNotFoundException e)
{
logger.severe("自定义词典" + mainPath + "不存在!" + e);
return false;
}
catch (IOException e)
{
logger.severe("自定义词典" + mainPath + "读取错误!" + e);
return false;
}
catch (Exception e)
{
logger.warning("自定义词典" + mainPath + "缓存失败!\n" + TextUtility.exceptionToString(e));
}
return true;
}
其中loadDat
/**
* 从磁盘加载双数组
*
* @param path
* @return
*/
static boolean loadDat(String path)
{
try
{
ByteArray byteArray = ByteArray.createByteArray(path + Predefine.BIN_EXT);
if (byteArray == null) return false;
int size = byteArray.nextInt();
if (size < 0) // 一种兼容措施,当size小于零表示文件头部储存了-size个用户词性
{
while (++size <= 0)
{
Nature.create(byteArray.nextString());
}
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 (!dat.load(byteArray, attributes)) return false;
}
catch (Exception e)
{
logger.warning("读取失败,问题发生在" + TextUtility.exceptionToString(e));
return false;
}
return true;
}
可以看出 reload方法必须先删除bin文件, 再重新生成.
在Python中可以按照下面的方法进行重载
custom_dictionary = JClass('com.hankcs.hanlp.dictionary.CustomDictionary')
custom_dictionary.reload()
调用CustomDictionary类的reload方法可以重新按照properties中的设置重构一个bin文件.
同时也提供了直接添加自定义字典的方法, 但是看起来并不是很合适, 因为这样会
HanLP.Config. CustomDictionaryPath = ["dict.txt","dict2.txt"]
CustomDictionary.reload()
pyhanlp重加载词典中可能会遇到的问题
在进行重加载过程中发现一个可能存在的问题, 已经提交 reload生成自定义词典bin文件在重新载入时抛出异常ArrayIndexOutOfBoundsException
该问题主要表现为reload生成的bin文件在下次启动时不能被正常识别, 会抛出ArrayIndexOutOfBoundsException的问题.
原因是因为reload过程中, 静态类已经及存储了在这一次启动过程中识别过的自定义词性, 但是reload中没有写入这些词性, 导致在第二次启动过程中, 会发生越界.