搜索引擎SOLR

搜索引擎SOLR

 

1.Lucene的介绍

lucene是一款高性能的、可扩展,纯java语言编写的信息检索(IR)工具库。

它适合几乎任何需要全文本搜索(特别是跨平台)的应用程序。

 

官网:http://lucene.apache.org/java

下载地址http://mirror.bit.edu.cn/apache/lucene/java/

所有版本:http://archive.apache.org/dist/lucene/java/

 

2.Lucene jar包及说明

lucene-core-3.1.0.jar 核心包

lucene-highlighter-3.1.0.jar 高亮包

lucene-analyzers-3.1.0.jar Lucene自带的分词解析包:

lucene-queries-3.1.0.jar搜索条件包

IKAnalyzer3.2.3Stable.jar IK中文分词包

 

3.Lucene原理

lucene是基于关键词索引和查询.

全文分析:把文本解析为一个个关键字存储到索引文件中。

倒排索引 (英语:Inverted index),也常被称为反向索引、置入档案或反向档案,是一种索引方法,被用来存储在全文搜索下某个单词在一个文档或者一组文档中的存储位置的映射。它是文档检索系统中最常用的数据结构。

设有两篇文章12
文章1的内容为:

Tom lives in Guangzhou,I live in Guangzhou too.
文章2的内容为:

He once lived in Shanghai.

倒排索引的原理图:

 

 

加入依赖

<dependency>

    <groupId>com.janeluo</groupId>

    <artifactId>ikanalyzer</artifactId>

    <version>2012_u6</version>

</dependency>



简单的分词和获取搜索

package cn.et;

 

import java.io.File;

import java.io.IOException;

import java.io.StringReader;

 

import org.apache.lucene.analysis.Analyzer;

import org.apache.lucene.analysis.Tokenizer;

import org.apache.lucene.document.Document;

import org.apache.lucene.document.Field;

import org.apache.lucene.document.TextField;

import org.apache.lucene.index.DirectoryReader;

import org.apache.lucene.index.IndexWriter;

import org.apache.lucene.index.IndexWriterConfig;

import org.apache.lucene.queryparser.classic.ParseException;

import org.apache.lucene.queryparser.classic.QueryParser;

import org.apache.lucene.search.IndexSearcher;

import org.apache.lucene.search.Query;

import org.apache.lucene.search.ScoreDoc;

import org.apache.lucene.store.Directory;

import org.apache.lucene.store.FSDirectory;

import org.apache.lucene.util.Version;

import org.wltea.analyzer.lucene.IKAnalyzer;

import org.wltea.analyzer.lucene.IKTokenizer;

 

public class IndexDemo {

static String dir="D:\\index";

//定义分词器

static  Analyzer analyzer = new IKAnalyzer();

public static void main(String[] args) throws IOException, ParseException {

//	write();

search();

//	String str="张三来自湖南是一位程序员";

//	Tokenizer token=new IKTokenizer(new StringReader(str),true);

}

public static void search() throws IOException, ParseException {

Directory directory=FSDirectory.open(new File(dir));

//索引库的存储目录

DirectoryReader ireader = DirectoryReader.open(directory);

//搜索类

IndexSearcher isearcher = new IndexSearcher(ireader);

//lucence查询解析 用于指定查询的属性名和分词器

QueryParser parser = new QueryParser(Version.LUCENE_47, "userDesc", analyzer);

Query query = parser.parse("来");

//获取搜索的结果  制定返回的document个数

ScoreDoc[] hits = isearcher.search(query, null, 10).scoreDocs;

    

    // Iterate through the results:

    for (int i = 0; i < hits.length; i++) {

      Document hitDoc = isearcher.doc(hits[i].doc);

      System.out.println(hitDoc.getField("userName").stringValue());

    }

    ireader.close();

    directory.close();

}

/**

 * 创建索引库

 * @throws IOException

 */

public static void write() throws IOException{

//索引库的存储目录

Directory directory=FSDirectory.open(new File(dir));

//关联lucence版本和当前分词器

IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_47, analyzer);

//传入写入的目录和分词器

IndexWriter iwriter = new IndexWriter(directory, config);

//document对象field属性

Document doc=new Document();

Field field=new Field("userName","张三",TextField.TYPE_STORED);

doc.add(field);

field=new Field("userDesc","张三来自湖南是一位程序员",TextField.TYPE_STORED);

doc.add(field);

iwriter.addDocument(doc);

Document doc1=new Document();

Field field1=new Field("userName","李四",TextField.TYPE_STORED);

doc1.add(field1);

field1=new Field("userDesc","李四来自湖南是一位白领",TextField.TYPE_STORED);

doc1.add(field1);

iwriter.addDocument(doc1);

iwriter.commit();

iwriter.close();

}

}


 

分词和高亮查询

从数据库获取

package cn.et.food.dao.impl;

 

import java.util.List;

import java.util.Map;

 

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.jdbc.core.JdbcTemplate;

import org.springframework.stereotype.Repository;

 

import cn.et.food.dao.FoodDao;

@Repository

public class FoodDaoImpl implements FoodDao {

@Autowired

private JdbcTemplate jdbc;

/* (non-Javadoc)

 * @see cn.et.food.dao.impl.FoodDao#queryFoodCount()

 */

@Override

public int queryFoodCount(){

String sql="select count(*) as foodCount from food";

return Integer.parseInt(jdbc.queryForList(sql).get(0).get("foodCount").toString());

}

/* (non-Javadoc)

 * @see cn.et.food.dao.impl.FoodDao#queryFood(int, int)

 */

@Override

public List<Map<String, Object>> queryFood(int start,int rows){

String sql="select * from food limit "+start+","+rows;

return jdbc.queryForList(sql);

}

}


 

高亮查询和分词

package cn.et.food.util;

 

 

import java.io.File;

import java.io.IOException;

import java.util.ArrayList;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

 

import org.apache.lucene.analysis.Analyzer;

import org.apache.lucene.analysis.TokenStream;

import org.apache.lucene.document.Document;

import org.apache.lucene.index.DirectoryReader;

import org.apache.lucene.index.IndexWriter;

import org.apache.lucene.index.IndexWriterConfig;

import org.apache.lucene.queryparser.classic.ParseException;

import org.apache.lucene.queryparser.classic.QueryParser;

import org.apache.lucene.search.IndexSearcher;

import org.apache.lucene.search.Query;

import org.apache.lucene.search.ScoreDoc;

import org.apache.lucene.search.highlight.Highlighter;

import org.apache.lucene.search.highlight.InvalidTokenOffsetsException;

import org.apache.lucene.search.highlight.QueryScorer;

import org.apache.lucene.search.highlight.SimpleHTMLFormatter;

import org.apache.lucene.search.highlight.TextFragment;

import org.apache.lucene.search.highlight.TokenSources;

import org.apache.lucene.store.Directory;

import org.apache.lucene.store.FSDirectory;

import org.apache.lucene.util.Version;

import org.wltea.analyzer.lucene.IKAnalyzer;

 

public class LuceneUtils {

static String dir ="D:\\index";

//创建分词器

static Analyzer analyzer=new IKAnalyzer();

public static List<Map> search(String field,String value) throws IOException, ParseException, InvalidTokenOffsetsException {

Directory directory=FSDirectory.open(new File(dir));

//索引库的存储目录

DirectoryReader ireader = DirectoryReader.open(directory);

//搜索类

IndexSearcher isearcher = new IndexSearcher(ireader);

//lucence查询解析 用于指定查询的属性名和分词器

QueryParser parser = new QueryParser(Version.LUCENE_47, "foodname", analyzer);

Query query = parser.parse(value);

//最终结果被分词后添加前缀和后缀的处理类 <B></B>

SimpleHTMLFormatter htmlFormatter = new SimpleHTMLFormatter("<font color=red>","</font>");

//将高亮搜索的词 添加到高亮处理器中

Highlighter highlighter = new Highlighter(htmlFormatter, new QueryScorer(query));

//获取搜索的结果  制定返回的document个数

ScoreDoc[] hits = isearcher.search(query, null, 10).scoreDocs;

List<Map> list=new ArrayList<Map>();

    // Iterate through the results:

    for (int i = 0; i < hits.length; i++) {

      int id=hits[i].doc;

      Document hitDoc = isearcher.doc(hits[i].doc);

      Map map=new HashMap();

      map.put("foodid", hitDoc.get("foodid"));

      String foodname=hitDoc.get("foodname");

      //将查询的结果和搜索词匹配 匹配到添加前缀和后缀高亮

      TokenStream tokenStream = TokenSources.getAnyTokenStream(isearcher.getIndexReader(), id, "foodname", analyzer);

      TextFragment[] frag = highlighter.getBestTextFragments(tokenStream, foodname, false, 10);//highlighter.getBestFragments(tokenStream, text, 3, "...");

      String foodnameHign="";

      for (int j = 0; j < frag.length; j++) {

        if ((frag[j] != null) && (frag[j].getScore() > 0)) {

        	foodnameHign=(frag[j].toString());

          System.out.println((frag[j].toString()));

        }

      }

      map.put("foodname", foodnameHign);

      map.put("price", hitDoc.get("price"));

      map.put("imagepath", hitDoc.get("imagepath"));

      list.add(map);

    }

    ireader.close();

    directory.close();

    

    return list;

}

public static void write(Document doc) throws IOException{

//索引库的存储目录

Directory directory=FSDirectory.open(new File(dir));

//关联lucence版本和当前分词器

IndexWriterConfig config=new IndexWriterConfig(Version.LUCENE_47,analyzer);

//传入目录和分词器

IndexWriter iwriter=new IndexWriter(directory, config);

iwriter.addDocument(doc);

iwriter.commit();

iwriter.close();

}

}


 

 

控制层

package cn.et.food.controller;

 

import java.io.IOException;

import java.util.List;

import java.util.Map;

 

import org.apache.lucene.document.Document;

import org.apache.lucene.document.Field;

import org.apache.lucene.document.TextField;

import org.apache.lucene.queryparser.classic.ParseException;

import org.apache.lucene.search.highlight.InvalidTokenOffsetsException;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.GetMapping;

import org.springframework.web.bind.annotation.RestController;

 

import cn.et.food.dao.FoodDao;

import cn.et.food.util.LuceneUtils;

@RestController

public class FoodController {

@Autowired

FoodDao dao;

@GetMapping("/searchFood")

public List<Map> getFood(String keyWord) throws IOException, ParseException, InvalidTokenOffsetsException{

return LuceneUtils.search("foodname", keyWord);

}

@GetMapping("createIndex")

public String createIndex(){

try {

//数据库查询所有 查询

int queryFoodCount=dao.queryFoodCount();

//第一次拉取 0,1000

//带二次拉取1001,2000

int startIndex=0;

int rows=1000;

while(startIndex<=queryFoodCount){

//每次拉取数据

List<Map<String,Object>> queryFood=dao.queryFood(startIndex, rows);

for(int i=0;i<queryFood.size();i++){

Map<String,Object> mso=queryFood.get(i);

Document doc=new Document();

Field field1=new Field("foodid",mso.get("foodid").toString(),TextField.TYPE_STORED);

Field field2=new Field("foodname",mso.get("foodname").toString(),TextField.TYPE_STORED);

Field field3=new Field("price",mso.get("price").toString(),TextField.TYPE_STORED);

Field field4=new Field("imagepath",mso.get("imagepath").toString(),TextField.TYPE_STORED);

doc.add(field1);

doc.add(field2);

doc.add(field3);

doc.add(field4);

LuceneUtils.write(doc);

}

//写入lucene索引

if(startIndex<rows){

startIndex+=1+rows;

}else{

startIndex+=rows;

}

}

} catch (Exception e) {

// TODO Auto-generated catch block

e.printStackTrace();

return "0";

}

return "1";

}

}


 

 

Apache Solr 是一个开源的搜索服务器,Solr 使用 Java 语言开发,主要基于 HTTP 和 Apache Lucene 实现。定制 Solr 索引的实现方法很简单,用 POST 方法向 Solr 服务器发送一 个描述所有 Field 及其内容的 XML 文档就可以了。定制搜索的时候只需要发送 HTTP GET 请求 即可,然后对 Solr 返回的信息进行重新布局,以产生利于用户理解的页面内容布局。Solr 1.3 版 本开始支持从数据库(通过 JDBC)、 RSS 提要、 Web 页面和文件中导入数据,但是不直接支持从 二进制文件格式中提取内容,比如 MS Office、Adobe PDF 或其他专有格式。 更重要的是,Solr 创建的索引与 Lucene 搜索引擎库完全兼容。 通过对 Solr 进行适当的配置, 某些情况下可能需要进行编码,Solr 可以阅读和使用构建到其他 Lucene 应用程序中的索引。此 外,很多 Lucene 工具(如 Nutch、 Luke)也可以使用 Solr 创建的索引 Solr 的特性包括:  高级的全文搜索功能  专为高通量的网络流量进行的优化  基于开放接口(XML 和 HTTP)的标准  综合的 HTML 管理界面  可伸缩性-能够有效地复制到另外一个 Solr 搜索服务器  使用 XML 配置达到灵活性和适配性  可扩展的插件体系 1.2.1 Solr使用Lucene并且进行了扩展  一个真正的拥有动态域(Dynamic Field)和唯一键(Unique Key)的数据模式(Data Schema)  对 Lucene 查询语言的强大扩展!  支持对结果进行动态的分组和过滤  高级的,可配置的文本分析  高度可配置和可扩展的缓存机制 5/8/2011 Page 4 5/8/20112009-02-17 版权所有,侵权必究All rights reserved 第4页,共56页Page 4 , Total56  性能优化  支持通过 XML 进行外部配置  拥有一个管理界面  可监控的日志  支持高速增量式更新(Fast incremental Updates)和快照发布(Snapshot Distribution) 1.2.2 Schema(模式)  定义域类型和文档的域  能够驱动智能处理  声明式的 Lucene 分析器规范  动态域能够随时增加域  拷贝域功能允许对一个域进行多种方式的索引,或者将多个域联合成一个可搜索的域  显式类型能够减少对域类型的猜测  能够使用外部的基于文件的终止词列表,同义词列表和保护词列表的配置 1.2.3 查询  拥有可配置响应格式(XML/XSLT,JSON,Python,Ruby)的 HTTP 接口  高亮的上下文搜索结果  基于域值和显式查询的片段式搜索(Faceted Search)  对查询语言增加了排序规范  常量的打分范围(Constant scoring range)和前缀式查询-没有 idf,coord,或者 lengthNorm 因子,对查询匹配的词没有数量限制  函数查询(Function Query)-通过关于一个域的数值或顺序的函数对打分进行影响  性能优化 1.2.4 核心  可插拔的查询句柄(Query Handler)和可扩展的 XML 数据格式  使用唯一键的域能够增强文档唯一性  能够高效地进行批量更新和删除  用户可配置的文档索引变化触发器(命令)  并发控制的搜索器  能够正确处理数字类型,从而能够进行排序和范围搜索  能够控制缺失排序域的文档  支持搜索结果的动态分组 1.2.5 缓存  可配置的查询结果,过滤器,和文档缓存实例  可插拔的缓存实现  后台缓存热启:当一个新的搜索器被打开时,可配置的搜索将它热启,避免第一个结果慢 下来,当热启时,当前搜索器处理目前的请求(???)。  后台自动热启:当前搜索器缓存中最常访问的项目在新的搜索器中再次生成,能够在索引 器和搜索器变化的时候高速缓存常查询的结果 5/8/2011 Page 5 5/8/20112009-02-17 版权所有,侵权必究All rights reserved 第5页,共56页Page 5 , Total56  快速和小的过滤器实现  支持自动热启的用户级别的缓存 1.2.6 复制  能够将使用 rsync 传输时改变的索引部分有效的发布  使用拉策略(Pull Strategy)来简化增加搜索器  可配置的发布间隔能够允许对时间线和缓存使用进行权衡选择 1.2.7 管理接口  能够对缓存使用,更新和查询进行综合统计  文本
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值