做app网站需要什么,网站建设 排名宝下拉,纷享销客crm官网,淘宝网网页前言 一直以来个人博客的搜索功能很蹩脚#xff0c;只是自己简单用数据库的like %keyword%来实现的#xff0c;所以导致经常搜不到想要找的内容#xff0c;而且高亮显示、摘要截取等也不好实现#xff0c;所以决定采用Lucene改写博客的搜索功能。先来看一下最终效果#x…前言 一直以来个人博客的搜索功能很蹩脚只是自己简单用数据库的like %keyword%来实现的所以导致经常搜不到想要找的内容而且高亮显示、摘要截取等也不好实现所以决定采用Lucene改写博客的搜索功能。先来看一下最终效果 本文demo地址https://github.com/liuxianan/lucene-demo (包括本文需要用到的jar包可以从这里面下载) 效果演示地址http://blog.liuxianan.com/search?kw%E7%AB%AF%E5%8F%A3%20%E5%8D%A0%E7%94%A8 Lucene 介绍 Lucene是一个用Java开发的开源全文检索引擎官网是http://lucene.apache.org/ Lucene不是一个完整的全文索引应用与之对应的是solr而是是一个用Java写的全文索引引擎工具包它可以方便的嵌入到各种应用中实现针对应用的全文索引/检索功能更多介绍大家自行搜索。 版本选择 目前最新版是6.5.1截止到2017-05-04本来想直接用最新版的但是下载下来之后发现老是提示找不到某些类可我直接找到对应的jar包下去看却是有的不过却无法用jd-gui反编译提示一个什么错误盲目的我竟然以为是因为版本太新apache在放出最新jar包时自己没测试后来试了几个老一点的6.x版本发现都是这个错误5.x就不会好吧这时才想起来应该是jdk版本不对Lucene6.x需要jdk1.8以上只能怪我太out了毕竟确实好久没怎么写过Java代码了。 由于本地、线上都是使用的jdk1.7不好为了一个Lucene就升级到1.8所以决定改用5.5.4版本。 正式开始 下载 从网上下载的包一般比较大有70多M官网目前只能下载最新版的5.x的估计要到其它地方下载一般人只用下面这几个就够了 也就是这几个 其中IKAnalyzer2012_FF.jar是一个国人写的中文分词工具Lucene自带的分词对中文支持不好。注意这个jar包网上比较乱随便从网上下载的话可能不兼容因为跟具体的Lucene版本有关初学者建议直接用我demo里面整理好的jar包https://github.com/liuxianan/lucene-demo/tree/master/WebContent/WEB-INF/lib 建立索引 特别注意Lucene不同版本的API变化比较大如果你用的是其它版本注意代码可能要变。 其实代码比较简单我们先来一个搜索文件的例子下面的FileUtil可以自己简单实现。 public static final String INDEX_PATH E:\\lucene; // 存放Lucene索引文件的位置
public static final String SCAN_PATH E:\\text; // 需要被扫描的位置测试的时候记得多在这下面放一些文件/*** 创建索引*/
public void creatIndex()
{IndexWriter indexWriter null;try{Directory directory FSDirectory.open(FileSystems.getDefault().getPath(INDEX_PATH));//Analyzer analyzer new StandardAnalyzer();Analyzer analyzer new IKAnalyzer(true);IndexWriterConfig indexWriterConfig new IndexWriterConfig(analyzer);indexWriter new IndexWriter(directory, indexWriterConfig);indexWriter.deleteAll();// 清除以前的index// 获取被扫描目录下的所有文件包括子目录ListFile files FileUtil.listAllFiles(SCAN_PATH);for(int i0; ifiles.size(); i){Document document new Document();File file files.get(i);document.add(new Field(content, FileUtil.readFile(file.getAbsolutePath()), TextField.TYPE_STORED));document.add(new Field(fileName, file.getName(), TextField.TYPE_STORED));document.add(new Field(filePath, file.getAbsolutePath(), TextField.TYPE_STORED));document.add(new Field(updateTime, file.lastModified(), TextField.TYPE_STORED));indexWriter.addDocument(document);}}catch (Exception e){e.printStackTrace();}finally{try{if(indexWriter ! null) indexWriter.close();}catch (Exception e){e.printStackTrace();}}
} 执行完之后就在指定目录新建了索引文件以后的搜索就靠他们了 简单的搜索 代码比较简单具体可以看注释这里就不详述了。 /*** 搜索*/
public void search(String keyWord)
{DirectoryReader directoryReader null;try{// 1、创建DirectoryDirectory directory FSDirectory.open(FileSystems.getDefault().getPath(INDEX_PATH));// 2、创建IndexReaderdirectoryReader DirectoryReader.open(directory);// 3、根据IndexReader创建IndexSearchIndexSearcher indexSearcher new IndexSearcher(directoryReader);// 4、创建搜索的Query// Analyzer analyzer new StandardAnalyzer();Analyzer analyzer new IKAnalyzer(true); // 使用IK分词// 简单的查询创建Query表示搜索域为content包含keyWord的文档//Query query new QueryParser(content, analyzer).parse(keyWord);String[] fields {fileName, content}; // 要搜索的字段一般搜索时都不会只搜索一个字段// 字段之间的与或非关系MUST表示andMUST_NOT表示notSHOULD表示or有几个fields就必须有几个clausesBooleanClause.Occur[] clauses {BooleanClause.Occur.SHOULD, BooleanClause.Occur.SHOULD};// MultiFieldQueryParser表示多个域解析 同时可以解析含空格的字符串如果我们搜索上海 中国 Query multiFieldQuery MultiFieldQueryParser.parse(keyWord, fields, clauses, analyzer);// 5、根据searcher搜索并且返回TopDocsTopDocs topDocs indexSearcher.search(multiFieldQuery, 100); // 搜索前100条结果System.out.println(共找到匹配处 topDocs.totalHits); // totalHits和scoreDocs.length的区别还没搞明白// 6、根据TopDocs获取ScoreDoc对象ScoreDoc[] scoreDocs topDocs.scoreDocs;System.out.println(共找到匹配文档数 scoreDocs.length);QueryScorer scorer new QueryScorer(multiFieldQuery, content);// 自定义高亮代码SimpleHTMLFormatter htmlFormatter new SimpleHTMLFormatter(span style\backgroud:red\, /span);Highlighter highlighter new Highlighter(htmlFormatter, scorer);highlighter.setTextFragmenter(new SimpleSpanFragmenter(scorer));for (ScoreDoc scoreDoc : scoreDocs){// 7、根据searcher和ScoreDoc对象获取具体的Document对象Document document indexSearcher.doc(scoreDoc.doc);//TokenStream tokenStream new SimpleAnalyzer().tokenStream(content, new StringReader(content));//TokenSources.getTokenStream(content, tvFields, content, analyzer, 100);//TokenStream tokenStream TokenSources.getAnyTokenStream(indexSearcher.getIndexReader(), scoreDoc.doc, content, document, analyzer);//System.out.println(highlighter.getBestFragment(tokenStream, content));System.out.println(-----------------------------------------);System.out.println(document.get(fileName) : document.get(filePath));System.out.println(highlighter.getBestFragment(analyzer, content, document.get(content)));System.out.println();}}catch (Exception e){e.printStackTrace();}finally{try{if(directoryReader ! null) directoryReader.close();}catch (Exception e){e.printStackTrace();}}
} 测试 public static void main(String args[])
{FileSearchDemo demo new FileSearchDemo();demo.creatIndex();demo.search(读取 导出);
} 稍微复杂一点的搜索 很多时候搜索时可能需要多个条件配合就像我们的SQL查询一样不然无法满足我们的业务。Lucene可以将多个query通过BooleanQuery进行与或非处理得到最终的query。其实再复杂一点的我也没试过下面只是一个简单的示例 String[] fields {fileName, content}; // 要搜索的字段一般搜索时都不会只搜索一个字段
// 字段之间的与或非关系MUST表示andMUST_NOT表示notSHOULD表示or有几个fields就必须有几个clauses
BooleanClause.Occur[] clauses {BooleanClause.Occur.SHOULD, BooleanClause.Occur.SHOULD};
// MultiFieldQueryParser表示多个域解析 同时可以解析含空格的字符串如果我们搜索上海 中国
Query multiFieldQuery MultiFieldQueryParser.parse(keyWord, fields, clauses, analyzer);
Query termQuery new TermQuery(new Term(content, keyWord));// 词语搜索,完全匹配,搜索具体的域
Query wildqQuery new WildcardQuery(new Term(content, keyWord));// 通配符查询
Query prefixQuery new PrefixQuery(new Term(content, keyWord));// 字段前缀搜索
Query fuzzyQuery new FuzzyQuery(new Term(content, keyWord));// 相似度查询,模糊查询比如OpenOfficaOpenOffice
BooleanQuery.Builder queryBuilder new BooleanQuery.Builder();
queryBuilder.add(multiFieldQuery, BooleanClause.Occur.SHOULD);
queryBuilder.add(termQuery, BooleanClause.Occur.SHOULD);
queryBuilder.add(wildqQuery, BooleanClause.Occur.SHOULD);
queryBuilder.add(prefixQuery, BooleanClause.Occur.SHOULD);
queryBuilder.add(fuzzyQuery, BooleanClause.Occur.SHOULD);
BooleanQuery query queryBuilder.build(); // 这才是最终的query
TopDocs topDocs indexSearcher.search(query, 100); // 搜索前100条结果 复杂的搜索还有可能涉及多个索引目录的搜索不同结果的权重分配、排序近义词搜索等等这里就不多说了本文只是入门而已。 数据库搜索 其实和文件搜索差不多只不过建立索引时是从数据库读取内容我也写了一个简单的数据库搜索示例可以从前面提到的demo找到https://github.com/liuxianan/lucene-demo/blob/master/src/com/test/DbSearchDemo.java 这里不细述。 运行效果如下 共找到匹配处1
共找到匹配文档数1
-----------------------------------------
文章标题Android原生与JS交互总结
文章地址http://blog.liuxianan.com/android-native-js-interactive.html
文章内容
test.testBoolean(false); // 输出boolean:null
可以发现如果span stylebackgroud:redAndroid/span这边参数使用了包装类型会导致参数接收不到必须使用基本类型把上面的 基于Lucene实现博客搜索功能 前面都只是例子下面要试着把它用于正式的项目中。 创建索引的时机 首先写一个LuceneService类这里面只有2个方法一个是创建索引一个是搜索那么在什么时候创建索引呢 我在SpringMVC的监听器里面加入了段代码在系统启动时主动创建一次索引另外每24小时再自动更新一次防止万一。为保证实时更新添加文章、修改文章、删除文章之后也都立即更新一次索引。 /*** 更新Lucene索引* param event*/
public void updateLuceneIndex(final ServletContextEvent event)
{luceneTimer new Timer(Lucene索引定时构建任务, true);log.debug(启动Lucene索引构建定时任务);ApplicationContext context WebApplicationContextUtils.getWebApplicationContext(event.getServletContext());final LuceneService luceneService context.getBean(LuceneService.class);// 系统启动1分钟之后主动建立一次Lucene索引luceneTimer.schedule(new TimerTask(){Overridepublic void run(){luceneService.updateIndex(event.getServletContext());}}, 1000 * 60, 1000 * 60 * 60 * 24);
} 必须要开新线程执行 经过测试对于博文内容不是很多的情况下一般建立索引都在数秒之内虽然比较快但还是要避免阻塞主线程这里我偷懒简单的用new Thread来实现 /*** 创建索引发布文章、修改文章、删除文章之后都应记得更新索引*/
public void updateIndex(final ServletContext application)
{new Thread(new Runnable(){Overridepublic void run(){try{Thread.sleep(3000); // 由于新增、修改文章之后立即更新索引可能太数据库还未写入所以延迟一段时间执行}catch (InterruptedException e){e.printStackTrace();}// 创建索引一般需要数秒种为避免阻塞主线程影响业务开启新线程执行createIndexSingleThread(application);}}).start();
} 如何搜索HTML或markdown 由于我的数据库存放的是markdown这里着重考虑一下后面这个问题虽然markdown已经和纯文本差不多了但是在搜索摘要里面显示一大堆类似# 这是一级标题这样的东西也是不爽的我没有找到合适的将markdown过滤为纯文本的工具类只能自己简单写一个真的是太简单简单到我的博客里面主要哪种类型的markdown标记我就过滤什么样的标记其它都没管这个方法肯定还有很多问题目前只要能满足我的需求就足够了如果有谁有好的工具欢迎推荐。另外一个就是注意替换HTML的标签 /*** 简单地过滤markdown标记使之成为纯文本主要用在摘要和搜索的场景* param md* return*/
public static String markdownToText(String md)
{if(StringUtil.isEmpty(md)) return ;md md.replaceAll((^|\n|\r\n)#{1,6} *, $1); // 去除 #md md.replaceAll((^|\n|\r\n)\\* *, $1); // 去除 *md md.replaceAll((^|\n|\r\n) *, $1); // 去除 引用md md.replaceAll((^|\n|\r\n)\\w*?(\n|\r\n)([\\s\\S]?), $2$3); // 去除代码块md md.replaceAll(([^]?), $1); // 去除行内 codemd md.replaceAll(!\\[(.*?)\\]\\(.?\\), $1); // 去除 imgmd md.replaceAll(\\[(.*?)\\]\\(.?\\), $1); // 去除 超链接md md.replaceAll(, );md md.replaceAll(, ); // 替换HTML标签return md;
} 如果是数据库存放的是HTML可以用一些开源库把它转换成纯文本再建立索引比如jsoup。 分页 官方建议一次性全部查出来然后再自己分页而且如果你要知道总页数也只能这么干。虽然还有一个searchAfter方法但是对于这里没啥用。 不同用户显示不同内容 比如有一些仅自己可见的文章我希望当我登录了时可以被搜索到没有登录时不能搜索可以这样实现 BooleanQuery.Builder queryBuilder new BooleanQuery.Builder();
queryBuilder.add(multiFieldQuery, BooleanClause.Occur.MUST);
if(user null)
{// 未登录用户只能查询公开的文章Query termQuery new TermQuery(new Term(permission, pub)); // term表示准确搜索queryBuilder.add(termQuery, BooleanClause.Occur.MUST);
}
BooleanQuery query queryBuilder.build(); 效果体验 可以访问我的博客 http://blog.liuxianan.com 然后双击Ctrl即可搜索。 结束语 由于时间匆忙目前草草地实现了搜索功能后续发现问题再慢慢优化吧毕竟这不是主业已转前端没那么多时间搞这东西。 搜索效果文章最前面已经给出了仿百度做的哈哈 本文是面向入门级别的想深入学习可以参考这位仁兄的系列文章 http://blog.csdn.net/wuyinggui10000/article/category/3173543