哪个Lucene SearchAnalyzer将用于特殊字符搜索

本文关键字:特殊字符 搜索 用于 Lucene SearchAnalyzer 哪个 | 更新日期: 2023-09-27 17:59:07

我在ASP.net项目搜索中使用Lucene.net Standard Analyzer。但该搜索不会返回C#、.NET等关键字的结果。但如果我键入C或NET(删除.和#),它就可以了。在Stackoverflow(它也使用Lucene)上,我注意到当我键入.NET时,它在搜索时将其更改为[.NET],我得到的链接说Standard Analyzer无法处理特殊字符搜索,而White Space Analyzer将不适用于我们,因为它不会给出预期的结果。有人能帮助SO管理搜索吗?

哪个Lucene SearchAnalyzer将用于特殊字符搜索

我将在这里更详细地描述SO正在做的事情:

虽然我并不真正了解StackOverflow的实现细节,但当搜索"java"或"hibernate"时,您会注意到相同的行为,即使这些行为与标准分析器没有问题。它们将被转换为"[java]"answers"[hibernate]"。这只是表示一个标签搜索。在搜索"lucene"或"junit"的地方不会发生这种情况,所以这可能与标签的流行有关。我肯定会怀疑标签标题会以未经分析的形式进行索引

举个有趣的例子,试试"j++"。这个死胡同的java实现在SO上使用j++标记只有8个问题,所以它不会触发自动标记搜索。搜索"[j++]",你会看到这8个。搜索"j++",你会很难找到与该特定语言相关的任何内容,但你会发现很多引用j.的内容

继续,解决您的问题:

是的,StandardAnalyzer(说得不准确,具体规则请参阅UAX-29)会去掉你所有的标点符号。对此,典型的方法是在查询时使用相同的分析器。如果你使用StandardAnalyzer来分析你的查询以及索引文档,你搜索到的术语会匹配,上面提到的两个查询术语会减少到netc,你应该会得到结果。

但现在,您可能遇到了StandardAnalyzer问题的经典示例。这意味着cc++c#将在索引中完全相同,如果不匹配其他两个,就无法搜索其中一个!

在我看来,有几种方法可以解决这个问题:

  1. 把婴儿和洗澡水一起扔出去:使用WhitespaceAnalyzer或类似的东西,就会失去StandardAnalyzer为帮助你而做的所有美好、花哨的事情。

  2. 只需处理这些小的边缘案例:好吧,Lucene不喜欢标点符号,而且你有一些已知的术语对此有问题。幸运的是,您有String.Replace。将它们替换为更友好的lucene,如"c"、"cplusplus"answers"csharp"。同样,请确保在查询和索引时都能完成问题是:由于您在分析器之外执行此操作,转换也会影响字段的存储版本,迫使您在向用户显示结果之前将其反转。

  3. 做与#2相同的事情,但有点花哨:所以#2可能可以正常工作,但您已经有了这些分析器来处理转换数据以供lucene使用,这只会影响字段的索引版本,而不会影响存储版本。为什么不使用它们?Analyzer有一个调用initReader,在该调用中,您可以在分析器堆栈的前面加上一个CharFilter(请参阅Analysis包文档底部的示例)。通过分析器运行的文本将在StandardTokenizer(除其他外,它去掉了标点符号)得到它的手之前由CharFilter进行转换。例如,MappingCharFilter

不过,您不能对StandardAnalyzer进行子类化,因为您的想法是应该实现Analyzer,而不是对其实现进行子类划分(如果您有兴趣在这里对思想过程进行更完整的讨论,请参阅此处的讨论)。因此,假设我们想确保在交易中获得StandardAnalyzer的所有功能,只需复制粘贴源代码,并添加initReaders方法的覆盖:

public class ExtraFancyStandardAnalyzer extends StopwordAnalyzerBase {
    public static final int DEFAULT_MAX_TOKEN_LENGTH = 255;
    private int maxTokenLength = DEFAULT_MAX_TOKEN_LENGTH;
    public static final CharArraySet STOP_WORDS_SET = StopAnalyzer.ENGLISH_STOP_WORDS_SET;
    public ExtraFancyStandardAnalyzer(Version matchVersion,
            CharArraySet stopWords) {
        super(matchVersion, stopWords);
        buildMap();
    }
    public ExtraFancyStandardAnalyzer(Version matchVersion) {
        this(matchVersion, STOP_WORDS_SET);
    }
    public ExtraFancyStandardAnalyzer(Version matchVersion, Reader stopwords)
            throws IOException {
        this(matchVersion, loadStopwordSet(stopwords, matchVersion));
    }
    public void setMaxTokenLength(int length) {
        maxTokenLength = length;
    }
    public int getMaxTokenLength() {
        return maxTokenLength;
    }

    // The following two methods, and a call to buildMap() in the ctor
    // are the only things changed from StandardAnalyzer
    private NormalizeCharMap map;
    public void buildMap() {
        NormalizeCharMap.Builder builder = new NormalizeCharMap.Builder();
        builder.add("c++", "cplusplus");
        builder.add("c#", "csharp");
        map = builder.build();
    }
    @Override
    protected Reader initReader(String fieldName, Reader reader) {
        return new MappingCharFilter(map, reader);
    }
    @Override
    protected TokenStreamComponents createComponents(final String fieldName,
            final Reader reader) {
        final StandardTokenizer src = new StandardTokenizer(matchVersion,
                reader);
        src.setMaxTokenLength(maxTokenLength);
        TokenStream tok = new StandardFilter(matchVersion, src);
        tok = new LowerCaseFilter(matchVersion, tok);
        tok = new StopFilter(matchVersion, tok, stopwords);
        return new TokenStreamComponents(src, tok) {
            @Override
            protected void setReader(final Reader reader) throws IOException {
                src.setMaxTokenLength(ExtraFancyStandardAnalyzer.this.maxTokenLength);
                super.setReader(reader);
            }
        };
    }
}

注意:这是用Java Lucene 4.7版编写和测试的。C#的实现不应该有太大的不同。复制StandardAnalyzer,构建一个MappingCharFilter(实际上在3.0.3版本中处理起来稍微简单一点),并在initReader方法的覆盖中用它包裹读取器。