首先,帮忙点击一下我的网站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. xshell常用命令

    常用的命令: suse linux 常用命令 (1) 命令ls--列出文件 ls 显示当前目录文件 ls -la 给出当前目录下所有文件的一个长列表,包括以句点开头的"隐藏"文件 ...

  2. 设计模式(3)抽象工厂模式(Abstract Factory)

    设计模式(0)简单工厂模式 设计模式(1)单例模式(Singleton) 设计模式(2)工厂方法模式(Factory Method) 源码地址 0 抽象工厂模式简介 0.0 抽象工厂模式定义 抽象工厂 ...

  3. 今天提示MyEclipse Trial Expired,如何手动获取MyEclipse 注册码!很牛!

    1.建立JAVA Project,随便命名,只要符合规则就行. 2.在刚刚建好的Project右击src,新建一个类,命名为MyEclipseGen,把.java里本来有的代码全部删掉,再把下面的代码 ...

  4. 快慢指针实现不依赖计数器寻找中位数(linked list)

    该方法在不借助计数器变量实现寻找中位数的功能.原理是:快指针的移动速度是慢指针移动速度的2倍,因此当快指针到达链表尾时,慢指针到达中点.程序还要考虑链表结点个数的奇偶数因素,当快指针移动x次后到达表尾 ...

  5. 起床困难综合症[NOI2014]

    [题解] 并不算很困难的贪心题.位运算毕竟是针对每一位的,从前向后处理,如果某一位1比0更优且可取1就使它为1.比较0和1的结果要单取这一位来看,但是题目中所给的参数并没有必要全部二进制分解,直接用十 ...

  6. 《奇思妙想》在JavaScript语言中floor和round方法在某种随机分配场景下对分配区间的公平性!!!

    前言 大欢哥的题目完成了,但是衍生出一个新的问题!上篇随笔中我和大欢哥采用的随机数生成方式,到底是谁的比较公平??? 正文 欢迎来到阿段博客<奇思妙想>!我们的口号是 “心有多大,bug就 ...

  7. ABP+AdminLTE+Bootstrap Table权限管理系统第一节--使用ASP.NET Boilerplate模板创建解决方案

    "abp是ASP.NET Boilerplate简称,是一个用最佳实践和流行技术开发现代WEB应用程序的新起点,它旨在成为一个通用的WEB应用程序框架和项目模板" abp官方网站: ...

  8. 【FAQ系列】:DB服务器产生大量物理读问题优化思路

    一 [现象] 1.7点到9点IO监控指标util特别高,如下: 2 .查看读写情况:读产生很高的物理IO,如下 [分析]:对比其他服务器,buffer pool都是80G,正常情况下热点数据都是从bu ...

  9. [STM32F429-DISCO-HAL]2.先学会点亮LED和使用LCD。。。

      首先就简单的一秒闪烁一次LED灯,进而类比推理其他外设的配置过程.然后呢我们就用上LCD吧,毕竟这块板上占地面积最大的就是这个2.4'LCD了.   先贴出简洁的main函数.很干净,比较容易看懂 ...

  10. Windows 程序注册成服务的方法

    Windows 程序注册成服务的方法 将windows 程序注册成服务这个是很多后台程序需要实现的功能,注册成服务后,你的程序就可以像windows 服务一样随系统启动,并且隐藏你的控制台界面.下面介 ...