搜索引擎Lucene技术

搜索引擎Lucene技术

  • Lucene 这个开源项目,使得 Java开发人员可以很方便地得到像搜索引擎google baidu那样的搜索效果。
1.1 Lucene入门

参照连接 https://blog.csdn.net/forfuture1978/article/details/4711308

1.1.1 数据添加到索引

例:

private static Directory createIndex(IKAnalyzer analyzer, List<Search> products) throws IOException
{
    //创建内存索引
    Directory index = new RAMDirectory();
    //根据中文分词器创建配置对象
    IndexWriterConfig config = new IndexWriterConfig(analyzer);
    //创建索引 writer
    IndexWriter writer = new IndexWriter(index, config);
    //遍历数据,把他们挨个放进索引里
    for (Search search : products) {
        addDoc(writer, search.getIntroduce());
    }
    writer.close();
    return index;
}
1.1.2 创建文档对象

文档(Document) : 获取原始内容的目的是为了索引,在索引前需要将原始内容创建成文档(Document),文档中包括一个一个的域(Field),域中存储内容。

private static void addDoc(IndexWriter w, String name) throws IOException
{
		//创建文档对象
    Document doc = new Document();
    //将数据添加到文档中 
    doc.add(new TextField("name", name, Field.Store.YES));
    w.addDocument(doc);
}
1.1.3 结果处理
private static void showSearchResults(IndexSearcher searcher, ScoreDoc[] hits, Query query, IKAnalyzer analyzer) throws Exception
{
    System.out.println("找到 " + hits.length + " 个命中.");
    System.out.println("序号\t匹配度得分\t结果");
    //每一个ScoreDoc[] hits 就是一个搜索结果,首先把他遍历出来
    for (int i = 0; i < hits.length; ++i) {
        ScoreDoc scoreDoc = hits[i];
         //然后获取当前结果的docid, 这个docid相当于就是这个数据在索引中的主键
         int docId = scoreDoc.doc;
         //再根据主键docid,通过搜索器从索引里把对应的Document取出来
         Document d = searcher.doc(docId);
         List<IndexableField> fields = d.getFields();
         System.out.print((i + 1));
         System.out.print("\t" + scoreDoc.score);
         for (IndexableField f : fields) {
              System.out.print("\t" + d.get(f.name()));
          }
         System.out.println();
     }
}
1.1.4 分词组件(Tokenizer)

分词组件(Tokenizer)会做以下几件事情( 此过程称为Tokenize) :

  1. 将文档分成一个一个单独的单词。
  2. 去除标点符号。
  3. 去除停词(Stop word) 。
    所谓停词(Stop word)就是一种语言中最普通的一些单词,由于没有特别的意义,因而大多数情况下不能成为搜索的关键词,因而创建索引时,这种词会被去掉而减少索引的大小。
    英语中挺词(Stop word)如:“the”,“a”,“this”等。
    对于每一种语言的分词组件(Tokenizer),都有一个停词(stop word)集合。
    经过分词(Tokenizer) 后得到的结果称为词元(Token)
1.1. 5 测试
@Test
void testOne() throws Exception
{
    //1,准备中文分词组件
    IKAnalyzer analyzer = new IKAnalyzer();
    //获取数据
    List<Search> searches = searchService.queryAllByLimit(0, 2000);
    //2,创建内存索引 并将数据添加到索引中
    Directory index = createIndex(analyzer, searches);
    // 查询关键字
    String keyword = "护眼";
  	//3,查询器
    Query query = new QueryParser("name", analyzer).parse(keyword);
    //4,创建索引 reader
    IndexReader reader = DirectoryReader.open(index);
    //基于 reader 创建搜索器
    IndexSearcher searcher = new IndexSearcher(reader);
    //指定每页显示多少条数据
    int numPerPage = 10;
    System.out.println("当前一共" + searches.size() + "条数据");
    System.out.println("查询关键字:" + keyword);
    //执行搜索
    ScoreDoc[] hits = searcher.search(query, numPerPage).scoreDocs;
    //5,显示结果
    showSearchResults(searcher, hits, query, analyzer);
    //6,关闭查询
    reader.close();
}
1.2 思路
  1. 首先搜集数据
    数据可以是文件系统,数据库,网络上,手工输入的,或者像本例直接写在内存上的
  2. 通过数据创建索引
  3. 用户输入关键字
  4. 通过关键字创建查询器
  5. 根据查询器到索引里获取数据
  6. 然后把查询结果展示在用户面前
1.3 和like的区别
  1. 相关度
    通过观察运行结果,可以看到不同相关度的结果都会查询出来,但是使用 like,就做不到这一点
  2. 性能
    数据量小的时候,like 也会有很好的表现,但是数据量一大,like 的表现就差很多。
1.4 分词器概念

分词器指的是搜索引擎如何使用关键字进行匹配,
如:输入搜索的关键字:“护眼带光源”。 如果使用like,那么**“护眼带光源”,匹配出来的结果要么是完全匹配**,要么是完全不匹配
而使用分词器,会把这个关键字分为 “护眼”“带”“光源” 3个关键字,这样就可以找到不同相关程度的结果了
例:

@Test
void testTwo() throws Exception
{
    IKAnalyzer analyzer = new IKAnalyzer();
    TokenStream ts = analyzer.tokenStream("name", "护眼带光源");
    ts.reset();
    while (ts.incrementToken()) {
        System.out.println(ts.reflectAsString(false));
    }
    /* 输出结果
    加载扩展词典:ext.dic
    加载扩展停止词典:stopword.dic
    term=护眼,bytes=......
    term=带,bytes=......
    term=光源,bytes=......
     */
}
1.5 分页查询
  • 假设要查询第10页,每页10条数据。
  • Lucene 分页通常来讲有两种方式:
    • 第一种是把100条数据查出来,然后取最后10条。 优点是快,缺点是对内存消耗大。
    • 第二种是把第90条查询出来,然后基于这一条,通过searchAfter方法查询10条数据。 优点是内存消耗小,缺点是比第一种更慢
1.5.1 第一种方式

例:

/**
 * 分页查询(方式一)
 * @param query
 * @param searcher
 * @param pageNow  第几页
 * @param pageSize 每页显示记录数
 * @return
 * @throws IOException
 */
 private static ScoreDoc[] pageSearch1(Query query, IndexSearcher searcher, int pageNow, int pageSize)throws IOException
 {
     TopDocs topDocs = searcher.search(query, pageNow * pageSize);
     System.out.println("查询到的总条数\t" + topDocs.totalHits);
     ScoreDoc[] alllScores = topDocs.scoreDocs;
     List<ScoreDoc> hitScores = new ArrayList<>();
     int start = (pageNow - 1) * pageSize;
     int end = pageSize * pageNow;
     for (int i = start; i < end; i++) {
         hitScores.add(alllScores[i]);
     }
     ScoreDoc[] hits = hitScores.toArray(new ScoreDoc[]{ });
     return hits;
 }
1.5.1 第二种方式

例:

/**
 * 分页查询(方式二)
 * @param query
 * @param searcher
 * @param pageNow  第几页
 * @param pageSize 每页显示的记录数
 * @return
 * @throws IOException
 */
private static ScoreDoc[] pageSearch2(Query query, IndexSearcher searcher, int pageNow, int pageSize)throws IOException
{
    //计算起始记录下标公式
    int start = (pageNow - 1) * pageSize;
    if (0 == start) {
        TopDocs topDocs = searcher.search(query, pageNow * pageSize);
        return topDocs.scoreDocs;
    }
    // 查询数据, 结束页面之前的数据都会查询到,但是只取本页的数据
    TopDocs topDocs = searcher.search(query, start);
    //获取到上一页最后一条
    ScoreDoc preScore = topDocs.scoreDocs[start - 1];
    //查询最后一条后的数据的一页数据
    topDocs = searcher.searchAfter(preScore, query, pageSize);
    return topDocs.scoreDocs;
}
Test测试
/**
 * 测试分页查询
 */
@Test
void testThree() throws Exception
{
    //1,准备中文分词器
    IKAnalyzer analyzer = new IKAnalyzer();
    //2,创建索引
    List<Search> searches = searchService.queryAllByLimit(0, 2000);
    Directory index = createIndex(analyzer, searches);
    //3,查询器
    String keyword = "护眼";
    Query query = new QueryParser("name", analyzer).parse(keyword);
    //4,创建索引 reader
    IndexReader reader = DirectoryReader.open(index);
    //基于 reader 创建搜索器
    IndexSearcher searcher = new IndexSearcher(reader);
    //指定每页显示多少条数据
    int numPerPage = 10;
    System.out.println("当前一共" + searches.size() + "条数据");
    System.out.println("查询关键字:" + keyword);
    // 分页查询方式一
    //ScoreDoc[] hits = pageSearch1(query, searcher, 2, 5);
    //分页查询方式二
    ScoreDoc[] hits = pageSearch2(query, searcher, 3, 5);
    //5,显示结果
    showSearchResults(searcher, hits, query, analyzer);
    //6,关闭查询
    reader.close();
}
1.6 索引删除和更新
  • 索引里的数据,其实就是一个一个的Document 对象

    删除索引API:

    DeleteDocuments(Query query) //根据Query条件来删除单个或多个Document
    DeleteDocuments(Query[] queries) //根据Query条件来删除单个或多个Document
    DeleteDocuments(Term term) //根据Term来删除单个或多个Document
    DeleteDocuments(Term[] terms) //根据Term来删除单个或多个Document
    DeleteAll() //删除所有的Document

测试:

@Test
void testFour() throws Exception
{
    //1,准备中文分词器
    IKAnalyzer analyzer = new IKAnalyzer();
    //2,创建索引
    List<Search> searches = searchService.queryAll();
    Directory index = createIndex(analyzer, searches);
    //3,删除索引
    IndexWriterConfig config = new IndexWriterConfig(analyzer);
    IndexWriter indexWriter = new IndexWriter(index, config);
    /*
     根据Term来删除单个或多个Document
   	 删除id=5205的索引
    */
    indexWriter.deleteDocuments(new Term("id", "5205"));
    indexWriter.commit();
    indexWriter.close();
    //删除之后再次查询
    String keyword = "光";
    // 4,查询
    Query query = new QueryParser("name", analyzer).parse(keyword);
    //5,创建索引 reader
    IndexReader reader = DirectoryReader.open(index);
    //基于 reader 创建搜索器
    IndexSearcher searcher = new IndexSearcher(reader);
    //指定每页显示多少条数据
    int numPerPage = 20;
    System.out.println("当前一共" + searches.size() + "条数据");
    System.out.println("查询关键字:" + keyword);
    //执行搜索
    ScoreDoc[] hits = searcher.search(query, numPerPage).scoreDocs;
    //6,显示结果
    showSearchResults(searcher, hits, query, analyzer);
    //7,关闭查询
    reader.close();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值