zoukankan      html  css  js  c++  java
  • Lucene4.X 高级应用

    Lucene 简介以及使用

    Lucene, 一个基于 Java 的开源的全文搜索工具包,可以方便的嵌入到各种应用系统中,实现针对应用的全文索引以及检索功能。目前是 Apache  jakarta 项目组的一个子项目,它的目的是为程序员提供工具包,让程序员利用工具包里的强大接口来完成全文检索。下面我们将以 Lucene4.7 版本为例,为您详细讲解索引的创建、创建时的参数配置、Lucene4.7 版本的各种 query 查询、Lucene 神器 Luke 的使用等内容。

    准备工作

    本文需要的 jar 包:

    lucene-analyzers-common-4.7.0.jar

    lucene-core-4.7.0.jar

    lucene-queryparser-4.7.0.jar

    Lucene 相关问题可以参考Lucene 的官方 API

    Lucene 常用包官方网站下载。

    重要关键字介绍

    IndexWriter:用于处理索引,如增加、更改或者删除索引。

    FSDirectory:索引目录,除此之外还有一个 Ramdirectry。FSDirectory 是将索引创建到磁盘里,而 Ramdirectry 是将索引创建到内存里。

    IndexWriterConfig:这里可配置版本号,分词器,打开模式等等,合理的应用该对象属性可以大大提高创建索引的性能。

    Document:文档,我们将每个字段放在 document 里。

    Field :域,类似数据库中的 column。

    Lucene 实战

    创建索引

    首 先我们介绍下如何创建索引。相关步骤分为:建立索引器 IndexWriter,建立文档对象 Document,建立信息字段对象 Field,将 Field 添加到 Document,将 Document 添加到 IndexWriter 里面,最后不要忘记关闭 IndexWriter。

    package lucene;
    ……
    public class IndexUtil {
    private String[] idArr = {"1","2","3","4","5","6"};
    private String[] emailArr = {"abc@us.ibm.com","ert@cn.ibm.com","lucy@us.ibm.com",
    "rock@cn.ibm.com","test@126.com","deploy@163.com"};
    private String[] contentArr = {
    "welcome to Lucene,I am abc","This is ert,I am from China",
    "I'm Lucy,I am english","I work in IBM",
    "I am a tester","I like Lucene in action"
    };
    
    private String[] nameArr = {"abc","ert","lucy","rock","test","deploy"};
    private Directory directory = null;
    public void index() {
    IndexWriter writer = null;
    try {
    directory = FSDirectory.open(new File("C:/lucene/index02"));
    IndexWriterConfig conf = new IndexWriterConfig(Version.LUCENE_47,
    new StandardAnalyzer(Version.LUCENE_47));
    conf.setOpenMode(OpenMode.CREATE_OR_APPEND);
    LogMergePolicy mergePolicy = new LogDocMergePolicy();
    mergePolicy.setMergeFactor(10);
    mergePolicy.setMaxMergeDocs(10);
    conf.setMaxBufferedDocs(10);
    writer = new IndexWriter(directory, conf);
    Document doc = null;
    int date = 1;
    for(int i=0;i<idArr.length;i++) {
    doc = new Document();
    doc.add(new StringField("id",idArr[i],Field.Store.YES));
    doc.add(new StringField("email",emailArr[i],Field.Store.YES));
    doc.add(new StringField("content",contentArr[i],Field.Store.YES));
    doc.add(new StringField("name",nameArr[i],Field.Store.YES));
    doc.add(new StringField("date","2014120"+date+“222222”,Field.Store.YES));
    writer.addDocument(doc);
    date++;
    }
    
    //新的版本对 Field 进行了更改,StringField 索引但是不分词、StoreField 至存储不索引、TextField 索引并分词
    } catch (CorruptIndexException e) {
    e.printStackTrace();
    } catch (LockObtainFailedException e) {
    e.printStackTrace();
    } catch (IOException e) {
    e.printStackTrace();
    } finally {
    try {
    if(writer!=null)writer.close();
    } catch (CorruptIndexException e) {
    e.printStackTrace();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }
    }
    
    public static void main(String args[]){
    IndexUtil indexUtil = new IndexUtil();
    indexUtil.index();
    }
    }

    参数解释:

    SetMergeFactor(合并因子),是控制 segment 合并频率的,其决定了一个索引块中包括多少个文档,当硬盘上的索引块达到这个值时,将它们合并成一个较大的索引块。当 MergeFactor 值较大时,生成索引的速度较快。MergeFactor 的默认值是 10。

    SetMaxMergeDocs 最大合并文档数,默认是 Integer.MAX_VALUE。设置 segment 最大合并文档 (Document) 数值较小越有利于追加索引的速度,值较大, 越适合批量建立索引和更快的搜索。

    setMaxBufferedDocs 最大缓存文档数,是控制写入一个新的 segment 前内存中保存的 document 的数目,设置较大的数目可以加快建索引速度,默认为 10。

    在创建创 IndexWriter 实例的时候应注意以下几个地方:

    1. 尽量保持 IndexWriter 在全局中只有一个实例,因为一个 directory 中只允许一个 IndexWriter 实例访问,如果两个或者两个以上的实例同时访问一个 directory 会出现 Lock obtain timed Out 异常, 在文件夹里会出现一个 write.lock 文件。
    2. 在配置 LogMergePolicy 的时候不要盲目的去设置,要根据物理机器的配置来进行次测试,来达到一个理想的配置

    查询索引

    当我们创建好索引后,就可以利用 Lucene 进行索引查询,Lucene 提供了多个查询功能,下面我们进行简单介绍。

    Query :一个查询的抽象类,有多个子类实现,TermQuery, BooleanQuery, PrefixQuery ,WildcardQuery 等。

    Term :是搜索的基本单位,一个 Term 是由两个 String 的 field 组成。比如,Term("name",“rock”), 此时该语句是查询 name 为 rock 的条件。

    IndexSearcher:当索引建立好后,用该对象进行查询。该对象只能以只读的方式打开索引,所以多个 IndexSearcher 对象可以查询一个索引目录。我们要注意一下这个现象。

    在介绍几种查询方式之前,首先要初始化 directory:

    directory = FSDirectory.open(new File("C:/lucene/index02"));

    其次获取 IndexSearcher:

    public IndexSearcher getSearcher() {
    	IndexReader reader = null;
    	try {
    reader = DirectoryReader.open(directory);
    		IndexSearcher searcher = new IndexSearcher(reader);
    		return searcher;
    	} catch (IOException e) {
    e.printStackTrace();
    	}
    	return null;
    	}
    	public void searchByTerm(String field, String name, int num) {
    	try {
    		IndexSearcher searcher = getSearcher();
    		Query query = new TermQuery(new Term(field, name));
    		TopDocs tds = searcher.search(query, num);
    System.out.println("count:" + tds.totalHits);
    		for (ScoreDoc sd : tds.scoreDocs) {
    			Document doc = searcher.doc(sd.doc);
    			System.out.println("docId:"+doc.get("id"));
    			System.out.println("name:"+doc.get("name"));
    			System.out.println("email:"+doc.get("email"));
    			System.out.println("date:"+doc.get("date"));
    		}
    		} catch (CorruptIndexException e) {
    			e.printStackTrace();
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    	}

    结果:

    count:1

    docId:4

    name:rock

    email:rock@cn.ibm.com

    date:2014-12-4

    说明:

    TermQuery 是 Lucene 查询中最基本的一种查询,它只能针对一个字段进行查询。

    清单 3. 范围查询 RangeQuery (搜索指定范围的数据)
    public void searchByTermRange(String field,String start,String end,int num) {
    try {
    IndexSearcher searcher = getSearcher();
    BytesRef lowerTerm = new BytesRef(start);
    BytesRef upperTerm = new BytesRef(end);
    Query query = new TermRangeQuery(field,lowerTerm,upperTerm,true, true);
    TopDocs tds = searcher.search(query, num);
    System.out.println("count:"+tds.totalHits);
    for(ScoreDoc sd:tds.scoreDocs) {
    Document doc = searcher.doc(sd.doc);
    System.out.println("docId:"+doc.get("id"));
    System.out.println("name:"+doc.get("name"));
    System.out.println("email:"+doc.get("email"));
    System.out.println("date:"+doc.get("date"));
    }
    } catch (CorruptIndexException e) {
    e.printStackTrace();
    } catch (IOException e) {
    e.printStackTrace();
    }
    }

    结果:

    count:3

    docId:1

    name:abc

    email:abc@us.ibm.com

    date:20141201222222

    docId:2

    name:ert

    email:ert@cn.ibm.com

    date:20141202222222

    docId:3

    name:lucy

    email:lucy@us.ibm.com

    date:20141203222222

    说明:

    TermRangeQuery query=new TermRangeQuery(字段名, 起始值, 终止值, 起始值是否包含边界, 终止值是否包含边界)。

    //查询以 ro 开头的 name
    public void searchByPrefix(String field, String value, int num) {
    	try {
    		IndexSearcher searcher = getSearcher();
    		Query query = new PrefixQuery(new Term(field, value));
    		TopDocs tds = searcher.search(query, num);
    System.out.println("count:" + tds.totalHits);
    		for (ScoreDoc sd : tds.scoreDocs) {
    			Document doc = searcher.doc(sd.doc);
    System.out.println("docId:"+doc.get("id"));
    System.out.println("name:"+doc.get("name"));
    System.out.println("email:"+doc.get("email"));
    System.out.println("date:"+doc.get("date"));
    		}
    } catch (CorruptIndexException e) {
    			e.printStackTrace();
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    	}

    结果:

    count:1

    docId:4

    name:rock

    email:rock@cn.ibm.com

    date:20141204222222

    说明:

    前缀查询, 搜索匹配开始位置的数据类似百度的输入框。

    //查询 email 是 test 的
    public void searchByWildcard(String field, String value, int num) {
    		try {
    			IndexSearcher searcher = getSearcher();
    			Query query = new WildcardQuery(new Term(field, value));
    			TopDocs tds = searcher.search(query, num);
    System.out.println("count" + tds.totalHits);
    			for (ScoreDoc sd : tds.scoreDocs) {
    				Document doc = searcher.doc(sd.doc);
    System.out.println("docId:"+doc.get("id"));
    System.out.println("name:"+doc.get("name"));
    System.out.println("email:"+doc.get("email"));
    System.out.println("date:"+doc.get("date"));
    			}
    		} catch (CorruptIndexException e) {
    			e.printStackTrace();
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    	}

    结果:

    count1

    docId:5

    name:test

    email:test@126.com

    date:20141205222222

    说明:

    通配符分为两种,“*”和“?”,“*”表示任何字符,“?”表示任意一个字符。

    Term term=new Term(字段名, 搜索关键字+通配符)。

    public void searchByFuzzy(int num) {
    	try {
    		IndexSearcher searcher = getSearcher();
    		FuzzyQuery query = new FuzzyQuery(new Term("name","acc"),1,1);
    		//System.out.println(query.getPrefixLength());
    TopDocs tds = searcher.search(query, num);
    System.out.println("count:"+tds.totalHits);
    		for(ScoreDoc sd:tds.scoreDocs) {
    			Document doc = searcher.doc(sd.doc);
    System.out.println("docId:"+doc.get("id"));
    System.out.println("name:"+doc.get("name"));
    System.out.println("email:"+doc.get("email"));
    System.out.println("date:"+doc.get("date"));
    		}
    		} catch (CorruptIndexException e) {
    			e.printStackTrace();
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    	}

    结果:

    count:1

    docId:1

    name:abc

    email:abc@us.ibm.com

    date:20141201222222

    说明

    FuzzyQuery(new Term("name","acc"),1,1),需要 3 个参数,第一个参数是词条对象,第二个参数是 levenshtein 算法的最小相似度,第三个参数是指与多少个前缀字符匹配。

    public void searchByBoolean(int num) {
    	try {
    IndexSearcher searcher = getSearcher();
    		BooleanQuery query = new BooleanQuery();
    	query.add(new TermQuery(new Term("name", "abc")),BooleanClause.Occur.SHOULD);
     query.add(new TermQuery(new Term("email","lucy@us.ibm.com")), BooleanClause.Occur.SHOULD);
    		TopDocs tds = searcher.search(query, num);
    		System.out.println("count" + tds.totalHits);
    		for (ScoreDoc sd : tds.scoreDocs) {
    			Document doc = searcher.doc(sd.doc);
    			System.out.println("docId:"+doc.get("id"));
    System.out.println("name:"+doc.get("name"));
    			System.out.println("email:"+doc.get("email"));
    			System.out.println("date:"+doc.get("date"));
    		}
    		} catch (CorruptIndexException e) {
    			e.printStackTrace();
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    }
    结果:

    count2

    docId:1

    name:abc

    email:abc@us.ibm.com

    date:20141201222222

    docId:3

    name:lucy

    email:lucy@us.ibm.com

    date:20141203222222

    说明:

    BooleanQuery,也就是组合查询,允许进行逻辑 AND、OR 或 NOT 的组合,通过 BooleanQuery 的 add 方法将一个查询子句增加到某个 BooleanQuery 对象中。

    BooleanClause.Occur.MUST:必须包含,相当于逻辑运算的与

    BooleanClause.Occur.MUST_NOT:必须不包含,相当于逻辑运算的非

    BooleanClause.Occur.SHOULD:可以包含,相当于逻辑运算的或

    	private static void testPageSearch1(int currentPage) {
    	int PAGE_SIZE = 10;
    	IndexReader reader = null;
    	try {
    	reader = DirectoryReader.open(FSDirectory.open(new File("")));
    	IndexSearcher searcher = new IndexSearcher(reader);
    	Query query = new TermQuery(new Term("name", "rock"));
    	TopDocs topDocs = searcher.search(query, currentPage * PAGE_SIZE);
    	ScoreDoc[] hits = topDocs.scoreDocs;
    	int endNuM = Math.min(topDocs.totalHits, currentPage * PAGE_SIZE);
    
    	for (int i = (currentPage - 1) * PAGE_SIZE; i < endNuM; i++) {
    			Document doc = searcher.doc(hits[i].doc);
    			System.out.print(doc.get("USERNAME"));
    		}
    	} catch (IOException e) {
    		e.printStackTrace();
    	}
    }
    //在 Lucene 的 3.5 以后的版本,Lucene 的 API 里提供了一个分页方法 searchafter。
    private ScoreDoc getLastScoreDoc(int pageIndex, int pageSize, Query query,
    			IndexSearcher searcher) throws IOException {
    		if (pageIndex == 1)
    			return null;
    		int num = pageSize * (pageIndex - 1);
    		TopDocs tds = searcher.search(query, num);
    		return tds.scoreDocs[num - 1];
    	}
    
    public void searchPageByAfter(String query, int pageIndex, int pageSize) {
    	try {
    		IndexSearcher searcher = getSearcher();
    		QueryParser parser = new QueryParser(Version.LUCENE_47, 
    		"content",new StandardAnalyzer(Version.LUCENE_47));
    		Query q = parser.parse(query);
    		ScoreDoc lastSd = getLastScoreDoc(pageIndex, pageSize, q, searcher);
    		TopDocs tds = searcher.searchAfter(lastSd, q, pageSize);
    		for (ScoreDoc sd : tds.scoreDocs) {
    			Document doc = searcher.doc(sd.doc);
    			System.out.println("docId:"+doc.get("id") + ",name:" +
    			doc.get("name")+",email:"+ doc.get("email") );
    		}
    		} catch (IOException e) {
    			e.printStackTrace();
    		} catch (org.apache.lucene.queryparser.classic.ParseException e) {
    			e.printStackTrace();
    		}
    	}

    排序

    Lucene 除了提供大量的查询功能外,还提供了一个可改变查询结果顺序的类 Sort,用户可根据自己的需求进行 Sort 排序设置。

    	Sort sort = new Sort();
    ortField sf=new SortField("name",Type.STRING_VAL, false);

    以 上语句表示根据 name 进行排序,false 代表升序,如果是 true 代表降序,可以有多个 SortField,利用 Sort 的 sort.setSort(sf,sf1...) 将每个 SortField 添加到 sort 中,最后返回按 sort 进行排序的搜索结果。

    TopDocs topDocs = searcher.search(query, currentPage * PAGE_SIZE,sort);
  • 相关阅读:
    MAC上Vue的一些安装及配置
    MySQL
    git
    win7系统的用户怎么去掉用户账户控制?
    JS
    IDEA使用总结
    Mybatis
    codeforces cf educatonal round 57(div2) D. Easy Problem
    codeforces round#509(div2) E. Tree Reconstruction
    codeforces round#509(div2) D. Glider
  • 原文地址:https://www.cnblogs.com/1130136248wlxk/p/5034517.html
Copyright © 2011-2022 走看看