我将在这里更详细地描述 SO 所做的事情:
虽然我并不真正了解 StackOverflow 的实现细节,但您在搜索“java”或“hibernate”时会注意到相同的行为,即使这些与标准分析器没有问题。它们将被转换为“[java]”和“[hibernate]”。这只是表示标签搜索。在搜索“lucene”或“junit”时不会发生这种情况,因此它可能与标签的受欢迎程度有关。我肯定会怀疑标签标题会以未经分析的形式编入索引。
举个有趣的例子,试试“j++”。这个死胡同的 java 实现只有 8 个问题,使用 SO 上的 j++ 标签,所以它不会触发自动标签搜索。搜索“[j++]”,您会看到那些 8。搜索“j++”,您将很难找到与该特定语言相关的任何内容,但您会找到很多引用 j。
接下来,解决您的问题:
是的,StandardAnalyzer 将(说得不准确,请参阅UAX-29 了解确切规则)摆脱所有标点符号。典型的方法是在查询时使用相同的分析器。如果您使用StandardAnalyzer 分析您的查询以及索引文档,您的搜索词将匹配,上面提到的两个查询词将减少为net 和c,您应该会得到结果。
但是现在,您可能遇到了StandardAnalyzer 问题的经典示例。这意味着c、c++ 和c# 在索引中都将完全相同地表示,如果不匹配其他两个,就无法搜索一个!
在我看来,有几种方法可以解决这个问题:
-
把婴儿和洗澡水一起扔出去:使用WhitespaceAnalyzer 或类似的东西,而失去StandardAnalyzer 为你提供帮助的所有美好、花哨的东西。
只处理那些小的边缘情况:好的,所以 Lucene 不喜欢标点符号,而且你有一些已知的术语对此有问题。幸运的是,你有String.Replace。将它们替换为对 lucene 更友好的东西,例如“c”、“cplusplus”和“csharp”。同样,确保它在查询和索引时间都完成。 问题是:由于您是在分析器之外执行此操作,因此转换也会影响字段的存储版本,迫使您在向用户显示结果之前反转它。
与 #2 一样,但更高级:所以 #2 可能工作正常,但您已经让这些分析器处理转换数据以供 lucene 使用,这只会影响字段的索引版本,而不是存储的版本。为什么不使用它们? Analyzer 有一个调用,initReader,您可以在其中将CharFilter 放在分析器堆栈的前面(参见the Analysis package documentation 底部的示例)。通过分析器运行的文本将由CharFilter 转换,然后StandardTokenizer(除去标点符号等)得到它的处理。例如MappingCharFilter。
但是,您不能子类化 StandardAnalyzer,您的想法是您应该实现 Analyzer,而不是子类化它的实现(请参阅 the discussion here,如果您有兴趣更完整地讨论思考过程那里)。因此,假设我们想确保我们在交易中获得绝对所有StandardAnalyzer 的功能,只需复制粘贴源代码,并添加对initReaders 方法的覆盖: p>
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 方法的覆盖中。