【问题标题】:Java Lucene search - is it possible to search a number in a range?Java Lucene 搜索 - 是否可以搜索范围内的数字?
【发布时间】:2025-12-13 15:15:01
【问题描述】:

使用 Lucene 库,我需要对现有的搜索功能进行一些更改: 让我们假设以下对象:

名称:“端口对象 1”

数据:“TCP (1)/1000-2000”

查询(或搜索文本)是“1142” 是否可以在数据字段中搜索“1142”并找到端口对象 1,因为它指的是 1000-2000 之间的范围?

我只设法找到了数字范围查询,但这不适用于这种情况,因为我不知道范围...

package com.company;

import java.io.IOException;

import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.queryparser.classic.ParseException;
import org.apache.lucene.queryparser.classic.QueryParser;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.Query;
import org.apache.lucene.search.ScoreDoc;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.RAMDirectory;

public class Main {
    public static void main(String[] args) throws IOException, ParseException {
        StandardAnalyzer analyzer = new StandardAnalyzer();

        // 1. create the index
        Directory index = new RAMDirectory();

        IndexWriterConfig config = new IndexWriterConfig(analyzer);

        IndexWriter w = new IndexWriter(index, config);
        addDoc(w, "TCP (6)/1100-2000", "193398817");
        addDoc(w, "TCP (6)/3000-4200", "55320055Z");
        addDoc(w, "UDP (12)/50000-65000", "55063554A");
        w.close();

        // 2. query
        String querystr = "1200";

        Query q = new QueryParser("title", analyzer).parse(querystr);

        // 3. search
        int hitsPerPage = 10;
        IndexReader reader = DirectoryReader.open(index);
        IndexSearcher searcher = new IndexSearcher(reader);
        TopDocs docs = searcher.search(q, hitsPerPage);
        ScoreDoc[] hits = docs.scoreDocs;

        // 4. display results
        System.out.println("Found " + hits.length + " hits.");
        for(int i=0;i<hits.length;++i) {
            int docId = hits[i].doc;
            Document d = searcher.doc(docId);
            System.out.println((i + 1) + ". " + d.get("isbn") + "\t" + d.get("title"));
        }

        reader.close();
    }

    private static void addDoc(IndexWriter w, String title, String isbn) throws IOException {
        Document doc = new Document();
        doc.add(new TextField("title", title, Field.Store.YES));

        doc.add(new StringField("isbn", isbn, Field.Store.YES));
        w.addDocument(doc);
    }
}

参考上面的代码。 查询“1200”应该找到第一个文档。

乐:

我认为我需要的与范围搜索完全相反: https://lucene.apache.org/core/5_5_0/queryparser/org/apache/lucene/queryparser/classic/package-summary.html#Range_Searches

【问题讨论】:

    标签: java search lucene lucene.net


    【解决方案1】:

    这是一种方法,但它需要您将范围数据解析为单独的值,然后您的数据才能被 Lucene 索引。所以,例如,从这个:

    "TCP (6)/1100-2000"
    

    您需要提取这两个值(例如,使用正则表达式):11002000

    带有 ContainsQuery 的 LongRange

    为每个文档添加一个新字段(例如,命名为“tcpRange”)并将其定义为LongRange 字段。

    (如果您不需要长值,还有IntRange。)

    long[] min = { 1100 };
    long[] max = { 2000 };
    Field tcpRange = new LongRange("tcpRange", min, max);
    

    值是在数组中定义的,因为这种范围类型可以在一个字段中处理多个范围。但在我们的例子中,我们只需要一个范围。

    然后您可以使用“contains”查询来搜索您的特定值,例如1200:

    long[] searchValue = { 1200 };
    Query containsQuery = LongRange.newContainsQuery("tcpRange", searchValue, searchValue);
    

    注意:我的示例基于最新版本的 Lucene (8.5)。我相信这也应该适用于其他早期版本。

    编辑

    关于 cmets 对此答案提出的其他问题...

    以下方法将 IPv4 地址转换为 long 值。使用它可以处理 IP 地址范围(并且可以使用与上面相同的 LongRange 方法):

    public long ipToLong(String ipAddress) {
        long result = 0;
        String[] ipAddressInArray = ipAddress.split("\\.");
        for (int i = 3; i >= 0; i--) {
            long ip = Long.parseLong(ipAddressInArray[3 - i]);
            // left shifting 24, 16, 8, 0 with bitwise OR
            result |= ip << (i * 8);
        }
        return result;
    }
    

    这也意味着不必处理有效的子网范围 - 任何两个 IP 地址都会生成一组连续的数字。

    感谢this mkyong site 的方法。

    【讨论】:

    • 我在小规模代码上尝试了您的建议,是的,它有效,这就是我一直在寻找的。但是,我正在处理的应用程序中的代码非常庞大,我不确定是否可以添加另一个字段。我明天得试试。
    • @CristianNicolaePerjescu 如果您不能创建新的文档字段,那么我不知道任何纯 Lucene 解决方案。也许是混合的,您将范围值与文档 ID 存储在其他地方(例如数据库),然后使用另一种机制(例如 SQL contains)执行该部分搜索。它是“混合”的,因为(例如)您使用 Lucene 使用已有的字段来获取一组初始匹配项,然后根据其他(SQL?)方法进一步过滤这些结果。反之亦然。
    • 我设法添加了另一个字段,它现在可以工作了。另外,你知道除了 IPv4 之外我怎么能做同样的搜索?如果我在“192.168.0.1-192.168.0.255”字符串中搜索“192.168.0.100”之类的内容?
    • 这将需要另一个新字段(字符串)作为 IP“前缀”(或者它可能是一个子网) - 在您的示例中为 192.168.0.。这必须是完全匹配的。紧随其后的是使用新的tcpRange 字段作为100 部分的范围查询。
    • 但你也可以有 192.168.0.1 - 192.168.100.0 之类的范围
    【解决方案2】:

    我设法添加了另一个字段,它现在可以工作了。另外,你知道除了 IPv4 之外我怎么能做同样的搜索?如果我在“192.168.0.1-192.168.0.255”字符串中搜索“192.168.0.100”之类的内容?

    嗨@CristianNicolaePerjescu 我不能评论因为我的声誉,但你可以创建一个扩展 Field 的类并将其添加到你的 lucene 索引中。例如:

    public class InetAddressRange extends Field {
      ...
    
      /**
       * Create a new InetAddressRange from min/max value
       * @param name field name. must not be null.
       * @param min range min value; defined as an {@code InetAddress}
       * @param max range max value; defined as an {@code InetAddress}
       */
      public InetAddressRange(String name, final InetAddress min, final InetAddress max) {
        super(name, TYPE);
        setRangeValues(min, max);
      }
    
      ...
    
    }
    

    然后添加到索引中:

    document.add(new InetAddressRange("field", InetAddressFrom, InetAddressTo));
    

    在您的班级中,您可以添加自己的查询格式,例如:

      public static Query newIntersectsQuery(String field, final InetAddress min, final InetAddress max) {
        return newRelationQuery(field, min, max, QueryType.INTERSECTS);
      }
    
      /** helper method for creating the desired relational query */
      private static Query newRelationQuery(String field, final InetAddress min, final InetAddress max, QueryType relation) {
        return new RangeFieldQuery(field, encode(min, max), 1, relation) {
          @Override
          protected String toString(byte[] ranges, int dimension) {
            return InetAddressRange.toString(ranges, dimension);
          }
        };
      }
    

    希望对你有帮助。

    【讨论】: