【Lucene4.8教程之二】索引

一、基础内容

0、官方文档说明

(1)org.apache.lucene.index provides two primary classes:
IndexWriter, which creates and adds documents to indices; and
IndexReader, which accesses the data in the index.

(2)涉及的两个主要包有:

org.apache.lucene.index:Code to maintain and access indices.

org.apache.lucene.document:Thelogical representation of a Document for indexing and searching.

1、创建一个索引时,涉及的重要类有下面几个:

(1)IndexWriter:索引过程中的核心组件,用于创建新索引或者打开已有索引。以及向索引中加入、删除、更新被索引文档的信息。

(2)Document:代表一些域(field)的集合。

(3)Field及其子类:一个域,如文档创建时间,作者。内容等。

(4)Analyzer:分析器。

(5)Directory:可用于描写叙述Lucene索引的存放位置。

2、索引文档的基本过程例如以下:

(1)创建索引库IndexWriter

(2)依据文件创建文档Document

(3)向索引库中写入文档内容

基本程序例如以下:

package org.jediael.search.index;

import java.io.File;
import java.io.FileReader;
import java.io.IOException;

import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.LongField;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.jediael.util.LoadProperties;

// 1、创建索引库IndexWriter
// 2、依据文件创建文档Document
// 3、向索引库中写入文档内容

public class IndexFiles {

	private IndexWriter writer = null;

	public void indexAllFileinDirectory(String indexPath, String docsPath)
			throws IOException {
		// 获取放置待索引文件的位置。若传入參数为空,则读取search.properties中设置的默认值。

if (docsPath == null) {
			docsPath = LoadProperties.getProperties("docsDir");
		}
		final File docDir = new File(docsPath);
		if (!docDir.exists() || !docDir.canRead()) {
			System.out
					.println("Document directory '"
							+ docDir.getAbsolutePath()
							+ "' does not exist or is not readable, please check the path");
			System.exit(1);
		}

		// 获取放置索引文件的位置,若传入參数为空。则读取search.properties中设置的默认值。
		if (indexPath == null) {
			indexPath = LoadProperties.getProperties("indexDir");
		}
		final File indexDir = new File(indexPath);
		if (!indexDir.exists() || !indexDir.canRead()) {
			System.out
					.println("Document directory '"
							+ indexDir.getAbsolutePath()
							+ "' does not exist or is not readable, please check the path");
			System.exit(1);
		}

		try {
			// 1、创建索引库IndexWriter
			if(writer == null){
				initialIndexWriter(indexDir);
			}
			index(writer, docDir);
		} catch (IOException e) {
			e.printStackTrace();
		} finally{
			writer.close();
		}
	}

	//使用了最简单的单例模式,用于返回一个唯一的IndexWirter。注意此处非线程安全,须要进一步优化。
	private void initialIndexWriter(File indexDir) throws IOException {

		Directory returnIndexDir = FSDirectory.open(indexDir);
		IndexWriterConfig iwc = new IndexWriterConfig(Version.LUCENE_48,new StandardAnalyzer(Version.LUCENE_48));
		writer = new IndexWriter(returnIndexDir, iwc);

	}

	private void index(IndexWriter writer, File filetoIndex) throws IOException {

		if (filetoIndex.isDirectory()) {
			String[] files = filetoIndex.list();
			if (files != null) {
				for (int i = 0; i < files.length; i++) {
					index(writer, new File(filetoIndex, files[i]));
				}
			}
		} else {
			// 2、依据文件创建文档Document,考虑一下是否能不用每次创建Document对象
			Document doc = new Document();
			Field pathField = new StringField("path", filetoIndex.getPath(),
					Field.Store.YES);
			doc.add(pathField);
			doc.add(new LongField("modified", filetoIndex.lastModified(),
					Field.Store.YES));
			doc.add(new StringField("title",filetoIndex.getName(),Field.Store.YES));
			doc.add(new TextField("contents", new FileReader(filetoIndex)));
			//System.out.println("Indexing " + filetoIndex.getName());

			// 3、向索引库中写入文档内容
			writer.addDocument(doc);
		}
	}
}

一些说明:

(1)使用了最简单的单例模式。用于返回一个唯一的IndexWirter,注意此处非线程安全,须要进一步优化。

(2)注意IndexWriter,IndexReader等均须要耗费较大的资源用于创建实例。因此如非必要,使用单例模式创建一个实例后。

3、索引、Document、Filed之间的关系

简而言之,多个Filed组成一个Document,多个Document组成一个索引。

它们之间通过下面方法相互调用:

Document doc = new Document();
Field pathField = new StringField("path", filetoIndex.getPath(),Field.Store.YES);
doc.add(pathField);

writer.addDocument(doc);

二、关于Field

(一)创建一个域(field)的基本方法

1、在Lucene4.x前,使用下面方式创建一个Field:

Field field = new Field("filename", f.getName(),  Field.Store.YES, Field.Index.NOT_ANALYZED);
Field field = new Field("contents", new FileReader(f));
Field field = new Field("fullpath", f.getCanonicalPath(), Field.Store.YES, Field.Index.NOT_ANALYZED)

Filed的四个參数分别代表:

域的名称

域的值

是否保存

是否分析。对于文件名,url。文件路径等内容。不须要对其进行分析。

2、在Lucene4后。定义了大量的Field的实现类型。依据须要,直接使用当中一个,不再使用笼统的Field来直接创建域。

Direct Known Subclasses:

BinaryDocValuesField, DoubleField, FloatField,IntField, LongField, NumericDocValuesField, SortedDocValuesField, SortedSetDocValuesField, StoredField, StringField,TextField
比如,对于上述三个Filed,可对应的改为:
<pre name="code" class="java">Field field = new StringField("path", filetoIndex.getPath(),Field.Store.YES);
Field field = new LongField("modified", filetoIndex.lastModified(),Field.Store.NO);
Field field = new TextField("contents", new FileReader(filetoIndex));

在4.x以后,StringField即为NOT_ANALYZED的(即不正确域的内容进行切割分析),而textField是ANALYZED的,因此,创建Field对象时。无需再指定此属性。见http://stackoverflow.com/questions/19042587/how-to-prevent-a-field-from-not-analyzing-in-lucene

即每个Field的子类均具有默认的是否INDEXED与ANALYZED属性,不再须要显式指定。

官方文档:

StringField: A field that is indexed but not tokenized: the entire String value is indexed as a single token. For example this might be used for a ‘country‘ field or an ‘id‘ field, or any field that you intend to use
for sorting or access through the field cache

TextField: A field that is indexed and tokenized,without term vectors. For example this would be used on a ‘body‘ field, that contains the bulk of a document‘s text.

(二)有关于Field的一些选项

1、Field.Store.Yes/No

在创建一个Field的时候,须要传入一个參数,用于指定内容是否须要存储到索引中。

这些被存储的内容能够在搜索结果中返回,呈现给用户。

二者最直观的差异在于:使用document.get("fileName")时,能否够返回内容。

比方,一个文件的标题通常都是Field.Store.Yes,由于其内容一般须要呈现给用户。文件的作者、摘要等信息也一样。

但一个文件的内容可能就不是必需保存了。一方面是文件内容太大。还有一方面是不是必需在索引中保存其信息,由于能够引导用户进入原有文件就可以。

2、加权

能够对Filed及Document进行加权。注意加权是影响返回结果顺序的一个因素,但也不过一个因素,它和其他因素一起构成了Lucene的排序算法。

(三)对富文本(非纯文本)的索引

上述的对正文的索引语句:

Field field = new TextField("contents", new FileReader(filetoIndex));

仅仅对纯文本有效。

对于word,excel,pdf等富文本。FileReader读取到的内容仅仅是一些乱码。并不能形成有效的索引。

若须要对此类文本进行索引,须要使用Tika等工具先将其正文内容提取出来,然后再进行索引。

http://stackoverflow.com/questions/16640292/lucene-4-2-0-index-pdf

Lucene doesn‘t handle files at all, really. That demo handles plain text files, but core Lucene doesn‘t. FileStreamReader is a Java standard stream reader,
and for your purposes, it will only handle plain text. This works on the Unix philosophy. Lucene indexes content. Tika extracts content from rich documents. I‘ve added links to a couple of examples using Tika, one with Lucene directly, the other using Solr
(which you might want to consider as well).

一个简单示比例如以下:

首先使用Tika提取word中的正文,再使用TextField索引文字。

doc.add(new TextField("contents", TikaBasicUtil.extractContent(filetoIndex),Field.Store.NO));

注意此处不能使用StringField。由于StringField限制了字符串的大小不能超过32766,否则会报异常IllegalArgumentException:Document contains at least one immense term in field="contents" (whose UTF8 encoding is longer than the max length 32766)*/

使用Tika索引富文本的简单示比例如以下:

注意,此演示样例不仅能够索引word。还能够索引pdf,excel等。

package org.jediael.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

import org.apache.tika.exception.TikaException;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.parser.AutoDetectParser;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.Parser;
import org.apache.tika.sax.BodyContentHandler;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;

public class TikaBasicUtil {

	public static String extractContent(File f) {
		//1、创建一个parser
		Parser parser = new AutoDetectParser();
		InputStream is = null;
		try {
			Metadata metadata = new Metadata();
			metadata.set(Metadata.RESOURCE_NAME_KEY, f.getName());
			is = new FileInputStream(f);
			ContentHandler handler = new BodyContentHandler();
			ParseContext context = new ParseContext();
			context.set(Parser.class,parser);

			//2、运行parser的parse()方法。
			parser.parse(is,handler, metadata,context);

			String returnString = handler.toString();

			System.out.println(returnString.length());
			return returnString;
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (TikaException e) {
			e.printStackTrace();
		}finally {
			try {
				if(is!=null) is.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return "No Contents";
	}
}

三、关于Document

FSDocument RAMDocument

四、关于IndexWriter

1、创建一个IndexWriter

		Directory returnIndexDir = FSDirectory.open(indexDir);
		IndexWriterConfig iwc = new IndexWriterConfig(Version.LUCENE_48,new StandardAnalyzer(Version.LUCENE_48));
		iwc.setOpenMode(IndexWriterConfig.OpenMode.CREATE);
		writer = new IndexWriter(returnIndexDir, iwc);
		System.out.println(writer.getConfig().getOpenMode()+"");
		System.out.println(iwc.getOpenMode());

创建一个IndexWriter时,须要2个參数,一个是Directory对象,用于指定所创建的索引写到哪个地方。还有一个是IndexWriterConfig对象,用于指定writer的配置。

2、IndexWriterConfig

(1)继承关系

  • All Implemented Interfaces:
    Cloneable
    (2)Holds all the configuration that is used to create an IndexWriter.
    Once IndexWriter has
    been created with this object, changes to this object will not affect the IndexWriterinstance.
    (3)IndexWriterConfig.OpenMode:指明了打开索引文件夹的方式,有下面三种:
    APPEND:Opens an existing index. 若原来存在索引,则将本次索引的内容追加进来。无论文档是否与原来是否反复。因此若2次索引的文档同样,则返回结果数则为原来的2倍。
    CREATE:Creates a new index or overwrites an existing one. 若原来存在索引,则先将其删除,再创建新的索引
    CREATE_OR_APPEND【默认值】:Creates a new index if one does not exist, otherwise it opens the index and documents will be appended.

3、索引的优化

索引过程中,会将索引结果存放至多个索引文件里,这样会回收索引的效率。但在搜索时,须要将多个索引文件里的返回结果进行合并处理。因此效率较低。

为了加快搜索结果的返回。能够将索引进行优化。

writer.addDocument(doc);
writer.forceMerge(2);

索引的优化是将索引结果文件归为一个或者有限的多个,它加大的索引过程中的消耗,降低了搜索时的消耗。

五、关于Analyzer

此处主要关于和索引期间相关的analyzer,关于analyzer更具体的内容请參见 http://blog.csdn.net/jediael_lu/article/details/33303499  【Lucene4.8教程之四】分析

在创建IndexWriter时。须要指定分析器。如:

IndexWriterConfig iwc = new IndexWriterConfig(Version.LUCENE_48,new StandardAnalyzer(Version.LUCENE_48));
writer = new IndexWriter(IndexDir, iwc);

便在每次向writer中加入文档时,能够针对该文档指定一个分析器,如

writer.addDocument(doc, new SimpleAnalyzer(Version.LUCENE_48));

六、关于Directory

时间: 2024-11-05 15:51:44

【Lucene4.8教程之二】索引的相关文章

【Lucene4.8教程之二】域(Field)的用法

1.在Lucene4.x前,使用以下方式创建一个Field: Field field = new Field("filename", f.getName(), Field.Store.YES, Field.Index.NOT_ANALYZED); Field field = new Field("contents", new FileReader(f)); Field field = new Field("fullpath", f.getCano

屌炸天实战 MySQL 系列教程(二) 史上最屌、你不知道的数据库操作

此篇写MySQL中最基础,也是最重要的操作! 第一篇:屌炸天实战 MySQL 系列教程(一) 生产标准线上环境安装配置案例及棘手问题解决 第二篇:屌炸天实战 MySQL 系列教程(二) 史上最屌.你不知道的数据库操作 本章内容: 查看\创建\使用\删除 数据库 用户管理及授权实战 局域网远程连接法 查看\创建\使用\删除\清空\修改 数据库表(是否可空,默认值,主键,自增,外键) 表内容的增删改查 where条件.通配符_%.限制limit.排序desc\asc.连表join.组合union 查

【Lucene4.8教程之四】分析

1.基础内容 (1)相关概念 分析(Analysis),在Lucene中指的是将域(Field)文本转换成最基本的索引表示单元--项(Term)的过程.在搜索过程中,这些项用于决定什么样的文档能够匹配查词条件. 分析器对分析操作进行了封装,它通过执行若干操作,将文本转化成语汇单元,这个处理过程也称为语汇单元化过程(tokenization),而从文本洲中提取的文本块称为语汇单元(token).词汇单元与它的域名结合后,就形成了项. (2)何时使用分析器 建立索引期间 Directory retu

Senparc.Weixin.MP SDK 微信公众平台开发教程(二):成为开发者

Senparc.Weixin.MP SDK 微信公众平台开发教程(二):成为开发者 这一篇主要讲作为一名使用公众平台接口的开发者,你需要知道的一些东西.其中也涉及到一些微信官方的规定或比较掩蔽的注意点.欢迎补充! 我觉得做好成为开发者的准备比稀里糊涂开通微信后台的"高级"功能更重要,所以这一节先放在前面说. 一.公众平台的通讯过程 作为开发者,我们需要面对的主要有两个对象:微信服务器和应用程序(网站)服务器. 当微信用户向你的公众平台发送一条消息,实际上这条消息首先发送到微信服务器,由

【Lucene4.8教程之三】搜索

1.关键类 Lucene的搜索过程中涉及的主要类有以下几个: (1)IndexSearcher:执行search()方法的类 (2)IndexReader:对索引文件进行读操作,并为IndexSearcher提供搜索接口 (3)Query及其子类:查询对象,search()方法的重要参数 (4)QueryParser:根据用户输入的搜索词汇生成Query对象. (5)TopDocs:search()方法返回的前n个文档 (6)ScoreDocs:提供TopDocs中搜索结果的访问接口 2.搜索的

Photoshop入门教程(二):暂存盘设置与标尺设置

新建文档之后大家就可以对图像进行编辑.在对图像进行编辑之前,先来了解一下如何查看图像的一些基本信息.在软件左下角,会有这样的信息显示窗口. 1窗口表示当前图像显示比例,200%代表当前为放大两倍显示.左键双击可修改显示比例. 2窗口显示当前文档所占空间,鼠标放到图示位置按住左键不放,出现3窗口,显示当前图像信息. 暂存盘设置 Photoshop暂存盘默认情况下是在C盘,但是C盘作为系统盘空间有限,当处理大型文档时,C盘空间就会不够用.同时系统响应速度也会下降.那么如何修改暂存盘? 菜单栏:编辑—

《80X86汇编语言程序设计教程》二十三 分页管理机制实例

1.  理论知识参考"<80X86汇编语言程序设计教程>二十二 分页管理机制与虚拟8086模式".演示分页机制实例:初始化页目录表和部分页表:启用分页管理机制:关闭分页管理机制等.逻辑功能:在屏幕上显示一条表示已启用分页管理机制的提示信息.大体步骤是:在实模式下拷贝显示串程序的代码到预定义区域,转保护模式,初始化页目录和2个页表,开启分页机制,转入预定义区执行显示代码,然后关闭分页机制,重新回到实模式,程序终止. 2.  源代码 "386scd.asm"

Senparc.Weixin.MP SDK 微信公众平台开发教程(二十一):在小程序中使用 WebSocket (.NET Core)

本文将介绍如何在 .NET Core 环境下,借助 SignalR 在小程序内使用 WebSocket.关于 WebSocket 和 SignalR 的基础理论知识不在这里展开,已经有足够的参考资料,例如参考 SignalR 的官方教程:https://docs.microsoft.com/zh-cn/aspnet/core/signalr/introduction?view=aspnetcore-2.1 我们先看一下完成本教程内容后,在小程序内实现的 WebSocket 效果: 私有及群发消息

Windows server 2012 搭建VPN图文教程(二)配置路由和远程访问服务

Windows server 2012 搭建VPN图文教程(一)安装VPN相关服务 Windows server 2012 搭建VPN图文教程(二)配置路由和远程访问服务 Windows server 2012 搭建VPN图文教程(三)配置VPN访问账户 Windows server 2012 搭建VPN图文教程(四)客户端访问VPN测试 PartII 配置路由和远程访问服务 本部分主要介绍如何安装和配置路由及远程访问服务的方法,请参考以下操作步骤: (续上)前面提到重新启动操作系统,重启后服务