solr中的同义词配置以及关键源码解读

阅读更多

由于工作中需要做同义词,今天看了看solr的实现以及源码,记个笔记。我看的solr的版本是5.5.3.

在solr的schema.xml中(5.x的版本是managed-schema文件)已经有实例了,截图如下:

     
      
        
        
        
        
      
      
          
          
          
         
      
    

 关键就是配置的SynonyFilterFactory,我们看看他的源码:

SynonymFilterFactory类时继承自TokenFilterFactory类,后者为所有的TokenFilter工厂的抽象类,上图中的LowerCaseFilterFactory也是继承自这个类。TokenFilterFactory这个抽象类最关键的是create(TokenStream)方法,即根据tokenizer的操作继续添加操作,这个倒是很容易理解,在TokenizerFactory中也有个create方法,不过是没有参数的,因为此时还没有生成tokenStream。

明白了TokenFilterFactory之后,再看一下SynonymFilterFactory类的结构,直接看他的构造方法吧:

  public SynonymFilterFactory(Map args) {//map即在配置中的参数,比如上面的synonyms,ignoreCase,expand
    super(args);
    ignoreCase = getBoolean(args, "ignoreCase", false);//ignoreCase表示再分词匹配的时候要不要忽略大小写
    synonyms = require(args, "synonyms");//同义词词典的位置
    format = get(args, "format");//解析同义词词典的时候使用的格式化对象,即怎么从同义词词典中解读同义词
    expand = getBoolean(args, "expand", true);//这个也是在解读同义词词典的时候的参数,用语言不好描述,等用到再说

    analyzerName = get(args, "analyzer");//这个是在词典表中读取一个字符串要进行分词,这个指定使用的分词器
    tokenizerFactory = get(args, "tokenizerFactory");//这个和上面的意思一样,只不过使用的是工厂模式
    if (analyzerName != null && tokenizerFactory != null) {
      throw new IllegalArgumentException("Analyzer and TokenizerFactory can't be specified both: " +
                                         analyzerName + " and " + tokenizerFactory);
    }

。。。//忽略不重要的参数
  }

 然后我们看看对同义词词典的加载,在org.apache.lucene.analysis.synonym.SynonymFilterFactory.inform(ResourceLoader)方法中有个loadSynonyms方法,顾名思义,就是加载同义词词典表的方法

 protected SynonymMap loadSynonyms(ResourceLoader loader, String cname, boolean dedup, Analyzer analyzer) throws IOException, ParseException {//第二个参数是使用的格式化对象的名字,第三个是加载表的时候要不要排除重复的,第四个是使用的分词器
    CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder()
        .onMalformedInput(CodingErrorAction.REPORT)
        .onUnmappableCharacter(CodingErrorAction.REPORT);

    SynonymMap.Parser parser;
    Class clazz = loader.findClass(cname, SynonymMap.Parser.class);
    try {
      parser = clazz.getConstructor(boolean.class, boolean.class, Analyzer.class).newInstance(dedup, expand, analyzer);//这个是用来生成最终的SynonyMap,里面最关键的是一个FST和一个类似于HashMap的BytesRefHash.
    } catch (Exception e) {
      throw new RuntimeException(e);
    }

    List files = splitFileNames(synonyms);//可以传多个同义词词典文件
    for (String file : files) {
      decoder.reset();
      try (final Reader isr = new InputStreamReader(loader.openResource(file), decoder)) {//读取同义词词典文件
        parser.parse(isr);//解析,将同义词进入fst,最关键的就是这个方法。
      }
    }
    return parser.build();//建造一个SynonymMap
  }

 现在最关键的就是一个parser.parse方法了,这里的parser是SolrSynonymParser类,继承自Builder类,用于构造fst

  public void parse(Reader in) throws IOException, ParseException {
    LineNumberReader br = new LineNumberReader(in);//读取一行
    try {
      addInternal(br);//调用addInternal方法
    。。。//去掉无用代码
  }

  private void addInternal(BufferedReader in) throws IOException {
    String line = null;
    while ((line = in.readLine()) != null) {
      if (line.length() == 0 || line.charAt(0) == '#') {//注释的一行
        continue; // ignore empty lines and comments
      }
      
      // TODO: we could process this more efficiently.
      String sides[] = split(line, "=>");//根据=>分开,
      if (sides.length > 1) { // 如果当前根据=>之后的个数大于1,即aa=>bb格式的同义词词典
        if (sides.length != 2) {
          throw new IllegalArgumentException("more than one explicit mapping specified on the same line");
        }
        String inputStrings[] = split(sides[0], ",");//左边的部分,用,分开,
        CharsRef[] inputs = new CharsRef[inputStrings.length];
        for (int i = 0; i < inputs.length; i++) {
          inputs[i] = analyze(unescape(inputStrings[i]).trim(), new CharsRefBuilder());//对左边的部分使用置顶的分词器处理
        }
        //对右边的部分做和左边同样的操作
        String outputStrings[] = split(sides[1], ",");
        CharsRef[] outputs = new CharsRef[outputStrings.length];
        for (int i = 0; i < outputs.length; i++) {
          outputs[i] = analyze(unescape(outputStrings[i]).trim(), new CharsRefBuilder());
        }
        // these mappings are explicit and never preserve original
        for (int i = 0; i < inputs.length; i++) {//循环左边的词,每一个词都添加所有的右边的词作为同义词,即如果配置的是a=>b,c,添加到synonymMap的是a->b,a->c,但是不会添加b->c,b->a,c->a。
          for (int j = 0; j < outputs.length; j++) {
            add(inputs[i], outputs[j], false);
          }
        }
      } else {//这个是没有=>的形式,仅仅有a,b,c,这些组成同义词
        String inputStrings[] = split(line, ",");
        CharsRef[] inputs = new CharsRef[inputStrings.length];
        for (int i = 0; i < inputs.length; i++) {
          inputs[i] = analyze(unescape(inputStrings[i]).trim(), new CharsRefBuilder());
        }
        if (expand) {//通过if-else的对比expand的意思是要不要反方向的添加,比如a,b,c,如果不是expand,就会只记录b->a,c->a,不会记录a->b,a->c,b->c,c->b。
          // all pairs
          for (int i = 0; i < inputs.length; i++) {//这两个for循环,将形成所有的同义词对,比如上面的
            for (int j = 0; j < inputs.length; j++) {
              if (i != j) {
                add(inputs[i], inputs[j], true);//add是添加到BytesRefHash的数组里面返回在数组中的位置,并记录到fst中,这样从fst中根据term获得时先获得的是在bytesRefHash的位置(可以是多个),然后再根据位置获得同义词,
              }
            }
          }
        } else {
          // all subsequent inputs map to first one; we also add inputs[0] here
          // so that we "effectively" (because we remove the original input and
          // add back a synonym with the same text) change that token's type to
          // SYNONYM (matching legacy behavior):
          for (int i = 0; i < inputs.length; i++) {
            add(inputs[i], inputs[0], false);//如果不是expand,那么只会将同义词映射到第一个词上。
          }
        }
      }
    }
  }

至此已经搞懂了同义词的用法,不过对于fst还是没有涉及到,不过已经可以使用了 同义词了。还有问题,如何更新同义词词典呢,总不能要更新后重启吧,还有就是词典保留在solr中修改起来特别麻烦,如何能更加方便的动态修改词典呢,这个留在下一个博客中,只要稍微做些修改,就能实现动态的添加词典,动态的做同义词。

 

 

 

你可能感兴趣的:(同义词,solr,SynonymFilter)