hanlp源码解读之字符正规化CharTable

今日头条

1151人已加入

描述

概述:字符正规化是指在分词之前把繁体转成简体、大写转成小写等,在自然语言处理中这是必不可以的一个步骤!在hanlp中的实现方法是基于词典的,也就是正规则字符对照表。就是“data/dictionary/other/CharTable.txt” 这个词典,打开后是下面这个样子的!



«=《

「=“

」=”

『=‘

』=’

【=《

〗="

〝="

〞="

と=之

ふ=子

ル=儿

ㄖ=日

丟=丢

在java程序中如何实现呢,相信大部分人会想用到用HashMap缓存起来不就可以了吗!当然,这个方法是可行的,但是HashMap在数据量比较大时,时间复杂度是接近O(n)的。这也是为什么加载词典用trie树,而不是直接用HashMap的原因了,当然内存也是一个方面,本篇文章不会讨论!下面我们来看下hanlp代码里的具体实现。


在hanlp中,是采用一维数据实现的,下面一步步来看源码的实现!源码位于com.hankcs.hanlp.HanLP包下的CharTable类中,这个类主是要加把 CharTable.txt加载到一维数组中。为了方便阅读,下面直接在代码中加入注释!


在分词之前会首化调用正规化接口(在启用正规化的情况下)



    public List seg(char[] text)

    {

        assert text != null;

        if (HanLP.Config.Normalization)

        {

            CharTable.normalization(text);//执行正规化

        }

        return segSentence(text);

    }

下面来看下CharTable.normalization(text);这个函数的实现:这个函数极其简单,就是对text中的每个字符查询一维数据COVERT,看到这里应该就能明白,正规化最重要的就是加载txt文件到CONVERT数组中

    public static void normalization(char[] charArray)

    {

        assert charArray != null;

        for (int i = 0; i < charArray.length; i++)

        {

            charArray[i] = CONVERT[charArray[i]];

        }

    }



下面看具本的代码,敝人在代码中都加入了注释,此处不再另行讲解

/**

 * 字符正规化表

 * @author hankcs

 */

public class CharTable

{

    /**

     * 正规化使用的对应表

     * 存储原理是CONVERT[line.charAt(0)] = CONVERT[line.charAt(2)];

     * line.charAt(0)是词典中的源始字符(如①),line.charAt(2)是正规化后的字符(如一)

     * ①=一

* ②=二

* ④=四

* ⑤=伍

* 这样以来在正规化时直接 charArray[i] = CONVERT[charArray[i]];就可以了,时间复杂度是O(1)

     */

    public static char[] CONVERT;

 

    static

    {

        long start = System.currentTimeMillis();

        if (!load(HanLP.Config.CharTablePath))//通过static语句块加载词典,hanlp中所有的词典都是这种方法加载的

        {

            logger.severe("字符正规化表加载失败");

            System.exit(-1);

        }

        logger.info("字符正规化表加载成功:" + (System.currentTimeMillis() - start) + " ms");

    }

 

    /**

     * 首先偿试加载CharTable.txt.bin序列化词典,首次编译好词典会序列化到CharTable.txt.bin中

     * 如果CharTable.txt.bin不存在,则加载CharTable.txt文件

     * 对于这个词典来说加载CharTable.txt.bin和CharTable.txt在效率上基本上是没有区别的,因为不存在编译的过程

     * 便CoreNatureDictionary.txt这类词典因为要编译成trie树,是需要一定时间的

     * @param path

     * @return

     */

    private static boolean load(String path)

    {

        String binPath = path + Predefine.BIN_EXT;

        if (loadBin(binPath)) return true;//二进制的词典存在直接读入到CONVERT数组中即可

        CONVERT = new char[Character.MAX_VALUE + 1];

        for (int i = 0; i < CONVERT.length; i++)//这个循环用来初始化数组,避免在使用时出现null的情况

        {

            CONVERT[i] = (char) i;

        }

        IOUtil.LineIterator iterator = new IOUtil.LineIterator(path);//读入txt对照表

        while (iterator.hasNext())

        {

            String line = iterator.next();

            if (line == null) return false;

            if (line.length() != 3) continue;

            CONVERT[line.charAt(0)] = CONVERT[line.charAt(2)];//这个其实就是正规化时的对照表,虽然简单的一条语句就实现了, 但是这种思考问题的方式和编码风格还是非常值和得学习的

        }

        logger.info("正在缓存字符正规化表到" + binPath);

        IOUtil.saveObjectTo(CONVERT, binPath);

 

        return true;

    }

 

    /**

     * 这个函数主要用来加载.bin对照表到CONVERT数组中

     * @param path

     * @return

     */

    private static boolean loadBin(String path)

    {

        try

        {

            ObjectInputStream in = new ObjectInputStream(IOUtil.newInputStream(path));

            CONVERT = (char[]) in.readObject();

            in.close();

        }

        catch (Exception e)

        {

            logger.warning("字符正规化表缓存加载失败,原因如下:" + e);

            return false;

        }

 

        return true;

    }

 

    /**

     * 将一个字符正规化

     * @param c 字符

     * @return 正规化后的字符

     */

    public static char convert(char c)

    {

        return CONVERT[c];

    }

 

    public static char[] convert(char[] charArray)

    {

        char[] result = new char[charArray.length];

        for (int i = 0; i < charArray.length; i++)

        {

            result[i] = CONVERT[charArray[i]];

        }

 

        return result;

    }

 

    public static String convert(String charArray)

    {

        assert charArray != null;

        char[] result = new char[charArray.length()];

        for (int i = 0; i < charArray.length(); i++)

        {

            result[i] = CONVERT[charArray.charAt(i)];

        }

 

        return new String(result);

    }

 

    /**

     * 正规化一些字符(原地正规化)

     * @param charArray 字符

     */

    public static void normalization(char[] charArray)

    {

        assert charArray != null;

        for (int i = 0; i < charArray.length; i++)

        {

            charArray[i] = CONVERT[charArray[i]];

        }

    }

}





文章来源于亚当-adam的博客

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 相关推荐

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分