首先,帮忙点击一下我的网站http://www.wenzhihuai.com/ 。谢谢啊,如果可以,GitHub上麻烦给个star,以后面试能讲讲这个项目,GitHub地址https://github.com/Zephery/newblog

Lucene的整体架构

搜索引擎的几个重要概念:

  1. 倒排索引:将文档中的词作为关键字,建立词与文档的映射关系,通过对倒排索引的检索,可以根据词快速获取包含这个词的文档列表。倒排索引一般需要对句子做去除停用词。

  2. 停用词:在一段句子中,去掉之后对句子的表达意向没有印象的词语,如“非常”、“如果”,中文中主要包括冠词,副词等。

  3. 排序:搜索引擎在对一个关键词进行搜索时,可能会命中许多文档,这个时候,搜索引擎就需要快速的查找的用户所需要的文档,因此,相关度大的结果需要进行排序,这个设计到搜索引擎的相关度算法。

Lucene中的几个概念

  1. 文档(Document):文档是一系列域的组合,文档的域则代表一系列域文档相关的内容。
  2. 域(Field):每个文档可以包含一个或者多个不同名称的域。
  3. 词(Term):Term是搜索的基本单元,与Field相对应,包含了搜索的域的名称和关键词。
  4. 查询(Query):一系列Term的条件组合,成为TermQuery,但也有可能是短语查询等。
  5. 分词器(Analyzer):主要是用来做分词以及去除停用词的处理。

索引的建立

索引的搜索

lucene在本网站的使用:

  1. 搜索 2. 自动分词

一、搜索

注意:本文使用最新的lucene,版本6.6.0。lucene的版本更新很快,每跨越一次大版本,使用方式就不一样。首先需要导入lucene所使用的包。使用maven:

<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId><!--lucene核心-->
<version>${lucene.version}</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-common</artifactId><!--分词器-->
<version>${lucene.version}</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-smartcn</artifactId><!--中文分词器-->
<version>${lucene.version}</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId><!--格式化-->
<version>${lucene.version}</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-highlighter</artifactId><!--lucene高亮-->
<version>${lucene.version}</version>
</dependency>
  1. 构建索引
Directory dir = FSDirectory.open(Paths.get("blog_index"));//索引存储的位置
SmartChineseAnalyzer analyzer = new SmartChineseAnalyzer();//简单的分词器
IndexWriterConfig config = new IndexWriterConfig(analyzer);
IndexWriter writer = new IndexWriter(dir, config);
Document doc = new Document();
doc.add(new TextField("title", blog.getTitle(), Field.Store.YES)); //对标题做索引
doc.add(new TextField("content", Jsoup.parse(blog.getContent()).text(), Field.Store.YES));//对文章内容做索引
writer.addDocument(doc);
writer.close();
  1. 更新与删除
IndexWriter writer = getWriter();
Document doc = new Document();
doc.add(new TextField("title", blog.getTitle(), Field.Store.YES));
doc.add(new TextField("content", Jsoup.parse(blog.getContent()).text(), Field.Store.YES));
writer.updateDocument(new Term("blogid", String.valueOf(blog.getBlogid())), doc); //更新索引
writer.close();
  1. 查询
private static void search_index(String keyword) {
try {
Directory dir = FSDirectory.open(Paths.get("blog_index")); //获取要查询的路径,也就是索引所在的位置
IndexReader reader = DirectoryReader.open(dir);
IndexSearcher searcher = new IndexSearcher(reader);
SmartChineseAnalyzer analyzer = new SmartChineseAnalyzer();
QueryParser parser = new QueryParser("content", analyzer); //查询解析器
Query query = parser.parse(keyword); //通过解析要查询的String,获取查询对象
TopDocs docs = searcher.search(query, 10);//开始查询,查询前10条数据,将记录保存在docs中,
for (ScoreDoc scoreDoc : docs.scoreDocs) { //取出每条查询结果
Document doc = searcher.doc(scoreDoc.doc); //scoreDoc.doc相当于docID,根据这个docID来获取文档
System.out.println(doc.get("title")); //fullPath是刚刚建立索引的时候我们定义的一个字段
}
reader.close();
} catch (IOException | ParseException e) {
logger.error(e.toString());
}
}
  1. 高亮
Fragmenter fragmenter = new SimpleSpanFragmenter(scorer);
SimpleHTMLFormatter simpleHTMLFormatter = new SimpleHTMLFormatter("<b><font color='red'>", "</font></b>");
Highlighter highlighter = new Highlighter(simpleHTMLFormatter, scorer);
highlighter.setTextFragmenter(fragmenter);
for (ScoreDoc scoreDoc : docs.scoreDocs) { //取出每条查询结果
Document doc = searcher.doc(scoreDoc.doc); //scoreDoc.doc相当于docID,根据这个docID来获取文档
String title = doc.get("title");
TokenStream tokenStream = analyzer.tokenStream("title", new StringReader(title));
String hTitle = highlighter.getBestFragment(tokenStream, title);
System.out.println(hTitle);
}

结果

<b><font color='red'>Java</font></b>堆.栈和常量池 笔记
  1. 分页

    目前lucene分页的方式主要有两种:

    (1). 每次都全部查询,然后通过截取获得所需要的记录。由于采用了分词与倒排索引,所有速度是足够快的,但是在数据量过大的时候,占用内存过大,容易造成内存溢出

    (2). 使用searchAfter把数据保存在缓存里面,然后再去取。这种方式对大量的数据友好,但是当数据量比较小的时候,速度会相对慢。

    lucene中使用searchafter来筛选顺序
ScoreDoc lastBottom = null;//相当于pageSize
BooleanQuery.Builder booleanQuery = new BooleanQuery.Builder();
QueryParser parser1 = new QueryParser("title", analyzer);//对文章标题进行搜索
Query query1 = parser1.parse(q);
booleanQuery.add(query1, BooleanClause.Occur.SHOULD);
TopDocs hits = search.searchAfter(lastBottom, booleanQuery.build(), pagehits); //lastBottom(pageSize),pagehits(pagenum)
  1. 使用效果

    全部代码放在这里,代码写的不太好,光从代码规范上就不咋地。在网页上的使用效果如下:

二、lucene自动补全

百度、谷歌等在输入文字的时候会弹出补全框,如下图:

在搭建lucene自动补全的时候,也有考虑过使用SQL语句中使用like来进行,主要还是like对数据库压力会大,而且相关度没有lucene的高。主要使用了官方suggest库以及autocompelte.js这个插件。

suggest的原理看这,以及索引结构看这

使用:

  1. 导入maven包
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-suggest</artifactId>
<version>6.6.0</version>
</dependency>
  1. 如果想将结果反序列化,声明实体类的时候要加上:
public class Blog implements Serializable {
  1. 实现InputIterator接口

    InputIterator的几个方法:

    long weight():返回的权重值,大小会影响排序,默认是1L

    BytesRef payload():对某个对象进行序列化

    boolean hasPayloads():是否有设置payload信息

    Set contexts():存入context,context里可以是任意的自定义数据,一般用于数据过滤

    boolean hasContexts():判断是否有下一个,默认为false
public class BlogIterator implements InputIterator {
/**
* logger
*/
private static final Logger logger = LoggerFactory.getLogger(BlogIterator.class);
private Iterator<Blog> blogIterator;
private Blog currentBlog; public BlogIterator(Iterator<Blog> blogIterator) {
this.blogIterator = blogIterator;
} @Override
public boolean hasContexts() {
return true;
} @Override
public boolean hasPayloads() {
return true;
} public Comparator<BytesRef> getComparator() {
return null;
} @Override
public BytesRef next() {
if (blogIterator.hasNext()) {
currentBlog = blogIterator.next();
try {
//返回当前Project的name值,把blog类的name属性值作为key
return new BytesRef(Jsoup.parse(currentBlog.getTitle()).text().getBytes("utf8"));
} catch (Exception e) {
e.printStackTrace();
return null;
}
} else {
return null;
}
} /**
* 将Blog对象序列化存入payload
* 可以只将所需要的字段存入payload,这里对整个实体类进行序列化,方便以后需求,不建议采用这种方法
*/
@Override
public BytesRef payload() {
try {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(bos);
out.writeObject(currentBlog);
out.close();
BytesRef bytesRef = new BytesRef(bos.toByteArray());
return bytesRef;
} catch (IOException e) {
logger.error("", e);
return null;
}
} /**
* 文章标题
*/
@Override
public Set<BytesRef> contexts() {
try {
Set<BytesRef> regions = new HashSet<BytesRef>();
regions.add(new BytesRef(currentBlog.getTitle().getBytes("UTF8")));
return regions;
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Couldn't convert to UTF-8");
}
} /**
* 返回权重值,这个值会影响排序
* 这里以产品的销售量作为权重值,weight值即最终返回的热词列表里每个热词的权重值
*/
@Override
public long weight() {
return currentBlog.getHits(); //change to hits
}
}
  1. ajax 建立索引
/**
* ajax建立索引
*/
@Override
public void ajaxbuild() {
try {
Directory dir = FSDirectory.open(Paths.get("autocomplete"));
SmartChineseAnalyzer analyzer = new SmartChineseAnalyzer();
AnalyzingInfixSuggester suggester = new AnalyzingInfixSuggester(dir, analyzer);
//创建Blog测试数据
List<Blog> blogs = blogMapper.getAllBlog();
suggester.build(new BlogIterator(blogs.iterator()));
} catch (IOException e) {
System.err.println("Error!");
}
}
  1. 查找

    因为有些文章的标题是一样的,先对list排序,将标题短的放前面,长的放后面,然后使用LinkHashSet来存储。
@Override
public Set<String> ajaxsearch(String keyword) {
try {
Directory dir = FSDirectory.open(Paths.get("autocomplete"));
SmartChineseAnalyzer analyzer = new SmartChineseAnalyzer();
AnalyzingInfixSuggester suggester = new AnalyzingInfixSuggester(dir, analyzer);
List<String> list = lookup(suggester, keyword);
list.sort(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
if (o1.length() > o2.length()) {
return 1;
} else {
return -1;
}
}
});
Set<String> set = new LinkedHashSet<>();
for (String string : list) {
set.add(string);
}
ssubSet(set, 7);
return set;
} catch (IOException e) {
System.err.println("Error!");
return null;
}
}
  1. controller层
@RequestMapping("ajaxsearch")
public void ajaxsearch(HttpServletRequest request, HttpServletResponse response) throws IOException {
String keyword = request.getParameter("keyword");
if (StringUtils.isEmpty(keyword)) {
return;
}
Set<String> set = blogService.ajaxsearch(keyword);
Gson gson = new Gson();
response.getWriter().write(gson.toJson(set));//返回的数据使用json
}
  1. ajax来提交请求

    autocomplete.js源代码与介绍:https://github.com/xdan/autocomplete
<link rel="stylesheet" href="js/autocomplete/jquery.autocomplete.css">
<script src="js/autocomplete/jquery.autocomplete.js" type="text/javascript"></script>
<script type="text/javascript">
/******************** remote start **********************/
$('#remote_input').autocomplete({
source: [
{
url: "ajaxsearch.html?keyword=%QUERY%",
type: 'remote'
}
]
});
/********************* remote end ********************/
</script>
  1. 效果:

欢迎访问我的个人网站

参考:

https://www.ibm.com/developerworks/cn/java/j-lo-lucene1/

http://iamyida.iteye.com/blog/2205114

谈谈个人网站的建立(二)—— lucene的使用的更多相关文章

  1. MVC5 网站开发之二 创建项目

    昨天对项目的思路大致理了一下,今天先把解决方案建立起来.整个解决包含Ninesky.Web.Ninesky.Core,Ninesky.DataLibrary等3个项目.Ninesky.Web是web应 ...

  2. ASP.NET MVC5 网站开发实践(二) Member区域 - 用户部分(3)修改资料、修改密码

    在上一篇博客中实现了用户的注销和登录,其实代码里落了点东西,就是用户登录要更新最后一次登录时间和登录IP,这次补上.今天做修改资料和修改密码,TryUpdateModel是新用到的东西. 目录: AS ...

  3. [译]MVC网站教程(二):异常管理

    介绍 “MVC网站教程”系列的目的是教你如何使用 ASP.NET MVC 创建一个基本的.可扩展的网站. 1)   MVC网站教程(一):多语言网站框架 2)   MVC网站教程(二):异常管理 3) ...

  4. ASP.NET MVC5 网站开发实践(二) Member区域–管理列表、回复及删除

    本来想接着上次把这篇写完的,没想到后来工作的一些事落下了,放假了赶紧补上. 目录: ASP.NET MVC5 网站开发实践 - 概述 ASP.NET MVC5 网站开发实践(一) - 项目框架 ASP ...

  5. ASP.NET MVC5 网站开发实践(二) Member区域–我的咨询列表及添加咨询

    上次把咨询的架构搭好了,现在分两次来完成咨询:1.用户部分,2管理部分.这次实现用户部分,包含两个功能,查看我的咨询和进行咨询. 目录: ASP.NET MVC5 网站开发实践 - 概述 ASP.NE ...

  6. ASP.NET MVC5 网站开发实践(二) Member区域 - 咨询管理的架构

    咨询.留言.投诉等功能是网站应具备的基本功能,可以加强管理员与用户的交流,在上次完成文章部分后,这次开始做Member区域的咨询功能(留言.投诉都是咨询).咨询跟文章非常相似,而且内容更少.更简单. ...

  7. ASP.NET MVC5 网站开发实践(二) Member区域 - 修改及删除文章

    上次做了显示文章列表,再实现修改和删除文章这部分内容就结束了,这次内容比较简单,由于做过了添加文章,修改文章非常类似,就是多了一个TryUpdateModel部分更新模型数据.   目录: ASP.N ...

  8. ASP.NET MVC5 网站开发实践(二) Member区域 - 全部文章列表

    显示文章列表分两块,管理员可以显示全部文章列表,一般用户只显示自己的文章列表.文章列表的显示采用easyui-datagrid.后台需要与之对应的action返回json类型数据   目录 ASP.N ...

  9. ASP.NET MVC5 网站开发实践(二) Member区域 - 添加文章

    上次把架构做好了,这次做添加文章.添加文章涉及附件的上传管理及富文本编辑器的使用,早添加文章时一并实现. 要点: 富文本编辑器采用KindEditor.功能很强大,国人开发,LGPL开源,自己人的好东 ...

随机推荐

  1. (转)Linux 下 查看以及修改文件权限

    场景:Linux环境下远程部署项目,发现因为文件权限问题,不能执行远端的可执行文件.问题还没解决,待议... 1 查看权限 在终端输入: ls -l xxx.xxx (xxx.xxx是文件名) 那么就 ...

  2. VMware workstation批量创建虚拟机和自动化安装操作系统(一)

    一. 简述 作为从事IT行业运维工作的Linuxer,大多情况下需要在测试环境中部署业务系统并进行测试,在没有足够的计算存储网络条件下,使用虚拟机进行虚拟集群的创建和使用,是一种不错的学习和实践方式. ...

  3. HDU 6035---Colorful Tree(树形DP)

    题目链接 Problem Description There is a tree with n nodes, each of which has a type of color represented ...

  4. VerilogHDL可综合设计的注意事项

    可综合的语法已经记录得差不多了,剩下一些遗留的问题,在这里记录一下吧. 一.逻辑设计 (1)组合逻辑设计 下面是一些用Verilog进行组合逻辑设计时的一些注意事项: ①组合逻辑可以得到两种常用的RT ...

  5. SpringBoot上传任意文件功能的实现

    一.pom文件依赖的添加 <dependencies> <dependency> <groupId>org.springframework.boot</gro ...

  6. 52. leetcode 96. Unique Binary Search Trees

    96. Unique Binary Search Trees Given n, how many structurally unique BST's (binary search trees) tha ...

  7. python扫描proxy并获取可用代理ip

    今天咱写一个挺实用的工具,就是扫描并获取可用的proxy 首先呢,我先百度找了一个网站:http://www.xicidaili.com 作为例子 这个网站里公布了许多的国内外可用的代理的ip和端口 ...

  8. git中常用的指令

    1.初始化仓库 git init2.设置用户名与邮箱 git config --global user.name 'name' git config --global user.email 'emai ...

  9. 关于ftp出现425错误

    在centos上搭建一个ftp,一切都配置好之后,我去访问时仍然会出现425 Failed to establish connection.这个错误,经过一番查找,原来是这个 -A INPUT -j ...

  10. 鄙人对constructor和prototype的总结

    在学习js面向对象过程中,我们总是对constructor和prototype充满疑惑,这两个概念是相当重要的,深入理解这两个概念对理解js的一些核心概念非常的重要.因此,在这里记录下鄙人见解,希望可 ...