再入lucene

        相信很多人都听过lucene,这样一个用于实现搜索引擎功能的一个工具包。说它是一个工具包,因为它只是提供我们用于索引和查询的工具,并不包含真正一个搜索引擎需要的其他东西——爬虫,抽取等等。

        废话不多说,我们直接来看看lucene4的一些简单的例子(lucene4.1已经出了,暂时没来得及看有什么变化,lucene的API经常修改,所以版本间可能会有些不同的,大家需要注意):

        还是给下lucene的下载地址,有些朋友喜欢用百度的,但那家伙太坑人了,很多外国网站没的。下载地址如下:http://archive.apache.org/dist/lucene/java/4.0.0/

        下载后,我们可以看到有一堆的文件夹,首先demo里面当然就是例子了,这个不多说。看看还是有好处的。
再入lucene_第1张图片

        我们经常使用的无非是下面几个:queryparser,analysis,core(核心,显然是必须的了),queries,其他的貌似比较少用到,除非你进行比较深入的定制,可能会用到suggest(建议方面的东西),其他的因为我暂时没接触到,就不在这里误人子弟了,以后有机会再学习。

        好,接下来来点干货了。我在例子中用到的包有analysis,queryparser,core,另外的暂没有使用。说到底是一个索引工具包,首先当然是建立索引啦,
        比如我现在在ubuntu下,我的eclipse目录是/opt/programs/eclipse,我要索引它里面的所有文件的文件名,放在一个名叫fileName的field里面。

        在看代码前,我们先来了解一下lucene索引的结构:

        一个索引是由无数多个doc组成的,而doc也是由无数多个field组成的。举个比较符合生活的例子:我们可以把一个索引想像成一本书,而doc则是每一本书的第一篇文章,而field则是每一篇文章里面的词语,词组,我们就是通过词组去找到那篇文章。(当然,说根据词组等去找一篇文章不太可行,我们可以把词语换成标题,每一篇文章的标题,这样肯定可以找到对应的文章的。)

        进行lucene检索的情况就是查找field,如果找到对应的,则把该doc取出来,作为我们查询出来的结果。

        1)首先我们看看索引情况:

        

package com.shun.lucene.simple;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;

public class IndexAllFiles {

	public static void main(String[] args) {
		Directory dir = null;
		IndexWriter indexWriter = null;
		Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_40);
		try {
			dir = FSDirectory.open(new File("allFiles"));
			
			IndexWriterConfig iwc = new IndexWriterConfig(Version.LUCENE_40,analyzer);
			/*
			 * 如果索引存在的情况下,我们采取什么样的策略
			 * 这里我们使用的是:存在的情况下新增
			 * 不存在的情况下创建
			 * 这符合我们一般的使用规则
			*/
			iwc.setOpenMode(OpenMode.CREATE);
			indexWriter = new IndexWriter(dir,iwc);
			File file = new File("/opt/programs/eclipse");
			System.out.println("Starting analyze fileNames...Please wait...");
			List<String> fileNameList = checkAllFile(file);
			System.out.println("Ending analyze fileNames...");
			for (String fileName:fileNameList) {
				/*
				 * field就是lucene里面的最小单位了,一个索引里面可以有一大堆Document,而一个document里面可以有很多field
				 * 这跟文章类似,一本书里面有很多篇文章,而一篇文章里面可以有很多单词,词组等。
				 */
				Document doc = new Document();
				Field field = new Field("fileName",fileName.toString(),TextField.TYPE_STORED);
				doc.add(field);
				indexWriter.addDocument(doc);
			}
			System.out.println("Writing index to file");
			System.out.println("Finish indexing");
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				indexWriter.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * 此方法只是循环列出所传入的文件夹下的所有文件列表
	 * @param dir
	 * @return
	 */
	private static List<String> checkAllFile(File dir) {
		List<String> fileNameList = new ArrayList<String>();
		//不是目录的话里面肯定没文件了,我们在下面直接添加该文件到列表就OK了
		if (dir.isDirectory()) {
			for (File file:dir.listFiles()) {
				//由于我在linux下,并且当前运行eclipse的用户非root,会有权限的问题,所以我这里加了一个判断是否可读
				//正常情况下在windows上不需要,只需要判断是否是目录,然后递归调用即可。
				if(file.isDirectory() && file.canRead()) {
					fileNameList.addAll(checkAllFile(file));
				} else {
					fileNameList.add(file.getName());
				}
			}
		} else {
			fileNameList.add(dir.getName());
		}
		return fileNameList;
	}

}
         相信注释已经够清楚了吧。

 

         这里有个地方需要解释下:

for (String fileName:fileNameList) {
/*
* field就是lucene里面的最小单位了,一个索引里面可以有一大堆Document,而一个document里面可以有很多field
 * 这跟文章类似,一本书里面有很多篇文章,而一篇文章里面可以有很多单词,词组等。
*/
	Document doc = new Document();
	Field field = new Field("fileName",fileName.toString(),TextField.TYPE_STORED);
	doc.add(field);
	indexWriter.addDocument(doc);
}

         我们这里是通过多个doc来添加的,注意,我们频繁的addDocument可能效率不高,我们其实可以把所有的fileName放在一个doc里面,doc并不像我们的map,它不会合并相同的名称的field的。我们稍后来看看这种情况。

         2)有了索引之后,我们肯定就是需要在索引当中找我们需要的东西了。我们搜索一个eclipse这个单词,当然,我们前面只有一个fileName这个field,没有其他的,也只能查那一个了。

package com.shun.lucene.simple;

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

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.index.DirectoryReader;
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.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;

public class ReadFromIndex {

	public static void main(String[] args) {
		Directory dir = null;
		Analyzer analyzer = new StandardAnalyzer(Version.LUCENE_40);
		try {
			//这里打开的当然就是我们之前建立的索引文件夹了
			dir = FSDirectory.open(new File("allFiles"));
			//DirectoryReader这个是lucene的一个工具类,在以前的IndexSearcher里面,貌似不用使用这个
			IndexSearcher searcher = new IndexSearcher(DirectoryReader.open(dir));
			QueryParser parser = new QueryParser(Version.LUCENE_40,"fileName",analyzer);
			Query query = parser.parse("eclipse");
			TopDocs topDocs = searcher.search(query, 10);
			System.out.println("找到结果数:"+topDocs.totalHits);
			for (ScoreDoc scoreDoc:topDocs.scoreDocs) {
				System.out.println(searcher.doc(scoreDoc.doc).get("fileName"));
			}
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ParseException e) {
			e.printStackTrace();
		}
	}

}

         我们可以看到,查询很简单,只是指定一个索引所在目录,然后来个IndexSearcher,再结合几个QueryParser和Query,然后就搞定了。

         结果就不截图了,大家私下去运行下。我们来看看索引里面的内容,这里又涉及到另外的工具了,luke,这个是用于看索引的工具,可以到这里去下载:http://code.google.com/p/luke/downloads/list。但到了4.0.0alpha就没更新好久了,好在alpha对4.0的正式版还能用。

        我们打开我们的索引,可以看到doc的内容:

再入lucene_第2张图片
         在这里,我们可以看到doc的数量,2835,还有doc里面的field名称和value,另外的IdfXXX和Norm这个大家有兴趣自己可以去研究,是lucene的一些属性信息,包含是否索引、分词等等。

 

        这里,我们是通过多个doc来添加的,我们上面说了,其他我们完全可以把多个field添加到同一个doc里面,虽然同一个fielName,但无所谓,因为索引并不是map形式的,不会覆盖相应的值。

        我们回到上面的创建索引的例子,我们把循环的代码修改如下:

Document doc = new Document();
for (String fileName:fileNameList) {
	/*
	 * field就是lucene里面的最小单位了,一个索引里面可以有一大堆Document,而一个document里面可以有很多field
	* 这跟文章类似,一本书里面有很多篇文章,而一篇文章里面可以有很多单词,词组等。
	 */
Field field = new Field("fileName",fileName.toString(),TextField.TYPE_STORED);
				doc.add(field);
}
indexWriter.addDocument(doc);

         我们把Document提出去了,并且只在最后的时候来一个addDocument,添加到indexWriter中去。这里,我们可以点击luke的File->Re-open current index来重新打开当前的索引。我们可以看到:

再入lucene_第3张图片
         我们可以看到,doc只剩一个了,而里面的fileName却有一大堆了。

         当然,我们在一个doc里面添加了多个同样名字的filedName之后,在检索的时候通过searcher.doc(scoreDoc.doc).get("xxx")这样取出来的只能是第一个了,我们可以换成getValues("xxx")这样就可以取出数组,再进行遍历。

 

 

         说到了索引,当然少不了分词了,中文分词比较好的有,IKAnalyzer,Paoding分词,另外的一些由于没用过不大清楚,大家可以在 网上找找。使用起来都比较简单,这里不说了。我们例子中用到了StandardAnalyzer,这里我们再说一下stopWords这个定义,对于非中文的,分词一般会根据一个stopWords列表来进行分词,比如遇到the,a,an等会自动忽略,并且会把左右的单词分开索引。
        我们可以找到StopAnalyzer这个类,可以看到它定义有一组默认的stopWords。
final List<String> stopWords = Arrays.asList(
      "a", "an", "and", "are", "as", "at", "be", "but", "by",
      "for", "if", "in", "into", "is", "it",
      "no", "not", "of", "on", "or", "such",
      "that", "the", "their", "then", "there", "these",
      "they", "this", "to", "was", "will", "with"
    );
         中文的当然比这个复杂多了,这个大家可以去看看中文分词的处理,实际上使用的时候只是实例化的时候换一个Analyzer而已,使用很简单。

你可能感兴趣的:(再入lucene)