Lucene是一个开源的搜索引擎, 或者叫全文索引工具, 用于快速查找, 而且可以获得一个匹配度得分;
比如我们直接使用sql进行搜索的时候, 可能会使用Like关键词
但是这样有两个不便之处, 一是当数据太多时, 会比较慢, 二是这样无法得到一些基于关键词的匹配相似度得分;
用lucene可以实现上面两点;
那么直接开始做一个简单的搜索的demo吧~
一.创建索引
假设现在已经有了一些数据, 放在了一个list里面:
List<String> productNames &#61; new ArrayList<>();
productNames.add("飞利浦led灯泡e27螺口暖白球泡灯家用照明超亮节能灯泡转色温灯泡");
productNames.add("飞利浦led灯泡e14螺口蜡烛灯泡3W尖泡拉尾节能灯泡暖黄光源Lamp");
productNames.add("雷士照明 LED灯泡 e27大螺口节能灯3W球泡灯 Lamp led节能灯泡");
productNames.add("飞利浦 led灯泡 e27螺口家用3w暖白球泡灯节能灯5W灯泡LED单灯7w");
productNames.add("飞利浦led小球泡e14螺口4.5w透明款led节能灯泡照明光源lamp单灯");
productNames.add("飞利浦蒲公英护眼台灯工作学习阅读节能灯具30508带光源");
productNames.add("欧普照明led灯泡蜡烛节能灯泡e14螺口球泡灯超亮照明单灯光源");
productNames.add("欧普照明led灯泡节能灯泡超亮光源e14e27螺旋螺口小球泡暖黄家用");
productNames.add("聚欧普照明led灯泡节能灯泡e27螺口球泡家用led照明单灯超亮光源");
Directory index &#61; createIndex(analyzer, productNames);
最后一行的createIndex方法实现如下:
private static Directory createIndex(IKAnalyzer analyzer, List<String> products) throws IOException {Directory index &#61; new RAMDirectory();IndexWriterConfig config &#61; new IndexWriterConfig(analyzer);IndexWriter writer &#61; new IndexWriter(index, config);for (String name : products) {addDoc(writer, name);}writer.close();return index;
}
这里传入的参数有两个, 第一个是中文分词器, 第二个是存入的数据;
第一步是创建一个Dictionary, 然后构建一个IndexWriter用于写入, 这里要给这个IndexWritter使用一个Config因为需要使用中文分词器对传入的数据进行解析, 然后创建好writer之后循环写入数据即可, 这里的addDoc方法实现如下, 最后关闭writer即可:
private static void addDoc(IndexWriter w, String name) throws IOException {Document doc &#61; new Document();doc.add(new TextField("name", name, Field.Store.YES));w.addDocument(doc);
}
在这里传入一个写入器;
其实这里是创建一个新的Document然后把这个document用writer写入到之前创建的dictionary里面, 此处的document是可以有多个Field的, 但是这里只传入了一项数据, 所以只需要用到一个TextField叫name;
然后到这里为止, 我们的数据部分就准备完成了, 然后下面我们开始执行一次查询
二.执行一次查询
先构建一个查询器
首先我们获取到用户提供的关键词, 然后根据这个关键词来构建一个查询器, 大概代码如下:
String keyword &#61; "护眼带光源";
Query query &#61; new QueryParser("name", analyzer).parse(keyword);
这里我们首先获得了一个keyword, 然后构造了一个QueryParser解析器, 然后传入一个analyzer(就是之前创建的中文分词器), 然后我们要对name进行搜索, 解析keyword, 这样一个搜索的查询器就构建好了;
然后进行搜索
IndexReader reader &#61; DirectoryReader.open(index);
IndexSearcher searcher &#61; new IndexSearcher(reader);
int numberPerPage &#61; 1000;
System.out.printf("当前一共有%d条数据%n",productNames.size());
System.out.printf("查询关键字是&#xff1a;\"%s\"%n",keyword);
ScoreDoc[] hits &#61; searcher.search(query, numberPerPage).scoreDocs;
首先要构造一个IndexReader对之前创建的Dictionary进行读取, 这里调用的是DirectoryReader的静态方法, 然后使用IndexSearcher来搜索, 设定每页要显示多少条数据, 然后调用searcher的search方法, 这里的返回的是一个scoreDoc的数组;
然后显示搜索结果
其实这部分就是要对刚才获得的结果, 也就是那个scoreDoc这个数组进行解析, 获得内容;
大概代码如下:
private static void showSearchResults(IndexSearcher searcher, ScoreDoc[] hits, Query query, IKAnalyzer analyzer)throws Exception {System.out.println("找到 " &#43; hits.length &#43; " 个命中.");System.out.println("序号\t匹配度得分\t结果");for (int i &#61; 0; i < hits.length; &#43;&#43;i) {ScoreDoc scoreDoc&#61; hits[i];int docId &#61; scoreDoc.doc;Document d &#61; searcher.doc(docId);List<IndexableField> fields &#61; d.getFields();System.out.print((i &#43; 1));System.out.print("\t" &#43; scoreDoc.score);for (IndexableField f : fields) {System.out.print("\t" &#43; d.get(f.name()));}System.out.println();}
}
可以看到, 这里直接取scoreDoc.doc获得的是在原来的Dictionary中的document id, 然后要利用searcher的doc方法来获得这个document的内容, 然后此处的document中只有一个field就是name, 但是往往可能会有多个field, 所以这里用了遍历所有的field来输出内容, 兼容性更好
整理
所以Lucene的基本流程是这样的:
第一步创建索引
获得数据 → 创建Dictionary
第二步开始查询
查询解析器处理关键词 → 查询器 → IndexSearcher(IndexReader) → ScoreDoc
一些花里胡哨的东西
1.分页查询
这个lucene可以使用分页查询, 可以看到, 在搜索这一步, 有这个操作
ScoreDoc[] hits &#61; searcher.search(query, numberPerPage).scoreDocs;
这一步就是把符合的hits的scoredoc全部存到了这个hits数组里面, 也就相当于保存到了内存中, 这样在数据量较大的时候是不太好的
所以可以使用分页查询, 比如每页10条数据, 一共20页, 我要查第7页的那10条数据
上面的方法就是, 我把这20页的200条数据全部存到内存中, 然后去读取我要的;
但是还可以我先查找到第69条数据, 然后使用searchAfter方法, 查找后面的10条数据, 这样就不用把所有数据都放到内存里了, 给个大概的demo把~
private static ScoreDoc[] pageSearch2(Query query, IndexSearcher searcher, int pageNow, int pageSize)throws IOException{int start &#61; (pageNow-1)*pageSize;if(start &#61;&#61; 0){return searcher.search(query, pageSize).scoreDocs;}TopDocs topDocs &#61; searcher.search(query, start);ScoreDoc preScoreDoc &#61; topDocs.scoreDocs[start-1];topDocs &#61; searcher.searchAfter(preScoreDoc, query, pageSize);return topDocs.scoreDocs;}
2.索引的增加删除修改
Dictionary在创建之后也是可以修改的
增加的方法就和前面创建的时候一样, 还是用writer来写进去add就好了;
删除可以用deleteDocument方法, 类似这样:
IndexWriterConfig config &#61; new IndexWriterConfig(analyzer);IndexWriter indexWriter &#61; new IndexWriter(index, config);indexWriter.deleteDocuments(new Term("id", "51173"));indexWriter.commit();indexWriter.close();
然后lucene还支持其他的一些方式的删除, 比如这样的:
DeleteDocuments(Query query):根据Query条件来删除单个或多个DocumentDeleteDocuments(Query[] queries):根据Query条件来删除单个或多个DocumentDeleteDocuments(Term term):根据Term来删除单个或多个DocumentDeleteDocuments(Term[] terms):根据Term来删除单个或多个DocumentDeleteAll():删除所有的Document
然后修改也差不多, demo给一个:
IndexWriterConfig config &#61; new IndexWriterConfig(analyzer);
IndexWriter indexWriter &#61; new IndexWriter(index, config);
Document doc &#61; new Document();
doc.add(new TextField("id", "51173", Field.Store.YES));
doc.add(new TextField("name", "神鞭&#xff0c;鞭没了&#xff0c;神还在", Field.Store.YES));
doc.add(new TextField("category", "道具", Field.Store.YES));
doc.add(new TextField("price", "998", Field.Store.YES));
doc.add(new TextField("place", "南海群岛", Field.Store.YES));
doc.add(new TextField("code", "888888", Field.Store.YES));
indexWriter.updateDocument(new Term("id", "51173"), doc );
indexWriter.commit();
indexWriter.close();
更多内容可以到HOW2J网站学习