18

我正在尝试使用 Lucene Java 2.3.2 来实现对产品目录的搜索。除了产品的常规字段外,还有一个名为“类别”的字段。一个产品可以属于多个类别。目前,我使用 FilteredQuery 为每个类别搜索相同的搜索词,以获取每个类别的结果数。

这会导致每个查询进行 20-30 次内部搜索调用以显示结果。这大大减慢了搜索速度。有没有更快的方法使用 Lucene 实现相同的结果?

4

5 回答 5

9

这就是我所做的,虽然它对记忆有点沉重:

你需要的是预先创建一堆BitSets,每个类别一个,包含一个类别中所有文档的doc id。现在,在搜索时,您使用HitCollector并根据 BitSet 检查文档 ID。

这是创建位集的代码:

public BitSet[] getBitSets(IndexSearcher indexSearcher, 
                           Category[] categories) {
    BitSet[] bitSets = new BitSet[categories.length];
    for(int i=0; i<categories.length; i++)
    {
        Query query = categories[i].getQuery();
        final BitSet bitset = new BitSet()
        indexSearcher.search(query, new HitCollector() {
            public void collect(int doc, float score) {
                bitSet.set(doc);
            }
        });
        bitSets[i] = bitSet;
    }
    return bitSets;
}

这只是执行此操作的一种方法。如果您的类别足够简单,您可能可以使用TermDocs而不是运行完整搜索,但这应该只在您加载索引时运行一次。

现在,当需要计算搜索结果的类别时,您可以这样做:

public int[] getCategroryCount(IndexSearcher indexSearcher, 
                               Query query, 
                               final BitSet[] bitSets) {
    final int[] count = new int[bitSets.length];
    indexSearcher.search(query, new HitCollector() {
        public void collect(int doc, float score) {
            for(int i=0; i<bitSets.length; i++) {
                if(bitSets[i].get(doc)) count[i]++;
            }
        }
    });
    return count;
}

您最终得到的是一个数组,其中包含搜索结果中每个类别的计数。如果您还需要搜索结果,则应将 TopDocCollector 添加到您的命中收集器(yo dawg ...)。或者,您可以再次运行搜索。2 次搜索优于 30 次。

于 2009-01-27T08:30:42.867 回答
8

我没有足够的声誉来评论(!),但在马特奎尔的回答中,我很确定你可以替换这个:

int numDocs = 0;
td.seek(terms);
while (td.next()) {
    numDocs++;
}

有了这个:

int numDocs = terms.docFreq()

然后完全摆脱 td 变量。这应该使它更快。

于 2008-10-01T18:11:52.327 回答
2

您可能需要考虑使用TermDocs 迭代器查看与类别匹配的所有文档。

此示例代码遍历每个“类别”术语,然后计算与该术语匹配的文档数。

public static void countDocumentsInCategories(IndexReader reader) throws IOException {
    TermEnum terms = null;
    TermDocs td = null;


    try {
        terms = reader.terms(new Term("Category", ""));
        td = reader.termDocs();
        do {
            Term currentTerm = terms.term();

            if (!currentTerm.field().equals("Category")) {
                break;
            }

            int numDocs = 0;
            td.seek(terms);
            while (td.next()) {
                numDocs++;
            }

            System.out.println(currentTerm.field() + " : " + currentTerm.text() + " --> " + numDocs);
        } while (terms.next());
    } finally {
        if (td != null) td.close();
        if (terms != null) terms.close();
    }
}

即使对于大型索引,此代码也应该运行得相当快。

下面是一些测试该方法的代码:

public static void main(String[] args) throws Exception {
    RAMDirectory store = new RAMDirectory();

    IndexWriter w = new IndexWriter(store, new StandardAnalyzer());
    addDocument(w, 1, "Apple", "fruit", "computer");
    addDocument(w, 2, "Orange", "fruit", "colour");
    addDocument(w, 3, "Dell", "computer");
    addDocument(w, 4, "Cumquat", "fruit");
    w.close();

    IndexReader r = IndexReader.open(store);
    countDocumentsInCategories(r);
    r.close();
}

private static void addDocument(IndexWriter w, int id, String name, String... categories) throws IOException {
    Document d = new Document();
    d.add(new Field("ID", String.valueOf(id), Field.Store.YES, Field.Index.UN_TOKENIZED));
    d.add(new Field("Name", name, Field.Store.NO, Field.Index.UN_TOKENIZED));

    for (String category : categories) {
        d.add(new Field("Category", category, Field.Store.NO, Field.Index.UN_TOKENIZED));
    }

    w.addDocument(d);
}
于 2008-09-30T12:09:27.137 回答
2

Sachin,我相信你想要多面搜索。它不是开箱即用的 Lucene。我建议您尝试使用SOLR,它具有刻面作为主要且方便的功能。

于 2009-04-12T10:12:01.547 回答
0

所以让我看看我是否正确理解了这个问题:给定用户的查询,您想显示每个类别中的查询有多少匹配项。正确的?

可以这样想:您的查询实际上是originalQuery AND (category1 OR category2 or ...)除了您想要为每个类别获得一个数字的总体得分。不幸的是,在 Lucene 中收集命中的界面非常狭窄,只能为您提供查询的总分。但是您可以实现自定义记分器/收集器。

查看 org.apache.lucene.search.DisjunctionSumScorer 的源代码。您可以复制其中的一些内容来编写一个自定义记分器,该记分器在您的主要搜索进行时迭代类别匹配。您可以保留 aMap<String,Long>以跟踪每个类别中的匹配项。

于 2008-12-24T19:32:50.653 回答