公众号阅读
https://mp.weixin.qq.com/s/M3...css
[TOC]git
The Apache LuceneTM project develops open-source search software, including:
Lucene Core, our flagship sub-project, provides Java-based indexing and search technology, as well as spellchecking, hit highlighting and advanced analysis/tokenization capabilities.
lucene官网(http://lucene.apache.org/)github
Lucene是apache软件基金会4 jakarta项目组的一个子项目,是一个开放源代码的全文检索引擎工具包,但它不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎(英文与德文两种西方语言)。Lucene的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础创建起完整的全文检索引擎。Lucene是一套用于全文检索和搜寻的开源程式库,由Apache软件基金会支持和提供。Lucene提供了一个简单却强大的应用程式接口,可以作全文索引和搜寻。在Java开发环境里Lucene是一个成熟的免费开源工具。就其自己而言,Lucene是当前以及最近几年最受欢迎的免费Java信息检索程序库。人们常常提到信息检索程序库,虽然与搜索引擎有关,但不该该将信息检索程序库与搜索引擎相混淆。
如今Lucene在互联网行业的用的很是普遍,尤为是大数据时代的今天,那么根据本身的理解给你们简单的介绍一下为何要学习Lucene。
传统的sql查询方式,数据量过多时,数据库的压力就会变得很大,查询速度会变得很是慢。咱们须要使用更好的解决方案来分担数据库的压力。为了解决数据库压力和速度的问题,咱们的数据库就变成了索引库,咱们使用Lucene的API的来操做服务器上的索引库。这样彻底和数据库进行了隔离。算法
如今咱们已经了解了Lucene。sql
总结:Lucene全文检索就是对文档中所有内容进行分词,而后对全部单词创建倒排索引的过程。数据库
检索数据须要咱们先分词
,存入索引库
。apache
<properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.compiler.target>1.7</maven.compiler.target> <lunece.version>4.10.2</lunece.version> </properties> <dependencies> <!-- 分词器 --> <dependency> <groupId>com.janeluo</groupId> <artifactId>ikanalyzer</artifactId> <version>2012_u6</version> </dependency> <!-- lucene核心库 --> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-core</artifactId> <version>${lunece.version}</version> </dependency> <!-- Lucene的查询解析器 --> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-queryparser</artifactId> <version>${lunece.version}</version> </dependency> <!-- lucene的默认分词器库 --> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-analyzers-common</artifactId> <version>${lunece.version}</version> </dependency> <!-- lucene的高亮显示 --> <dependency> <groupId>org.apache.lucene</groupId> <artifactId>lucene-highlighter</artifactId> <version>${lunece.version}</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> </dependencies>
// 建立索引 @Test public void testCreate() throws Exception { // 1 建立文档对象 Document document = new Document(); // 建立并添加字段信息。参数:字段的名称、字段的值、是否存储,这里选Store.YES表明存储到文档列表。Store.NO表明不存储 document.add(new StringField("id", "1", Store.YES)); // 这里咱们title字段须要用TextField,即建立索引又会被分词。StringField会建立索引,可是不会被分词 document.add(new TextField("title", "谷歌地图之父跳槽facebook", Store.YES)); // 2 索引目录类,指定索引在硬盘中的位置 Directory directory = FSDirectory.open(new File("E:\\luceneTest")); // 3 建立分词器对象 // Analyzer analyzer = new StandardAnalyzer(); Analyzer analyzer = new IKAnalyzer(); // 4 索引写出工具的配置对象 IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, analyzer); // 是否清空索引库;设置打开方式:OpenMode.APPEND // 会在索引库的基础上追加新索引。OpenMode.CREATE会先清空原来数据,再提交新的索引 conf.setOpenMode(OpenMode.CREATE); // 5 建立索引的写出工具类。参数:索引的目录和配置信息 IndexWriter indexWriter = new IndexWriter(directory, conf); // 6 把文档交给IndexWriter indexWriter.addDocument(document); // 7 提交 indexWriter.commit(); // 8 关闭 indexWriter.close(); }
索引查看工具
启动run.bat数组
*** @Test public void testSearch() throws Exception { // 索引目录对象 Directory directory = FSDirectory.open(new File("E:\\luceneTest")); // 索引读取工具 IndexReader reader = DirectoryReader.open(directory); // 索引搜索工具 IndexSearcher searcher = new IndexSearcher(reader); // 建立查询解析器,两个参数:默认要查询的字段的名称,分词器 QueryParser parser = new QueryParser("title", new IKAnalyzer()); // 建立查询解析器,俩个参数:默认要查询的字段名称,分词器 // MultiFieldQueryParser parser2 = new MultiFieldQueryParser(new // String[] { // "id", "title" }, new IKAnalyzer()); // Query query2 = parser2.parse("1"); // 建立查询对象 Query query = parser.parse("谷歌之父"); // 搜索数据,两个参数:查询条件对象要查询的最大结果条数 // 返回的结果是 按照匹配度排名得分前N名的文档信息(包含查询到的总条数信息、全部符合条件的文档的编号信息)。 TopDocs topDocs = searcher.search(query, 10); // 获取总条数 System.out.println("本次搜索共找到" + topDocs.totalHits + "条数据"); // 获取得分文档对象(ScoreDoc)数组.SocreDoc中包含:文档的编号、文档的得分 ScoreDoc[] scoreDocs = topDocs.scoreDocs; for (ScoreDoc scoreDoc : scoreDocs) { // 取出文档编号 int docID = scoreDoc.doc; // 根据编号去找文档 Document doc = reader.document(docID); System.out.println("id: " + doc.get("id")); System.out.println("title: " + doc.get("title")); // 取出文档得分 System.out.println("得分: " + scoreDoc.score); } }
注:代码中加了必要注释服务器
public void search(Query query) throws Exception { // 索引目录对象 Directory directory = FSDirectory.open(new File("E:\\luceneTest")); // 索引读取工具 IndexReader reader = DirectoryReader.open(directory); // 索引搜索工具 IndexSearcher searcher = new IndexSearcher(reader); // 搜索数据,两个参数:查询条件对象要查询的最大结果条数 // 返回的结果是 按照匹配度排名得分前N名的文档信息(包含查询到的总条数信息、全部符合条件的文档的编号信息)。 TopDocs topDocs = searcher.search(query, 10); // 获取总条数 System.out.println("本次搜索共找到" + topDocs.totalHits + "条数据"); // 获取得分文档对象(ScoreDoc)数组.SocreDoc中包含:文档的编号、文档的得分 ScoreDoc[] scoreDocs = topDocs.scoreDocs; for (ScoreDoc scoreDoc : scoreDocs) { // 取出文档编号 int docID = scoreDoc.doc; // 根据编号去找文档 Document doc = reader.document(docID); System.out.println("id: " + doc.get("id")); System.out.println("title: " + doc.get("title")); // 取出文档得分 System.out.println("得分: " + scoreDoc.score); } }
注:普通词条查询架构
/* * 测试普通词条查询 注意:Term(词条)是搜索的最小单位,不可再分词。值必须是字符串! */ @Test public void testTermQuery() throws Exception { // 建立词条查询对象 Query query = new TermQuery(new Term("title", "谷歌地图")); search(query); }
注:通配符查询
/* * 测试通配符查询 ? 能够表明任意一个字符 * 能够任意多个任意字符 */ @Test public void testWildCardQuery() throws Exception { // 建立查询对象 Query query = new WildcardQuery(new Term("title", "*歌*")); search(query); }
注:模糊查询;数组范围查询;布尔查询
/* * 测试模糊查询 */ @Test public void testFuzzyQuery() throws Exception { // 建立模糊查询对象:容许用户输错。可是要求错误的最大编辑距离不能超过2 // 编辑距离:一个单词到另外一个单词最少要修改的次数 facebool --> facebook 须要编辑1次,编辑距离就是1 // Query query = new FuzzyQuery(new Term("title","fscevool")); // 能够手动指定编辑距离,可是参数必须在0~2之间 Query query = new FuzzyQuery(new Term("title", "facevool"), 2); search(query); } /*************************************************************** * 测试:数值范围查询 注意:数值范围查询,能够用来对非String类型的ID进行精确的查找 */ @Test public void testNumericRangeQuery() throws Exception { // 数值范围查询对象,参数:字段名称,最小值、最大值、是否包含最小值、是否包含最大值 Query query = NumericRangeQuery.newLongRange("id", 2L, 2L, true, true); search(query); } /***************************************************************** * 布尔查询: 布尔查询自己没有查询条件,能够把其它查询经过逻辑运算进行组合! 交集:Occur.MUST + Occur.MUST * 并集:Occur.SHOULD + Occur.SHOULD 非:Occur.MUST_NOT */ // @Test // public void testBooleanQuery() throws Exception { // // Query query1 = NumericRangeQuery.newLongRange("id", 1L, 3L, true, true); // Query query2 = NumericRangeQuery.newLongRange("id", 2L, 4L, true, true); // // 建立布尔查询的对象 // BooleanQuery query = new BooleanQuery(); // // 组合其它查询 // query.add(query1, BooleanClause.Occur.MUST_NOT); // query.add(query2, BooleanClause.Occur.SHOULD); // // search(query); // }
注:修改和删除操做
/* * 测试:修改索引 注意: A:Lucene修改功能底层会先删除,再把新的文档添加。 * B:修改功能会根据Term进行匹配,全部匹配到的都会被删除。这样很差 C:所以,通常咱们修改时,都会根据一个惟一不重复字段进行匹配修改。例如ID * D:可是词条搜索,要求ID必须是字符串。若是不是,这个方法就不能用。 * 若是ID是数值类型,咱们不能直接去修改。能够先手动删除deleteDocuments(数值范围查询锁定ID),再添加。 */ @Test public void testUpdate() throws Exception { // 建立目录对象 Directory directory = FSDirectory.open(new File("E:\\luceneTest")); // 建立配置对象 IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, new IKAnalyzer()); // 建立索引写出工具 IndexWriter writer = new IndexWriter(directory, conf); // 建立新的文档数据 Document doc = new Document(); doc.add(new StringField("id", "1", Store.YES)); doc.add(new TextField("title", "谷歌地图之父跳槽facebook ", Store.YES)); /* * 修改索引。参数: 词条:根据这个词条匹配到的全部文档都会被修改 文档信息:要修改的新的文档数据 */ writer.updateDocument(new Term("id", "1"), doc); // 提交 writer.commit(); // 关闭 writer.close(); } /* * 演示:删除索引 注意: 通常,为了进行精确删除,咱们会根据惟一字段来删除。好比ID 若是是用Term删除,要求ID也必须是字符串类型! */ @Test public void testDelete() throws Exception { // 建立目录对象 Directory directory = FSDirectory.open(new File("E:\\luceneTest")); // 建立配置对象 IndexWriterConfig conf = new IndexWriterConfig(Version.LATEST, new IKAnalyzer()); // 建立索引写出工具 IndexWriter writer = new IndexWriter(directory, conf); // 根据词条进行删除 // writer.deleteDocuments(new Term("id", "1")); // 根据query对象删除,若是ID是数值类型,那么咱们能够用数值范围查询锁定一个具体的ID // Query query = NumericRangeQuery.newLongRange("id", 2L, 2L, true, // true); // writer.deleteDocuments(query); // 删除全部 writer.deleteAll(); // 提交 writer.commit(); // 关闭 writer.close(); }
// 高亮显示 @Test public void testHighlighter() throws Exception { // 目录对象 Directory directory = FSDirectory.open(new File("indexDir")); // 建立读取工具 IndexReader reader = DirectoryReader.open(directory); // 建立搜索工具 IndexSearcher searcher = new IndexSearcher(reader); QueryParser parser = new QueryParser("title", new IKAnalyzer()); Query query = parser.parse("谷歌地图"); // 格式化器 Formatter formatter = new SimpleHTMLFormatter("<em>", "</em>"); QueryScorer scorer = new QueryScorer(query); // 准备高亮工具 Highlighter highlighter = new Highlighter(formatter, scorer); // 搜索 TopDocs topDocs = searcher.search(query, 10); System.out.println("本次搜索共" + topDocs.totalHits + "条数据"); ScoreDoc[] scoreDocs = topDocs.scoreDocs; for (ScoreDoc scoreDoc : scoreDocs) { // 获取文档编号 int docID = scoreDoc.doc; Document doc = reader.document(docID); System.out.println("id: " + doc.get("id")); String title = doc.get("title"); // 用高亮工具处理普通的查询结果,参数:分词器,要高亮的字段的名称,高亮字段的原始值 String hTitle = highlighter.getBestFragment(new IKAnalyzer(), "title", title); System.out.println("title: " + hTitle); // 获取文档的得分 System.out.println("得分:" + scoreDoc.score); } }
// 排序 @Test public void testSortQuery() throws Exception { // 目录对象 Directory directory = FSDirectory.open(new File("indexDir")); // 建立读取工具 IndexReader reader = DirectoryReader.open(directory); // 建立搜索工具 IndexSearcher searcher = new IndexSearcher(reader); QueryParser parser = new QueryParser("title", new IKAnalyzer()); Query query = parser.parse("谷歌地图"); // 建立排序对象,须要排序字段SortField,参数:字段的名称、字段的类型、是否反转若是是false,升序。true降序 Sort sort = new Sort(new SortField("id", SortField.Type.LONG, true)); // 搜索 TopDocs topDocs = searcher.search(query, 10,sort); System.out.println("本次搜索共" + topDocs.totalHits + "条数据"); ScoreDoc[] scoreDocs = topDocs.scoreDocs; for (ScoreDoc scoreDoc : scoreDocs) { // 获取文档编号 int docID = scoreDoc.doc; Document doc = reader.document(docID); System.out.println("id: " + doc.get("id")); System.out.println("title: " + doc.get("title")); } }
// 分页 @Test public void testPageQuery() throws Exception { // 实际上Lucene自己不支持分页。所以咱们须要本身进行逻辑分页。咱们要准备分页参数: int pageSize = 2;// 每页条数 int pageNum = 3;// 当前页码 int start = (pageNum - 1) * pageSize;// 当前页的起始条数 int end = start + pageSize;// 当前页的结束条数(不能包含) // 目录对象 Directory directory = FSDirectory.open(new File("indexDir")); // 建立读取工具 IndexReader reader = DirectoryReader.open(directory); // 建立搜索工具 IndexSearcher searcher = new IndexSearcher(reader); QueryParser parser = new QueryParser("title", new IKAnalyzer()); Query query = parser.parse("谷歌地图"); // 建立排序对象,须要排序字段SortField,参数:字段的名称、字段的类型、是否反转若是是false,升序。true降序 Sort sort = new Sort(new SortField("id", Type.LONG, false)); // 搜索数据,查询0~end条 TopDocs topDocs = searcher.search(query, end,sort); System.out.println("本次搜索共" + topDocs.totalHits + "条数据"); ScoreDoc[] scoreDocs = topDocs.scoreDocs; for (int i = start; i < end; i++) { ScoreDoc scoreDoc = scoreDocs[i]; // 获取文档编号 int docID = scoreDoc.doc; Document doc = reader.document(docID); System.out.println("id: " + doc.get("id")); System.out.println("title: " + doc.get("title")); } }
- 当谈论到查询的相关性,很重要的一件事就是对于给定的查询语句,如何计算文档得分。文档得分是一个用来描述查询语句和文档之间匹配程度的变量。若是你但愿经过干预Lucene查询来改变查询结果的排序,你就须要对Lucene的得分计算有所理解。
计算文档得分,考虑因素以下:
Lucene概念上的打分公式是这样的:(TF/IDF公式的概念版)
上面的公式展现了布尔信息检索模型和向量空间信息检索模型的组合。咱们暂时不去讨论它,直接见识下Lucene实际应用的打分公式:
能够看到,文档的分数其实是由查询语句q和文档d做为变量的一个函数值。打分公式中有两部分不直接依赖于查询词,它们是coord和queryNorm。公式的值是这样计算的,coord和queryNorm两大部分直接乘以查询语句中每一个查询词计算值的总和。另外一方面,这个总和也是由每一个查询词的词频(tf),逆文档频率(idf),查询词的权重,还有norm,也就是前面说的length norm相乘而得的结果。听上去有些复杂吧?不用担忧,这些东西不须要所有记住。你只须要知道在进行文档打分的时候,哪些因素是起决定做用的就能够了。基本上,从前面的公式中能够提炼出如下的几个规则:
正如咱们所看到的那样,Lucene会给具备这些特征的文档打最高分:文档内容可以匹配到较多的稀有的搜索关键词,文档的域包含较少的Term,而且域中的Term可能是稀有的。简而言之
将IKAnalyzer.cfg.xml和stopword.dic和xxx.dic文件复制到MyEclipse的src目录下,再进行配置
IK Analyzer默认的停用词词典为IKAnalyzer2012_u6/stopword.dic,这个停用词词典并不完整,只有30多个英文停用词。能够扩展停用词字典,新增ext_stopword.dic,文件和IKAnalyzer.cfg.xml在同一目录,编辑IKAnalyzer.cfg.xml把新增的停用词字典写入配置文件,多个停用词字典用逗号隔开,以下所示。
<entry key="ext_stopwords">stopword.dic;ext_stopword.dic</entry>
接下来就能够构建本身的搜索引擎了。
上面展现了lucene一些基本操做,更详细的的工具类能够访问https://github.com/wangshiyu777/usefulDemo,分享了详细Demo。