中文分词——知更鸟分词(RS)设计与实现

  本文设计了一种带逆向回退策略的正向最大匹配。

分词概述

  英文文本的处理相对简单,每一个单词之间有空格或标点符号隔开。如果不考虑短语,仅以单词作为唯一的语义单元的话,处理英文单词切分相对简单,只需要分类多有单词,去除标点符号。中文自然语言处理首先要解决的难题就是中文分词技术。
  中文分词(Chinese Word Segmentation) 指的是将一个汉字序列切分成一个个单独的词。分词就是将连续的字序列按照一定的规范重新组合成词序列的过程。我们知道,

算法分类

  现有的分词算法可分为三大类:基于字符串匹配的分词方法、基于理解的分词方法和基于统计的分词方法。按照是否与词性标注过程相结合,又可以分为单纯分词方法和分词与标注相结合的一体化方法。
字符匹配
  这种方法又叫做机械分词方法,它是按照一定的策略将待分析的汉字串与一个“充分大的”机器词典中的词条进行配,若在词典中找到某个字符串,则匹配成功(识别出一个词)。按照扫描方向的不同,串匹配分词方法可以分为正向匹配和逆向匹配;按照不同长度优先匹配的情况,可以分为最大(最长)匹配和最小(最短)匹配。
常用的几种机械分词方法如下:
  1)正向最大匹配法(由左到右的方向);
  2)逆向最大匹配法(由右到左的方向);
  3)最少切分(使每一句中切出的词数最小);
  4)双向最大匹配法(进行由左到右、由右到左两次扫描)
理解法
  这种分词方法是通过让计算机模拟人对句子的理解,达到识别词的效果。其基本思想就是在分词的同时进行句法、语义分析,利用句法信息和语义信息来处理歧义现象。它通常包括三个部分:分词子系统、句法语义子系统、总控部分。在总控部分的协调下,分词子系统可以获得有关词、句子等的句法和语义信息来对分词歧义进行判断,即它模拟了人对句子的理解过程。这种分词方法需要使用大量的语言知识和信息。由于汉语语言知识的笼统、复杂性,难以将各种语言信息组织成机器可直接读取的形式,因此目前基于理解的分词系统还处在试验阶段。
统计法
  从形式上看,词是稳定的字的组合,因此在上下文中,相邻的字同时出现的次数越多,就越有可能构成一个词。因此字与字相邻共现的频率或概率能够较好的反映成词的可信度。可以对语料中相邻共现的各个字的组合的频度进行统计,计算它们的互现信息。定义两个字的互现信息,计算两个汉字X、Y的相邻共现概率。互现信息体现了汉字之间结合关系的紧密程度。当紧密程度高于某一个阈值时,便可认为此字组可能构成了一个词。这种方法只需对语料中的字组频度进行统计,不需要切分词典,因而又叫做无词典分词法或统计取词方法。但这种方法也有一定的局限性,会经常抽出一些共现频度高、但并不是词的常用字组,例如“这一”、“之一”、“有的”、“我的”、“许多的”等,并且对常用词的识别精度差,时空开销大。实际应用的统计分词系统都要使用一部基本的分词词典(常用词词典)进行串匹配分词,同时使用统计方法识别一些新的词,即将串频统计和串匹配结合起来,既发挥匹配分词切分速度快、效率高的特点,又利用了无词典分词结合上下文识别生词、自动消除歧义的优点。

常见项目

极易分词
  MMAnalyzer极易中文分词组件,由中科院提供的中文极易分词器。比较完善的中文分词器。
  支持英文、数字、中文(简体)混合分词。
  常用的数量和人名的匹配。
  超过22万词的词库整理。
  实现正向最大匹配算法。

IK
  IKAnalyzer是一个开源的,基于java语言开发的轻量级的中文分词工具包。从2006年12月推出1.0版开始,IKAnalyzer已经推出了3个大版本。最初,它是以开源项目Luence为应用主体的,结合词典分词和文法分析算法的中文分词组件。新版本的IKAnalyzer3.0则发展为面向Java的公用分词组件,独立于Lucene项目,同时提供了对Lucene的默认优化实现。
Paoding
  Paoding(庖丁解牛分词)基于Java的开源中文分词组件,提供lucene和solr 接口,具有极 高效率和 高扩展性。引入隐喻,采用完全的面向对象设计,构思先进。
  高效率:在PIII 1G内存个人机器上,1秒可准确分词 100万汉字。
  采用基于不限制个数的词典文件对文章进行有效切分,使能够将对词汇分类定义。
  能够对未知的词汇进行合理解析。
  仅支持Java语言。
MMSEG4J
  MMSEG4J基于Java的开源中文分词组件,提供lucene和solr 接口:
  1.mmseg4j 用 Chih-Hao Tsai 的 MMSeg 算法实现的中文分词器,并实现 lucene 的 analyzer 和 solr 的TokenizerFactory 以方便在Lucene和Solr中使用。
  2.MMSeg 算法有两种分词方法:Simple和Complex,都是基于正向最大匹配。Complex 加了四个规则过虑。官方说:词语的正确识别率达到了 98.41%。mmseg4j 已经实现了这两种分词算法。
smallseg
  开源的,基于DFA的轻量级的中文分词工具包。
  特点:可自定义词典、切割后返回登录词列表和未登录词列表、有一定的新词识别能力。

知更鸟分词实现

  中文分词既然作为自然语言处理的基本工具,那么我们就来实现一个基于字符匹配的中文分词,用笔者的英文名(Robin)给她起个名字,就叫做——知更鸟分词器(RS/Robin-Segmenter)

算法描述

  基本实现算法:
  (1)正向最大匹配;
  (2)逆向最大匹配;
  (3)带逆向回退策略的正向最大匹配(重点)。
  支持以下功能:

  • 新词发现、新词标注;
  • 字符转换、格式统一;
  • 自定义分割符;
  • 特殊符号保留;
  • 检索分词模式;

数据结构

  基于字典字符匹配匹配的分词算法,重点是字典数据结构的设计。好的字典数据结构能够极大提升匹配检索的效率。
  采用基本容器HashMap实现的一种变种字典树(Trie)结构。其基本的思想如图。

Trie树型结构之正向最大匹配过程

  假设词典:中国 | 中南海 | 人民 | 人民币 | 人情 | 银行 | 中国人民 | 中国人民银行 | 中国人寿保险 | ……
  例如:“中国人民银行行长易纲。”这样一条文本的匹配过程如图示红线路径。分词结果应该示“中国人民银行 行长 易纲”。
  由于中文词首字分布比较均匀,并且查询首字的概率远大于其他非首字,根节点采用map数组方式,数组下标为字符的Unicode码减去最小中文汉字 Unicode码。数组维度为汉字的Unicode码范围。

代码实现

Unicode工具类:

package com.robin.segment.robinseg;

import com.robin.config.ConfigUtil;
import com.robin.file.FileUtil;
import java.util.HashSet;
import java.util.Set;

/**
 * <DT><B>描述:</B></DT>
 * <DD>Unicode 编码工具类</DD>
 *
 * @version Version1.0
 * @author Robin
 * @version <I> V1.0 Date:2018-01-28</I>
 * @author  <I> E-mail:[email protected]</I>
 */
public class Unicode {

    /** 量词配置路径 */
    private static final String QUANTIFIER_PATH;
    /** 量词集合 */
    private static Set<Character> quantifierSet;
    /** 中文数词路径 */
    private static final String NUMERAL_PATH;
    /** 中文数词集合 */
    private static Set<Character> numeralSet;
    /** 词典文件编码 */
    private static final String DIC_ENCODING;
    /** 空字符 Unicode */
    public static final int NULL = 0x0;
    /** 半角空格 Unicode */
    public static final int DBC_BLANK = 0x20;
    /** 半角句点 Unicode */
    public static final int DBC_POINT = 0x2E;
    /** 半角0 Unicode */
    public static final int DBC_0 = 0x30;
    /** 半角9 Unicode */
    public static final int DBC_9 = 0x39;
    /** 半角A Unicode */
    public static final int DBC_A = 0x41;
    /** 半角Z Unicode */
    public static final int DBC_Z = 0x5A;
    /** 半角a Unicode */
    public static final int DBC_LA = 0x61;
    /** 半角z Unicode */
    public static final int DBC_LZ = 0x7A;
    /** 普通最小中文汉字 Unicode */
    public static final int MIN_CHINESE = 0x4E00;
    /** 普通最大中文汉字 Unicode */
    public static final int MAX_CHINESE = 0x9FA5;
    /** 全角空格 Unicode */
    public static final int SBC_BLANK = 0x3000;//0xFF00也是全角空格
    /** 关注全角下限 Unicode */
    public static final int SBC_LOW_LIMIT = 0xFF00;
    /** 关注全角上限 Unicode */
    public static final int SBC_UP_LIMIT = 0xFF5E;
    /** 全角句点 Unicode */
    public static final int SBC_POINT = 0xFF0E;
    /** 全角0 Unicode */
    public static final int SBC_0 = 0xFF10;
    /** 全角9 Unicode */
    public static final int SBC_9 = 0xFF19;
    /** 全角A Unicode */
    public static final int SBC_A = 0xFF21;
    /** 全角Z Unicode */
    public static final int SBC_Z = 0xFF3A;
    /** 全角a Unicode */
    public static final int SBC_LA = 0xFF41;
    /** 全角z Unicode */
    public static final int SBC_LZ = 0xFF5A;
    /** 半角全角数字或字母 Unicode 编码偏移 */
    public static final int OFFSET_DBC_SBC = 0xFEE0;
    /** 英文大小写字母 Unicode 编码偏移 */
    public static final int OFFSET_UP_LOW = 0x20;

    static {
        QUANTIFIER_PATH = ConfigUtil.getConfig("dic.quantifier");
        NUMERAL_PATH = ConfigUtil.getConfig("dic.numeral");
        DIC_ENCODING = ConfigUtil.getConfig("dic.encoding");
        initQantifierSet();
        initNumeralSet();
    }

    /**
     * Unicode 字符类型
     */
    public enum CharType {
        /** 控制符 */
        CONTROL,
        /** 半角数字 */
        DBC_DIGIT,
        /** 半角大写字母 */
        DBC_UPPER_CASE,
        /** 半角小写字母 */
        DBC_LOWER_CASE,
        /** 中文 */
        COMMON_CHINESE,
        /** 全角字符 */
        SBC_CHAR,
        /** 全角字母 */
        SBC_CASE,
        /** 全角数字 */
        SBC_DIGIT,
        /** 全角大写字母 */
        SBC_UPPER_CASE,
        /** 全角小写字母 */
        SBC_LOWER_CASE,
        /** 小数点 */
        DECIMAL_POINT,
        /** 数字后缀 */
        DECIMAL_SUFFIX,
        /** 百分比符号 */
        PERCENT_CHAR,
        /** 空白符 */
        BLANK_CHAR,
        /** 其他 */
        OTHER_CHAR,
    }

    /**
     * 通过字符判断其类型
     *
     * @param ch Unicode字符
     * @return 字符类型
     */
    public static CharType getCharType(char ch) {
        int value = ch;
        //Unicode 普通中文汉字区
        if ((value >= MIN_CHINESE) && (value <= MAX_CHINESE)) {
            return CharType.COMMON_CHINESE;
        }
        //空白符、控制字符、半角空格0x20、全角空格0x3000
        if ((value <= DBC_BLANK) || (value == SBC_BLANK)) {
            return CharType.BLANK_CHAR;
        }
        //半角数字0-9:0x30-0x39
        if ((value >= DBC_0) && (value <= DBC_9)) {
            return CharType.DBC_DIGIT;
        }
        //半角字母a-z:0x61-0x7A
        if ((value >= DBC_LA) && (value <= DBC_LZ)) {
            return CharType.DBC_LOWER_CASE;
        }
        //半角字母A-Z:0x41-0x5A
        if ((value >= DBC_A) && (value <= DBC_Z)) {
            return CharType.DBC_UPPER_CASE;
        }
        //小数点:半角0x2E
        if (value == DBC_POINT) {
            return CharType.DECIMAL_POINT;
        }
        //数字后缀
        if ((value == (int) ('%'))
                || (value == (int) ('$'))
                || (value == (int) ('¥'))) {
            return CharType.DECIMAL_SUFFIX;
        }
        //关注的全角区:0xFF00-0xFF5E  与半角相距FEE0
        if ((value >= SBC_LOW_LIMIT) && (value <= SBC_UP_LIMIT)) {
            return CharType.SBC_CHAR;
        }
        //说明:全角其他子区可以不判断了,为扩展保留
        //全角数字0-9:0xFF10-0xFF19  与半角相距FEE0
        if ((value >= SBC_0) && (value <= SBC_9)) {
            return CharType.SBC_CHAR;
        }
        //全角字母A-Z:0xFF21-0xFF3A  与半角相距FEE0
        if ((value >= SBC_A) && (value <= SBC_Z)) {
            return CharType.SBC_CHAR;
        }
        //全角字母a-z:0xFF41-0xFF5A  与半角相距FEE0
        if ((value >= SBC_LA) && (value <= SBC_LZ)) {
            return CharType.SBC_CHAR;
        }
        //其他字符
        return CharType.OTHER_CHAR;
    }

    /**
     * 判断字符否是数字类型字符。
     *
     * @param ch 字符
     * @return boolean true-数字类型,fasle-非数字类型
     */
    static boolean isDecimalType(char ch) {

        if (Unicode.isDecimal(ch)) {
            return true;
        }

        if (Unicode.isPoint(ch)) {
            return true;
        }
        return Unicode.isDecimalSuffix(ch);
    }

    /**
     * 判断字符否是数字。
     *
     * @param ch 字符
     * @return boolean true-数字,fasle-非数字
     */
    static boolean isDecimal(char ch) {
        int unicode = ch;
        //半角或全角数字
        return ((unicode >= DBC_0) && (unicode <= DBC_9))
                || (unicode >= SBC_0) && (unicode <= SBC_9);
    }

    /**
     * 判断字符是否是数字后缀。
     *
     * @param ch 字符
     * @return boolean true-数字后缀,false-非数字后缀
     */
    static boolean isDecimalSuffix(char ch) {
        int unicode = ch;
        //数字后缀
        return (unicode == (int) ('%'))
                || (unicode == (int) ('$'))
                || (unicode == (int) ('¥'));
    }

    /**
     * 判断字符是否是小数点。
     *
     * @param ch 字符
     * @return boolean true-小数点,fasle-非小数点
     */
    static boolean isPoint(char ch) {
        int unicode = ch;
        //半角或全角数字
        return (unicode == DBC_POINT) || (unicode == SBC_POINT);
    }

    /**
     * 初始化中文数字集合
     */
    private static void initNumeralSet() {
        numeralSet = new HashSet<>();
        String numerals = FileUtil.readText(NUMERAL_PATH, DIC_ENCODING);
        char[] symbolArr = numerals.toCharArray();
        for (int i = 0; i < symbolArr.length; i++) {
            numeralSet.add(symbolArr[i]);
        }
    }

    /**
     * 判断一个字符是否是中文数字
     *
     * @param ch 输入字符
     * @return 是否是中文数字
     */
    static boolean isNumeral(char ch) {
        int unicode = ch;
        if ((unicode < Unicode.MIN_CHINESE) || (unicode > Unicode.MAX_CHINESE)) {
            return false;
        }
        return numeralSet.contains(ch);
    }

    /**
     * 初始化单量词集合
     */
    private static void initQantifierSet() {
        quantifierSet = new HashSet<>();
        String quantifiers = FileUtil.readText(QUANTIFIER_PATH, DIC_ENCODING);
        char[] symbolArr = quantifiers.toCharArray();
        for (int i = 0; i < symbolArr.length; i++) {
            quantifierSet.add(symbolArr[i]);
        }
    }

    /**
     * 判断一个字符是否是中文量词
     *
     * @param ch 输入字符
     * @return 是否是量词
     */
    static boolean isQuantifier(char ch) {
        int unicode = ch;
        if ((unicode < Unicode.MIN_CHINESE) || (unicode > Unicode.MAX_CHINESE)) {
            return false;
        }
        return quantifierSet.contains(ch);
    }
}

分词器参数配置:

package com.robin.segment.robinseg;

/**
 * <DT><B>描述:</B></DT>
 * <DD>Robin分词器参数配置类</DD>
 *
 * @version 1.0
 * @author Robin
 * @version <I> V1.0 Date:2018-01-30</I>
 * @author  <I> E-mail:[email protected]</I>
 */
public class SegmentArgs {

    /** 词结束符 */
    static final char END_MARK = Unicode.NULL;
    /** 分词方法 */
    SegAlgorithm segMethod = SegAlgorithm.FORWARD;
    /** 符号标志 */
    boolean cleanSymbolFlag = true;
    /** 新词标注标志 */
    boolean markNewWordFlag = false;
    /** 大小写转换 */
    boolean downCasingFlag = true;
    /** 分词合并模式-字典中未出现的孤立子合并 */
    boolean mergePatternFlag = true;
    /** 分词检索模式 */
    boolean retrievalPatternFlag = false;
    /** 分隔符 */
    String separator = " ";
    /** 词标符号 */
    private static final String WORD_MARK = "⊙";
    /** 新词标记 */
    static final String NEW_WORD_MARK = "[" + WORD_MARK + "新词]";
    /** 拼接词标记 */
    static final String SPLICE_WORD_MARK = "[" + WORD_MARK + "拼接词]";
    /** 混合词标记 */
    static final String CN_EN_MIX_MARK = "[" + WORD_MARK + "混合词]";

    /**
     * Robin分词方法枚举类型
     */
    public enum SegAlgorithm {

        /** 带回退策略的正向最大匹配 */
        FORWARD,
        /** 简单反向最大匹配 */
        REVERSE
    }

    /**
     * 词的类别
     */
    enum WordClass {

        STANDARD,
        /** 英文词,自动识别 */
        ENGLISH,
        /** 数量词,包括自动识别的中文数量词 */
        QUANTIFIER,
        /** 新词,识别的含有中文字符的新词 */
        NEW_WORD,
        /** 中英文混合词 */
        CN_EN_MIX,
        /** 拼接新词,标准词与孤立字符拼接出来的新词 */
        SPLICE_WORD,
        /** 孤立中文字符 */
        ISOLATED_CN,
        /** 其他,上述之外 */
        OTHER
    }

    /**
     * 构造方法
     */
    public SegmentArgs() {
    }

    /**
     * 重置默认配置参数
     */
    public void resetDefaultArgs() {
        this.segMethod = SegAlgorithm.FORWARD;
        this.cleanSymbolFlag = true;
        this.markNewWordFlag = false;
        this.downCasingFlag = true;
        this.mergePatternFlag = true;
        this.retrievalPatternFlag = false;
    }

    /**
     * 设置分词分隔符
     *
     * @param separator 分隔符
     */
    void setSeparator(String separator) {
        this.separator = separator;
    }

    /**
     * 设置删除符号标志
     *
     * @param flag 是否删除
     */
    public void setCleanSymbolFlag(boolean flag) {
        this.cleanSymbolFlag = flag;
    }

    /**
     * 设置是否检索型分词
     *
     * @param flag 是否检索型分词
     */
    public void setRetrievalPatternFlag(boolean flag) {
        this.retrievalPatternFlag = flag;
    }

    /**
     * 设置拼接模式标志
     *
     * @param flag 是否拼接模式
     */
    public void setMergePatternFlag(boolean flag) {
        this.mergePatternFlag = flag;
    }

    /**
     * 设置新词标注开关
     *
     * @param flag 是否标注
     */
    public void setMarkNewWordFlag(boolean flag) {
        this.markNewWordFlag = flag;
    }

    /**
     * 设置分词算法 目前支持正向最大匹配、反向最大匹配 分词算法修改需要重新加载词典
     *
     * @param segAlgorithm 分词算法
     */
    public void setSegAlgorithm(SegAlgorithm segAlgorithm) {
        this.segMethod = segAlgorithm;
    }

    /**
     * 设置是否大-小写字母转换
     *
     * @param flag 是否转换
     */
    public void setDowncasingFlag(boolean flag) {
        this.downCasingFlag = flag;
    }
}

词典:

package com.robin.segment.robinseg;

import com.robin.config.ConfigUtil;
import com.robin.log.RobinLogger;
import com.robin.file.FileUtil;
import com.robin.segment.robinseg.Unicode.CharType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * <DT><B>描述:</B></DT>
 * <DD>词典类</DD>
 *
 * @version Version1.0
 * @author Robin
 * @version <I> V1.0 Date:2018-01-28</I>
 * @author  <I> E-mail:[email protected]</I>
 */
class Dictionary {

    private static final Logger LOGGER = RobinLogger.getLogger();

    // 词典路径
    private static final String DIC_PATH;
    // 词典文件编码
    private static final String DIC_ENCODING;
    // 词典MapTree
    private static Map<Character, Object> reverseDicMap = null;
    // 词典MapTree数组
    private static Map[] forwardDicArr = null;

    static {
        DIC_PATH = ConfigUtil.getConfig("dic.base");
        if (DIC_PATH == null) {
            LOGGER.log(Level.SEVERE, "dictionary file path config err.");
        }
        String encoding = ConfigUtil.getConfig("dic.encoding");
        if (null == encoding) {
            DIC_ENCODING = "UTF-8";
        } else {
            DIC_ENCODING = encoding;
        }
        
        loadForwardDictionary();
        loadReverseDictionary();
    }

    static Map<Character, Object> getReverseDicMap() {
        return reverseDicMap;
    }

    static Map[] getForwardDicArr() {
        return forwardDicArr;
    }
    
    /**
     * 加载正向词典mapTree数组
     */
    private static void loadForwardDictionary() {
        if (null != forwardDicArr){
            return;
        }
        LOGGER.log(Level.CONFIG, "Start load reverse dictionary.");
        forwardDicArr = new Map[Unicode.MAX_CHINESE - Unicode.MIN_CHINESE];
        
        //记录上一层map
        Map<Character, Map> prevMap = null;
        //记录下一层map
        Map<Character, Map> nextMap = null;
        // 读取词典文件,词之间使用空格符分割
        String dicStr = FileUtil.readText(DIC_PATH, DIC_ENCODING);
        String[] wordArr = dicStr.split("\n");
        //循环处理每一个词,注意去除第一行,避免文件BOM引起字符解析错误,
        //java读取文件默认按照无BOM方式处理
        for (int i = 1; i < wordArr.length; i++) {
            String word = wordArr[i];
            char[] chars = word.toCharArray();
            if (chars.length <= 0) {
                continue;
            }
            char currChar = ' ';
            //循环处理每一个字符
            for (int j = 0; j < chars.length; j++) {
                currChar = chars[j];
                if (j == 0) {
                    if (Unicode.getCharType(currChar).equals(CharType.COMMON_CHINESE)) {
                        int index = currChar - Unicode.MIN_CHINESE;
                        nextMap = forwardDicArr[index];
                        if (null == nextMap) {
                            nextMap = new HashMap<>();
                            forwardDicArr[index] = nextMap;
                        }
                    }
                    continue;
                }
                if (null == nextMap) {
                    nextMap = new HashMap<>();
                    nextMap.put(currChar, null);
                    if (null != prevMap) {
                        prevMap.put(chars[j - 1], nextMap);
                    }
                } else if (!nextMap.containsKey(currChar)) {
                    nextMap.put(currChar, null);
                }
                prevMap = nextMap;
                nextMap = nextMap.get(currChar);
            }
            //在一个词末尾加上标记,表示在此层可以成词
            if (null == nextMap) {
                nextMap = new HashMap<>();
                if (null != prevMap) {
                    prevMap.put(currChar, nextMap);
                }
            }
            nextMap.put(SegmentArgs.END_MARK, null);
        }

        LOGGER.log(Level.INFO, "Load forward dictionary successfully.");
    }

    /**
     * 加载反向词典mapTree
     */
    private static void loadReverseDictionary() {
        if (null != reverseDicMap) {
            return;
        }
        LOGGER.log(Level.CONFIG, "Start load reverse dictionary.");
        reverseDicMap = new HashMap<>();
        //记录上一层map
        Map<Character, Object> prevMap = null;
        //记录下一层map
        Map<Character, Object> nextMap = null;
        // 读取词典文件,词之间使用空格符分割
        String dicStr = FileUtil.readText(DIC_PATH);
        // 如果是反向匹配,生成反向词典
        StringBuilder sb = new StringBuilder();
        dicStr = sb.append(dicStr).reverse().toString();
        String[] wordArr = dicStr.split("\n");
        //循环处理每一个词
        for (String word : wordArr) {
            char[] chars = word.toCharArray();
            if (chars.length <= 0) {
                continue;
            }
            char currChar;
            //循环处理每一个字符
            for (int j = 0; j < chars.length; j++) {
                currChar = chars[j];
                if (j == 0) {
                    nextMap = reverseDicMap;
                }
                if (null == nextMap) {
                    nextMap = new HashMap<>();
                    nextMap.put(currChar, null);
                    if (null != prevMap) {
                        prevMap.put(chars[j - 1], nextMap);
                    }
                } else if (!nextMap.containsKey(currChar)) {
                    nextMap.put(currChar, null);
                }
                prevMap = nextMap;
                nextMap = (Map<Character, Object>) nextMap.get(currChar);
            }
            //在一个词末尾加上标记,表示在此层可以成词
            currChar = chars[chars.length - 1];
            nextMap = null;
            if (null != prevMap) {
                nextMap = (Map<Character, Object>) prevMap.get(currChar);
            }
            if (null == nextMap) {
                nextMap = new HashMap<>();
                if (null != prevMap) {
                    prevMap.put(currChar, nextMap);
                }
            }
            nextMap.put(SegmentArgs.END_MARK, null);
        }

        LOGGER.log(Level.INFO, "Load reverse dictionary successfully.");
    }

    /**
     * 获取分词全字符字典
     *
     * @return 分词字典
     */
    public String getDicAllTrems() {
        buildTrems(reverseDicMap);
        return dicBuilder.toString();
    }
    private List<Character> wordList = new ArrayList<>();
    private StringBuilder dicBuilder = new StringBuilder();

    /**
     * 递归构建全字符词典
     *
     * @param map Map + Tree 格式词典
     */
    private void buildTrems(Map<Character, Object> map) {
        if (null == map) {
            for (int i = 0; i < wordList.size(); i++) {
                Character character = wordList.get(i);
                dicBuilder.append(character);
            }
            dicBuilder.append("\n");
            return;
        }
        Iterator<Character> it = map.keySet().iterator();
        while (it.hasNext()) {
            Character character = it.next();
            wordList.add(character);
            Map<Character, Object> nextMap = (Map<Character, Object>) map.get(character);
            buildTrems(nextMap);
            wordList.remove(wordList.size() - 1);
        }
    }
}

分词器:

package com.robin.segment.robinseg;

import com.robin.segment.robinseg.SegmentArgs.SegAlgorithm;
import com.robin.segment.AbstractSegmenter;
import com.robin.segment.robinseg.SegmentArgs.WordClass;
import com.robin.segment.robinseg.Unicode.CharType;
import java.util.Map;

/**
 * <DT><B>描述:</B></DT>
 * <DD>Robin分词类</DD>
 * <DD>实现:正向最大匹配算法,反向最大匹配(simple)</DD>
 * <DD>默认:带逆向回退策略的正向最大匹配</DD>
 *
 * @version Version1.0
 * @author Robin
 * @version <I> V1.0 Date:2018-01-28</I>
 * @author  <I> E-mail:[email protected]</I>
 */
public class RobinSeg extends AbstractSegmenter {

    //分词实例
    private static AbstractSegmenter instance = null;
    //分词配置项
    private static final SegmentArgs SEG_CONG = new SegmentArgs();

    //遍历字符序列的标记前一记录
    private Record last = new Record();
    //遍历字符序列的标记当前记录
    private Record curr = new Record();
    //存放已经分好词的序列
    private StringBuilder splited;
    // 当前最大索引(已经扫描到的最前索引,主要用于检索分词)
    private int currMaxIndex = -1;
    
    private final Map[] forwardDicArr;
    private final Map<Character, Object> reverseDicMap;

    /**
     * 构造方法
     */
    private RobinSeg() {
        forwardDicArr = Dictionary.getForwardDicArr();
        reverseDicMap = Dictionary.getReverseDicMap();
    }

    /**
     * 
     * @return
     */
    public SegmentArgs getSegmentConfInstance() {
        return SEG_CONG;
    }

    /**
     * 调用具体分词方法
     *
     * @param text 输入待分词文本
     * @param separator 分隔符
     * @return 已分词文本
     */
    @Override
    public String segment(String text, String separator) {
        synchronized (this) {
            if (SEG_CONG.segMethod.equals(SegAlgorithm.FORWARD)) {
                return forwardMaxMatch(text, separator);
            } else if (SEG_CONG.segMethod.equals(SegAlgorithm.REVERSE)) {
                return reverseMaxMatch(text, separator);
            }
        }
        assert false : "算法类型参数错误,检查参数设置。";
        return "";
    }

    /**
     * 判断当前字符序列是否在字典中构成一个完整的词
     *
     * @return boolean 是否在字典中构成一个完整的词
     */
    private boolean isStandardWord(char[] wordArr) {
        if (wordArr.length <= 0) {
            return false;
        }
        Map<Character, Map> nextMap = null;
        if (Unicode.getCharType(wordArr[0]).equals(CharType.COMMON_CHINESE)) {
            nextMap = forwardDicArr[wordArr[0] - Unicode.MIN_CHINESE];
        }
        int index = 1;
        while ((null != nextMap) && (index < wordArr.length)) {
            if (!nextMap.containsKey(wordArr[index])) {
                return false;
            }
            nextMap = nextMap.get(wordArr[index]);
            index++;
        }
        return (null != nextMap) && nextMap.containsKey(SegmentArgs.END_MARK);
    }

    /**
     * 判断当前字符在字符序列里是否是孤立字符(不能够成为中文词首)
     *
     * @return boolean 是否是孤立字符
     */
    private boolean isIsoLatedChar(char[] charArray, int cursor) {
        int textLength = charArray.length;
        int next = cursor + 1;
        if (next >= textLength) {
            return true;//字符序列结尾是孤立字符
        }
        Character curChar = charArray[next];
        Map<Character, Map> nextMap;
        if (Unicode.getCharType(curChar).equals(CharType.COMMON_CHINESE)) {
            nextMap = (forwardDicArr[curChar - Unicode.MIN_CHINESE]);
            if (null == nextMap) {
                return false;//后一字符是字典中不存在的中文,认为本字符不孤立
            }
        } else {
            return true;//后一字符是非中文其他任何符号都认为是孤立字符
        }
        while (next < textLength) {
            next++;
            if (nextMap.containsKey(SegmentArgs.END_MARK)) {
                return true;
            }
            curChar = null;
            if (next < textLength) {
                curChar = charArray[next];
            }
            nextMap = nextMap.get(curChar);
            if (null == nextMap) {
                return false;
            }
        }
        return false;
    }

    /**
     * 词的记录私有类
     */
    private class Record {

        //词的类别
        WordClass wordClass = WordClass.OTHER;
        //以一个字符开头一条字符序列最多可能形成的词的数目,先估计最大不超过10个
        private static final int MAX_NUM = 10;
        int start = 0;
        int maxIdx = -1;
        int[] end = new int[MAX_NUM];
        boolean continued = false;
        Record next = null;

        /**
         * 重置记录项
         */
        public void init(int start) {
            this.start = start;
        }

        /**
         * 记录构成词的游标位置 仅标准词(词典存在的词)才会调用此方法
         */
        public void addIndex(int cursor) {
            assert (maxIdx < MAX_NUM - 1) : "字符序列成词数目越界,扩大词数目.";
            end[++maxIdx] = cursor;
        }

        /**
         * 重置记录项
         */
        public void reset() {
            continued = false;
            wordClass = WordClass.OTHER;
            for (int i = 0; i <= maxIdx; i++) {
                end[i] = 0;
            }
            maxIdx = -1;
        }
    }

    /**
     * 记录前移,交换角色
     */
    private void moveRocord() {
        Record swap = last;
        last = curr;
        curr = swap;
        curr.reset();
    }

    /**
     * 文本分词 正向最大匹配改进算法
     *
     * @param text 输出待分词文本
     * @param separator 分隔符
     * @return 使用分隔符隔开的词文本
     */
    private String forwardMaxMatch(String text, String separator) {
        assert SEG_CONG.segMethod.equals(SegAlgorithm.FORWARD) : "算法类型设置错误。";
        currMaxIndex = -1;
        SEG_CONG.setSeparator(separator);
        splited = new StringBuilder();
        //添加一个空格,为了处理字符结尾是标准词序列
        char[] charArray = text.concat(" ").toCharArray();
        int textLength = charArray.length;
        int cursor = 0;
        //循环遍历字符序列
        while (cursor < charArray.length) {
            int start = cursor;
            if (!curr.continued) {
                curr.init(start);
            }
            char currChar = charArray[cursor];
            switch (Unicode.getCharType(currChar)) {
                //普通中文汉字
                case COMMON_CHINESE:
                    //搜索词典
                    boolean success = searchInDictionary(charArray, cursor);
                    if (success) {
                        if (SEG_CONG.retrievalPatternFlag) {
                            cursor++;
                        } else {
                            cursor = last.end[last.maxIdx];
                        }
                        continue;
                    }

                    if (SEG_CONG.retrievalPatternFlag) {
                        if (curr.start < currMaxIndex) {
                            cursor++;
                            continue;
                        }
                    }
                    //词典未搜索到,回退
                    cursor = start;
                    currChar = charArray[cursor];
                    //中文数字
                    if (Unicode.isNumeral(currChar)) {
                        cursor = handleCNNumeral(charArray, cursor);
                        continue;
                    }
                    //如果字符前面是未处理完的非标准词
                    if (curr.continued) {
                        if (curr.wordClass == WordClass.ENGLISH) {
                            curr.wordClass = WordClass.CN_EN_MIX;
                        } else if (curr.wordClass != WordClass.CN_EN_MIX) {
                            curr.wordClass = WordClass.NEW_WORD;
                        }
                        break;
                    }
                    //非单独孤立中文首字符
                    if (!isIsoLatedChar(charArray, cursor)) {
                        curr.wordClass = WordClass.NEW_WORD;
                        break;
                    }
                    //当前处理的字符是单个孤立的中文字符
                    if (SEG_CONG.mergePatternFlag
                            && WordClass.STANDARD == last.wordClass) {
                        cursor = handleIsoLatedCNChar(charArray, cursor);
                        continue;
                    }
                    curr.wordClass = WordClass.ISOLATED_CN;
                    break;

                //半角数字
                case DBC_DIGIT:
                    curr.wordClass = WordClass.QUANTIFIER;
                    if (handleQuantifier(charArray, cursor)) {
                        cursor = last.end[last.maxIdx];
                        continue;
                    }
                    break;

                //半角大写字母
                case DBC_UPPER_CASE:
                    if (SEG_CONG.downCasingFlag) {
                        charArray[cursor]
                                = (char) (charArray[cursor] + Unicode.OFFSET_UP_LOW);
                    }
                /**
                 * 穿越CASE到小写处理
                 */
                //半角小写字母
                case DBC_LOWER_CASE:
                    if ((curr.wordClass == WordClass.NEW_WORD)
                            || (curr.wordClass == WordClass.ISOLATED_CN)) {
                        curr.wordClass = WordClass.CN_EN_MIX;
                    } else if (curr.wordClass != WordClass.CN_EN_MIX) {
                        curr.wordClass = WordClass.ENGLISH;
                    }
                    break;

                //关注的全角区
                case SBC_CHAR:
                    //全角统一转换并回退处理
                    charArray[cursor]
                            = (char) (charArray[cursor] - Unicode.OFFSET_DBC_SBC);
                    continue;

                //数字后缀
                case DECIMAL_SUFFIX:
                    if (cursor > 0) {
                        //正向的考虑前一个字符
                        char prev = charArray[cursor - 1];
                        if (Unicode.isDecimal(prev)) {
                            splited.append(charArray[cursor]);
                            splited.append(SEG_CONG.separator);
                            curr.continued = false;
                            cursor++;
                            curr.end[0] = cursor;
                            curr.wordClass = WordClass.QUANTIFIER;
                            moveRocord();
                            continue;
                        }
                    }
                /**
                 * 此处需要穿越到case OTHER_CHAR,判断符号是否保留,不满足数字后缀的
                 * 话更不满足小数要求,case穿越没问题,但是不能调换上下两条case语句位置.
                 */
                //小数点或英文句点符号
                case DECIMAL_POINT:
                    if ((cursor > 0) && (cursor < textLength - 1)) {
                        char prev = charArray[cursor - 1];
                        char next = charArray[cursor + 1];
                        if (Unicode.isDecimal(prev) && Unicode.isDecimal(next)) {
                            curr.wordClass = WordClass.QUANTIFIER;
                            break;
                        }
                    }
                /**
                 * 此处就是要穿越到下一条case,判断符号是否保留.
                 */
                // 其他所有字符
                case OTHER_CHAR:
                    addSeparator(start);
                    if (!SEG_CONG.cleanSymbolFlag) {
                        splited.append(charArray[cursor]);
                        splited.append(SEG_CONG.separator);
                    }
                    cursor++;
                    curr.end[0] = cursor;
                    curr.wordClass = WordClass.OTHER;
                    moveRocord();
                    continue;

                // 空白符
                case BLANK_CHAR:
                    addSeparator(start);
                    cursor++;
                    curr.addIndex(cursor);
                    curr.wordClass = WordClass.OTHER;
                    moveRocord();
                    continue;

                default:
                    break;
            }
            //将字典没有的字符添加到分词StringBuilder,并且不分割,与下一个孤立字符合并
            splited.append(charArray[cursor]);
            curr.continued = true;
            cursor++;
        }
        return splited.toString();
    }

    /**
     * 根据条件增加分隔符
     *
     * @param start 下一记录起始位置
     */
    private void addSeparator(int start) {
        //如果还未分割
        if (curr.continued) {
            if (SEG_CONG.markNewWordFlag) {
                switch (curr.wordClass) {
                    case NEW_WORD:
                        splited.append(SegmentArgs.NEW_WORD_MARK);
                        break;
                    case CN_EN_MIX:
                        splited.append(SegmentArgs.CN_EN_MIX_MARK);
                        break;
                    case SPLICE_WORD:
                        splited.append(SegmentArgs.SPLICE_WORD_MARK);
                        break;
                    default:
                        break;
                }
            }
            splited.append(SEG_CONG.separator);
            curr.addIndex(start);
            moveRocord();
            curr.init(start);
        }
    }

    /**
     * 处理(阿拉伯数字)数量词
     *
     * @param charArray 字符序列
     * @param cursor 输入游标位置
     * @return boolean 是否完成
     */
    private boolean handleQuantifier(char[] charArray, int cursor) {
        int start = cursor;
        char currChar = charArray[cursor];
        //正向最大匹配的数字前一个是未分割
        if (cursor > 0) {
            //考虑前一个字符
            Character prev = charArray[cursor - 1];
            if (!Unicode.isDecimal(prev) && !Unicode.isPoint(prev)) {
                addSeparator(start);
            }
        }
        //正向最大匹配的数字+量词
        if (cursor < charArray.length - 1) {
            //正向的考虑后一个字符
            Character next = charArray[++cursor];
            if (Unicode.isQuantifier(next)) {
                splited.append(currChar);
                splited.append(next);
                splited.append(SEG_CONG.separator);
                curr.continued = false;
                curr.wordClass = WordClass.QUANTIFIER;
                cursor++;
                curr.addIndex(cursor);
                moveRocord();
                return true;
            }
        }
        return false;
    }

    /**
     * 词典中查找匹配的词
     *
     * @param charArray 字符序列
     * @param cursor 当前游标
     * @return boolean 是否搜索到
     */
    private boolean searchInDictionary(char[] charArray, int cursor) {
        //本次词典搜索的起始字符位置
        int start = cursor;
        int textMaxIdx = charArray.length - 1;
        char currChar = charArray[cursor];
        Map<Character, Map> nextMap = forwardDicArr[currChar - Unicode.MIN_CHINESE];
        while ((null != nextMap) && (cursor < textMaxIdx)) {
            cursor++;
            if (nextMap.containsKey(SegmentArgs.END_MARK)) {
                addSeparator(start);
                curr.addIndex(cursor);
                curr.wordClass = WordClass.STANDARD;
            }
            currChar = charArray[cursor];
            nextMap = nextMap.get(currChar);
            if (null == nextMap) {
                if (curr.maxIdx > -1) {
                    //词典存在最大匹配词
                    if (SEG_CONG.retrievalPatternFlag) {
                        for (int i = 0; i <= curr.maxIdx; i++) {
                            int end = curr.end[i];
                            for (int j = curr.start; j < end; j++) {
                                splited.append(charArray[j]);
                            }
                            splited.append(SEG_CONG.separator);
                        }
                        if (curr.end[curr.maxIdx] > currMaxIndex) {
                            currMaxIndex = curr.end[curr.maxIdx];
                        }
                    } else {
                        int end = curr.end[curr.maxIdx];
                        for (int i = curr.start; i < end; i++) {
                            splited.append(charArray[i]);
                        }
                        splited.append(SEG_CONG.separator);
                    }
                    moveRocord();
                    return true;
                } else {
                    return false;
                }
            }
        }
        return false;
    }

    /**
     * 处理中文数字
     *
     * @param charArray 字符序列
     * @param cursor 输入游标位置
     * @return cursor 输出游标位置
     */
    private int handleCNNumeral(char[] charArray, int cursor) {
        int start = cursor;
        char currChar = charArray[cursor];
        addSeparator(start);
        splited.append(currChar);
        cursor++;
        curr.wordClass = WordClass.QUANTIFIER;
        while (cursor < charArray.length) {
            currChar = charArray[cursor];
            if (Unicode.isNumeral(currChar)) {
                splited.append(currChar);
                cursor++;
            } else if (Unicode.isQuantifier(currChar)) {
                splited.append(currChar);
                cursor++;
                break;
            } else {
                break;
            }
        }
        splited.append(SEG_CONG.separator);
        curr.end[0] = cursor;
        curr.maxIdx = 0;
        moveRocord();
        return cursor;
    }

    /**
     * 处理中文孤立字符
     *
     * @param charArray 字符序列
     * @param cursor 输入游标位置
     * @return int 输出游标位置
     */
    private int handleIsoLatedCNChar(char[] charArray, int cursor) {

        char currChar = charArray[cursor];
        if (last.maxIdx > 0) {
            int bestEnd = last.end[last.maxIdx];
            int betterEnd = last.end[last.maxIdx - 1];
            int len = bestEnd - betterEnd + 1;
            char[] wordArr = new char[len];
            for (int i = 0; i < wordArr.length; i++) {
                wordArr[i] = charArray[betterEnd + i];
            }
            if (isStandardWord(wordArr)) {
                splited.delete(splited.length() - len, splited.length());
                splited.append(SEG_CONG.separator);
                last.maxIdx--;
                curr.init(betterEnd);
                curr.addIndex(bestEnd);
                for (int i = betterEnd; i <= cursor; i++) {
                    splited.append(charArray[i]);
                }
                splited.append(SEG_CONG.separator);
                curr.wordClass = WordClass.STANDARD;
                moveRocord();
                cursor++;
                return cursor;
            }
        }
        splited.delete(splited.length() - 1, splited.length());
        splited.append(currChar);
        Record temp = curr;
        curr = last;//此处last仅作为作为持有对象的引用,并不是上一条记录了
        last = temp;
        curr.wordClass = WordClass.SPLICE_WORD;
        curr.continued = true;
        cursor++;
        addSeparator(cursor);
        return cursor;
    }

    /**
     * 文本分词 反向
     *
     * @param text 输出待分词文本
     * @param separator 分隔符
     * @return 使用分隔符隔开的词文本
     */
    private String reverseMaxMatch(String text, String separator) {
        assert SEG_CONG.segMethod.equals(SegAlgorithm.REVERSE) : "算法类型设置错误。";
        boolean separated = true;
        boolean hasAppend;
        StringBuilder sb = new StringBuilder();
        Map<Character, Object> nextLevelMap;
        Map<Character, Object> rootMap = reverseDicMap;
        int textMaxIndex = text.length() - 1;

        text = sb.append(text).reverse().toString();
        sb.delete(0, textMaxIndex + 1);

        int cursor = 0;
        int start;
        int end = 0;
        char[] charArray = text.toCharArray();
        while (cursor < charArray.length) {
            Character curChar = charArray[cursor];
            start = cursor;
            nextLevelMap = (Map<Character, Object>) rootMap.get(curChar);
            if (null == nextLevelMap) {
                hasAppend = false;
                switch (Unicode.getCharType(curChar)) {
                    //普通中文汉字
                    case COMMON_CHINESE:
                        if (separated) {
                            if (!Unicode.isQuantifier(curChar)) {
                                sb.append('#');
                            }
                            sb.append(charArray[cursor]);
                            separated = false;
                            hasAppend = true;
                        } else {
                            if (Unicode.isQuantifier(curChar)) {
                                sb.append(separator);
                                separated = true;
                            }
                        }
                        break;
                    //半角数字
                    case DBC_DIGIT:
                        if (cursor < textMaxIndex) {
                            Character next = charArray[cursor + 1];
                            if (!Unicode.isDecimalType(next)) {
                                sb.append(charArray[cursor]);
                                sb.append(separator);
                                separated = true;
                                cursor++;
                                continue;
                            }
                        }
                        break;
                    //半角小写字母
                    case DBC_LOWER_CASE:
                        break;
                    //半角大写字母
                    case DBC_UPPER_CASE:
                        if (SEG_CONG.downCasingFlag) {
                            charArray[cursor] = (char) (charArray[cursor] + Unicode.OFFSET_UP_LOW);
                        }
                        break;
                    //关注的全角区,目前不会返回全角子区
                    case SBC_CHAR:
                    //全角字母
                    case SBC_CASE:
                    //全角数字
                    case SBC_DIGIT:
                    //全角大写字母
                    case SBC_UPPER_CASE:
                    //全角小写字母
                    case SBC_LOWER_CASE:
                        //全角统一转换并回退处理
                        charArray[cursor] = (char) (charArray[cursor] - Unicode.OFFSET_DBC_SBC);
                        continue;
                    //数字后缀
                    case DECIMAL_SUFFIX:
                        if (cursor < textMaxIndex) {
                            //反向的考虑后一个字符
                            Character next = charArray[cursor + 1];
                            if (Unicode.isDecimal(next)) {
                                if (!separated) {
                                    sb.append(separator);
                                }
                                sb.append(charArray[cursor]);
                                separated = false;
                                cursor++;
                                continue;
                            }
                        }
                    /**
                     * 此处需要穿越到case OTHER_CHAR,判断符号是否保留,不满足数字后缀的
                     * 话更不满足小数要求,case穿越没问题,但是不能调换上下两条case语句位置.
                     */
                    //小数点或英文句点符号
                    case DECIMAL_POINT:
                        if ((cursor > 0) && (cursor < textMaxIndex)) {
                            Character prev = charArray[cursor - 1];
                            Character next = charArray[cursor + 1];
                            if (Unicode.isDecimal(prev) && Unicode.isDecimal(next)) {
                                break;
                            }
                        }
                    /**
                     * 此处就是要穿越到下一条case,判断符号是否保留.
                     */
                    // 其他所有字符
                    case OTHER_CHAR:
                        if (!separated) {
                            sb.append(separator);
                        }
                        if (!SEG_CONG.cleanSymbolFlag) {
                            sb.append(charArray[cursor]);
                            sb.append(separator);
                        }
                        separated = true;
                        hasAppend = true;
                        break;

                    // 空白符
                    case BLANK_CHAR:
                        if (!separated) {
                            sb.append(separator);
                        }
                        separated = true;
                        hasAppend = true;
                        break;
                    default:
                        break;
                }
                //将字典没有的字符添加到分词StringBuilder,并且不分割,与下一个孤立字符合并
                if (!hasAppend) {
                    sb.append(charArray[cursor]);
                    separated = false;
                }
                cursor++;
                continue;
            }
            cursor++;
            while ((null != nextLevelMap) && (cursor <= textMaxIndex)) {
                if (nextLevelMap.containsKey(SegmentArgs.END_MARK)) {
                    end = cursor;
                }
                curChar = charArray[cursor];
                if (!nextLevelMap.containsKey(curChar)) {
                    if (end <= start) {
                        //孤立字符
                        if (Unicode.isQuantifier(charArray[start])) {
                            if (!separated) {
                                sb.append(separator);
                            }
                        } else if (separated) {
                            sb.append('#');
                        }
                        sb.append(charArray[start]);
                        separated = false;
                        cursor = start + 1;
                    } else {
                        //词典存的最大匹配词
                        if (!separated) {
                            sb.append(separator);
                        }
                        for (int i = start; i < end; i++) {
                            sb.append(charArray[i]);
                        }
                        sb.append(separator);
                        separated = true;
                        cursor = end;
                    }
                    break;
                }
                nextLevelMap = (Map<Character, Object>) nextLevelMap.get(curChar);
                cursor++;
            }
            //解决最后一个可构成词,前一字符不能构成词,无分隔符问题
            if (cursor == textMaxIndex + 1) {
                if ((!separated) && (null != nextLevelMap)
                        && (nextLevelMap.containsKey(SegmentArgs.END_MARK))) {
                    sb.append(separator);
                }
                for (int i = start; i
                        < charArray.length; i++) {
                    sb.append(charArray[i]);
                }
            }
        }

        String splitedText;
        if (SEG_CONG.segMethod.equals(SegAlgorithm.REVERSE)) {
            splitedText = sb.reverse().toString();
        } else {
            splitedText = sb.toString();
        }
        return splitedText;
    }

    /**
     * 获取 RobinSeg 分词类的实例
     *
     * @return 分词类的单实例
     */
    public static AbstractSegmenter getInstance() {
        if (null == instance) {
            instance = new RobinSeg();
        }
        return instance;
    }
}

运行结果

分词效果

-----------------------------分词效果-------------------------------
【-用例原始文本-】:马云,中国著名企业家,浙江绍兴人,阿里巴巴集团主要创始人之一。现任阿里巴巴集团主席和首席执行官,他是《福布斯》杂志创办50多年来成为封面人物的首位大陆企业家,曾获选为未来全球领袖。董事等职务。2013年3月11日,阿里巴巴集团董事局主席兼CEO马云昨日发出内部邮件称,集团首席数据官陆兆禧将接任CEO一职。
【拼接-分类-模式】:马云[⊙新词]|中国|著名|企业家|浙江|绍兴|人|阿里巴巴|集团|主要|创始人|之一|现任|阿里巴巴|集团|主席|和|首席执行官|他是|福布斯|杂志|创办|50|多年来|成为|封面人物|的|首位|大陆|企业家|曾|获选|为|未来|全球|领袖|董事|等|职务|2013年|3月|11日|阿里巴巴|集团|董事局|主席|兼|ceo马云[⊙混合词]|昨日|发出|内部|邮件|称|集团|首席|数据|官[⊙新词]|陆兆|禧|将|接任|ceo|一|职|
【拼接-检索-模式】:马云[⊙新词]|中|中国|著名|企业|企业家|浙江|绍兴|人|阿|阿里|阿里巴巴|巴巴|集团|主要|要|创始|创始人|人|之|之一|现任|任|阿|阿里|阿里巴巴|巴巴|集团|主席|和|首席|首席执行官|执行|执行官|他|他是|是|福布斯|杂志|创办|办|50|多|多年|多年来|年来|来|成|成为|为|封面|封面人物|面人|人|人物|的|首位|大|大陆|企业|企业家|曾|获选|选|选为|为|未来|来|全球|领|领袖|董事|等|职务|2013年|3月|11日|阿|阿里|阿里巴巴|巴巴|集团|董事|董事局|主席|兼|ceo马云[⊙混合词]|昨日|日|发|发出|出|内部|邮件|称|集团|首席|数|数据|据|官[⊙新词]|陆兆|禧|将|接|接任|任|ceo|一|职|
-------------------------------------------------------------------
【-用例原始文本-】:阿拉伯金额数字万位和元位是"0",或者数字中间连续有几个"0",万位、元位也是"0",但千位、角位不是"0"时,中文大写金额中可以只写一个零字,也可以不写"零"字。如¥1680.32,应写成人民币壹仟陆佰捌拾元零叁角贰分,或者写成人民币壹仟陆佰捌拾元叁角贰分,又如¥107000.53,应写成人民币壹拾万柒仟元零伍角叁分,或者写成人民币壹拾万零柒仟元伍角叁分。 
【拼接-分类-模式】:阿拉伯|金额|数字|万位[⊙新词]|和|元位[⊙拼接词]|是|0|或者|数字|中间|连续|有几个|0|万位[⊙新词]|元位[⊙拼接词]|也是|0|但|千位[⊙新词]|角位[⊙拼接词]|不是|0|时|中文|大写|金额|中|可以|只写|一个|零字|也可以|不|写|零|字|如|1680.32|应|写成|人民币|壹仟|陆佰|捌拾元|零叁角|贰分|或者|写成|人民币|壹仟|陆佰|捌拾元|叁角|贰分|又如|107000.53|应|写成|人民币|壹拾万|柒仟|元|零伍角|叁分|或者|写成|人民币|壹拾万|零柒仟|元|伍角|叁分|
-------------------------------------------------------------------
【-用例原始文本-】:p非v IBM研究院 IBM非 VC维 VC银翘片,魅族MX系列。我很喜欢陈述高的演讲,我很不喜欢陈述高调的样子。人们向林俊德表示深切的问候。中华人民共和国
【拼接-分类-模式】:p非v[⊙混合词]|ibm|研究院|ibm非[⊙混合词]|vc维[⊙混合词]|vc银翘片[⊙混合词]|魅族mx[⊙混合词]|系列|我很|喜欢|陈述高[⊙拼接词]|的|演讲|我很|不喜欢|陈述|高调|的|样子|人们|向|林俊德[⊙新词]|表示|深切|的|问候|中华人民共和国|
-------------------------------------------------------------------
【-用例原始文本-】:姚明退役了。草泥马的,据路透社报道,印度尼西亚社会事务部一官员星期二(29日)表示,日惹市附近当地时间27日晨5时53分发生的里氏6.2级地震已经造成至少5427人死亡,20000余人受伤,近20万人无家可归。
【拆分-分类-模式】:姚明|退役|了|草泥马[⊙新词]|的|据|路透社|报道|印度尼西亚|社会|事务部|一|官员|星期二|29日|表示|日惹|市|附近|当地时间|27日|晨|5时|53分|发生|的|里氏|6.2级|地震|已经|造成|至少|5427|人|死亡|20000|余人|受伤|近|20万|人|无家可归|
【拼接-分类-模式】:姚明|退役|了|草泥马[⊙新词]|的|据|路透社|报道|印度尼西亚|社会|事务部|一|官员|星期二|29日|表示|日惹市[⊙拼接词]|附近|当地时间|27日|晨|5时|53分|发生|的|里氏|6.2级|地震|已经|造成|至少|5427|人|死亡|20000|余人|受伤|近|20万|人|无家可归|
-------------------------------------------------------------------
【-用例原始文本-】:歧义和同义词:研究生命起源,混合词: 做B超检查身体,本质是X射线,单位和全角: 2009年8月6日开始大学之旅,中文数字: 四分之三的人都交了六十五块钱班费,那是一九九八年前的事了,四川麻辣烫很好吃,五四运动留下的五四精神。笔记本五折包邮亏本大甩卖。人名识别: 我是陈鑫,也是jcesg的作者,三国时期的诸葛亮是个天才,我们一起给刘翔加油,罗志高兴奋极了因为老吴送了他一台笔记本。配对标点: 本次『畅想杯』黑客技术大赛的得主为电信09-2BF的张三,奖励C++程序设计语言一书和【畅想网络】的『PHP教程』一套。特殊字母: 【Ⅰ】(Ⅱ),英文数字: bug report [email protected] or visit http://code.google.com/p/jcseg, 15% of the day's time i will be there.特殊数字: ① ⑩ ⑽ ㈩. 
【拆分-分类-模式】:歧义|和|同义词|研究生|命|起源|混合|词|做|b超[⊙混合词]|检查|身体|本质|是|x|射线|单位|和|全角|2009年|8月|6日|开始|大学|之旅|中文数字|四分之三|的|人|都|交了|六十五块|钱|班费|那是|一九九八年|前|的|事|了|四川|麻辣烫|很好|吃|五四运动|留下|的|五四|精神|笔记本|五折|包|邮|亏本|大|甩卖|人名|识别|我是|陈鑫[⊙新词]|也是|jcesg|的|作者|三国|时期|的|诸葛亮[⊙新词]|是个|天才|我们|一起|给|刘翔[⊙新词]|加油|罗志[⊙新词]|高兴|奋|极了|因为|老吴|送了|他|一台|笔记本|配对|标点|本次|畅想|杯|黑客|技术|大赛|的|得主|为|电信|09|2bf|的|张三|奖励|c|程序设计语言|一书|和|畅想|网络|的|php|教程|一套|特殊|字母|英文|数字|bug|report|chenxin|619315|gmail|com|or|visit|http|code|google|com|p|jcseg|15%|of|the|day|s|time|i|will|be|there|特殊|数字|
【拼接-分类-模式】:歧义|和|同义词|研究|生命|起源|混合词[⊙拼接词]|做|b超[⊙混合词]|检查|身体|本质|是|x|射线|单位|和|全角|2009年|8月|6日|开始|大学|之旅|中文数字|四分之三|的|人|都|交了|六十五块|钱|班费|那是|一九九八年|前|的事[⊙拼接词]|了|四川|麻辣烫|很好|吃|五四运动|留下|的|五四|精神|笔记本|五折|包邮[⊙拼接词]|亏本|大|甩卖|人名|识别|我是|陈鑫[⊙新词]|也是|jcesg|的|作者|三国|时期|的|诸葛亮[⊙新词]|是个|天才|我们|一起|给|刘翔[⊙新词]|加油|罗志[⊙新词]|高兴奋[⊙拼接词]|极了|因为|老吴|送了|他|一台|笔记本|配对|标点|本次|畅想杯[⊙拼接词]|黑客|技术|大赛|的|得主|为|电信|09|2bf|的|张三|奖励|c|程序设计语言|一书|和|畅想|网络|的|php|教程|一套|特殊|字母|英文|数字|bug|report|chenxin|619315|gmail|com|or|visit|http|code|google|com|p|jcseg|15%|of|the|day|s|time|i|will|be|there|特殊|数字|
-------------------------------------------------------------------
【-用例原始文本-】: ENGLISH CHInese WhAt are you doING!. The state investigation that followed the AJC’s analyses of test scores depicted a culture that rewarded cheaters, punished whistle-blowers and covered up improprieties.
【拼接-分类-模式】:english|chinese|what|are|you|doing|the|state|investigation|that|followed|the|ajc|s|analyses|of|test|scores|depicted|a|culture|that|rewarded|cheaters|punished|whistle|blowers|and|covered|up|improprieties|
-------------------------------------------------------------------
【-用例原始文本-】:全角半角、大小写转换 小数:5条狗,4.5个 6.3 6.7 0.6 7.8 1.5 ENglIsH ChineSE. TExt.SEGMenT 0.1%2.3$4.5¥6.7%8.9¥ 11.2 %¥$
【拆分-分类-模式】:全角|半角|大小写|转换|小数|5条|狗|4.5个|6.3|6.7|0.6|7.8|1.5|english|chinese|text|segment|0.1%|2.3$|4.5¥|6.7%|8.9¥|11.2|
-------------------------------------------------------------------

分词性能

⊙字符总数:[  17.25 ]W. 用时:[   18 ]ms。 速度:[  958W ]字符/秒。文件名:data/凯文凯利-失控.txt 
失控|全人类|的|最终|命运|和|结局|作者|美|凯文[⊙新词]|凯利[⊙新词]|著|东西|文库|译|出版社|新星|出版社|出版|时间|2010|12|1|版次|1|页数|700|字数|500000|印刷|时间|2010|12|1|开本|16开|纸张|胶版纸|印次|1|isbn|9787513300711|包装|平装|内容简介|这是|黑客帝国|主要|演员|的|必读物[⊙拼接词]|之一|这本|关于|
-------------------------------------------------------------------
⊙字符总数:[  37.10 ]W. 用时:[   34 ]ms。 速度:[ 1091W ]字符/秒。文件名:data/绝影-疯狂的程序员.txt 
疯狂|的|程序员|绝影[⊙新词]|1|hello|world|天|已经|七分|黑了|屋里|却|还没|开灯|这个|全身|黑衣服|的|男子|突然|像|想起|什么|从|包里[⊙拼接词]|掏出烟[⊙拼接词]|抽出|一只|递给|旁边|的|人|兄弟|抽烟|么|那烟[⊙拼接词]|是|红塔山|旁边|那人|连忙|一边|摆手|一边|说|不|不|语气|有点|紧张|好像|那|黑衣服|递过来|的|不是烟[⊙拼接词]|是|海
-------------------------------------------------------------------
⊙字符总数:[   4.27 ]W. 用时:[    5 ]ms。 速度:[  853W ]字符/秒。文件名:data/高晓松-如丧.txt 
如丧[⊙拼接词]|我们|终于|老得|可以|谈谈|未来|序|时隔|十二年|为|出版|第二|本|文集|坐在|洛杉矶|垂垂[⊙新词]|夕阳|下|校对|文稿|看到|歌词|部分|时|忽然|瞥见|好多年|前|给|叶蓓|的|几句词[⊙拼接词]|夕阳|你|温暖|的|肩膀|我|柔软的|心房|大地|以及|忧伤|每一|天|在|我心|上|我为你舞[⊙拼接词]|在|远方|我是|你的|花|我不|管|春天|有多|长|正好在|夕
-------------------------------------------------------------------
⊙字符总数:[ 235.73 ]W. 用时:[  238 ]ms。 速度:[  990W ]字符/秒。文件名:data/谭松波-体育-2805P.txt 
1|跳水队[⊙拼接词]|老大哥|熊倪[⊙新词]|在|一个|集体|跳水|的|动作|中|因|队友|失手|不|配合|头撼[⊙新词]|对方|的|臀部|而|伤|及|後颈[⊙新词]|香港电[⊙拼接词]|高台|集体|跳水|失手|奥运|巨星|熊倪[⊙新词]|伤颈[⊙拼接词]|万众瞩目|的|中国|跳水队[⊙拼接词]|昨天|首次|在|九龙|公园|表演|时|险|生|悲剧|跳水队[⊙拼接词]|老大哥|熊倪[⊙新词]|在|
-------------------------------------------------------------------
⊙字符总数:[  14.01 ]W. 用时:[   14 ]ms。 速度:[ 1001W ]字符/秒。文件名:data/塞林格-麦田里的守望者.txt 
麦田|里的|守望|者|作者|塞林格[⊙新词]|状态|全本|内容简介|本书|的|主人公|霍尔顿[⊙拼接词]|是个|中学生|出身|于|富裕|的|中产阶级|家庭|他|虽|只有|16岁[⊙新词]|但|比|常人|高出|一头|整日|穿着|风雨衣|戴着|鸭舌帽|游游荡荡|不愿|读书|他对|学校|里的|一切|老师|同学|功课|球赛|等等|全都|腻烦|透了|3次[⊙新词]|被|学校|开除|又一个|学期结束|了|他|
-------------------------------------------------------------------

分析总结

  由于笔者没有重点研究中文分词,代码也仅是简单实现,分词效果没有进行专业评估。后续将看到RS分词在文本分类中的应用,验证分类结果与其他分词器差别不大。

  • 可用性:支持多种模式,有效支持中文文本分类的分类结果;
  • 效率高:Core 2.50GHz下单线程 分词速度:千万字符/秒 量级,字节处理速度超过15MB/s.

参考:
[1]https://blog.csdn.net/u013063153/article/details/72904322
[2]http://blog.jobbole.com/111680/
[3]http://blog.51cto.com/tianxingzhe/1720067
[4]https://blog.csdn.net/flysky1991/article/details/73948971

猜你喜欢

转载自blog.csdn.net/xsdjj/article/details/83759093