Lucene初试——关于大文本建立索引和中文乱码以及QueryParser检索的一些体会

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/shine_of_sun/article/details/39226113

这几天因为一个小项目用到Lucene,于是去学习了一下,现在还有很多地方没有了解,先就我遇到的问题做下总结。

一、大文本建索引问题

我这里说的大文本,实际上也就200M左右的txt,或许不应该成为大文本,但是我在建索引时遇到200M左右的的确导致了内存溢出,报错误java.lang.OutOfMemoryError: Java heap space ,到网上查了很久,试了一些方法,比如修改JVM的运行参数等,都不行。我测试的机器为i5四核,4G内存,实测时可用内存1G多,按说对于200M的文本不应该可以接受吗?但是就是出现了内存溢出的情况。在对Lucene的机制还不了解的情况下,我想到了以下几种解决方案,一个是切割文本,将大文本首先预处理以下,分成小的文本,二是在建立索引时,对于大文本分段建立,比如读到50M往磁盘写一次,三是按行建立索引,比如一次读1w行。其实我觉得这些内在都是一个道理,就是一点点切分文本,只是实现方式稍有区别。

第一种情况我没有测试,觉得太麻烦,还要写个独立程序切割文本。第二种方式,我的代码逻辑是,读到一定大小的数据之后,就建立一个Document对象,然后设置setMaxBufferedDocs(n),我实测是10M的时候存一个Document,然后setMaxBufferedDocs(5),根据Lucene的官方文档,当内存中的缓存到达指定大小(我设置的200M)或者doc数目达到指定大小(也就是这里设置的5)时,就会触发一次往磁盘里写数据的操作。我觉的这样的话,内存里顶多只会有5*10=50M,大不了IO太频繁降低速度而已,至少得能跑。但实际测试过程中,发现跑了很久很久都没有把一个文本(170M)索引建好,按照我的理解,170M也就3、4次写操作不就可以了,但事实是很久没出现结果。于是我放弃了这种想法,没再去细究。

后来我用第三种方式测试了下,可行,而且效率还可以。思路是,每读1W行建立一个doc,我的这个文本比较特殊,2W行大概也就1m,然后设置setMaxBufferedDocs(100),这样反而可以。具体的数值设置可以根据自己环境来看,代码我也就不贴了,就是循环按行读取文本,1w次之后建立一个Document对象。

后来因为业务的需求,我又改成了1行建一个Document,然后setMaxBufferedDocs(2W),实测效率也可以。因为我检索时需要每一行的信息,所以只能按行读取。比如我的一行数据为“1052307934----huajun7089059----73.63.134.205----安徽省滁州市电信ADSL----2011年7月10日----14:28:24”,我搜索“安徽省滁州市”如果搜到了这条记录,那么我需要这一行的所有信息,如果不按每一行一个Document的话,比如我现在10行一个Document,那么噪音数据太多了。不知道Lucene有没有提供只提取我需要的信息的功能?要不然我就得自己写,从10行中找到我要的,那样也没有多大意义。

二、中文乱码问题

中文乱码真是无处不在。如果文件编码、系统编码、运行环境编码都一样应该不会出现乱码问题。如果知道文件的编码,一般

reader = new BufferedReader(new InputStreamReader(
							new FileInputStream(file), “gbk”));
类似这样的设置就可以解决,但是我的比较麻烦,因为源文件有的是gbk有的是utf-8。所以还得动态的去识别当前文件的编码。我查了下,识别文件编码好像不是那么容易,网上给了很多例子都不是很精确或者稳定,还好我找了一个,对于我的txt还是有用的,代码如下,共享下(非原创):
/**
	 * 查询字符编码
	 * @param fileName
	 * @return UTF-8/Unicode/UTF-16BE/GBK
	 * @throws Exception
	 */
	public static String codeStringPlus(String fileName) throws Exception {
		BufferedInputStream bin = null;
		String code = null;

		try {
			bin = new BufferedInputStream(new FileInputStream(fileName));
			int p = (bin.read() << 8) + bin.read();
			switch (p) {
			case 0xefbb:
				code = "UTF-8";
				break;
			case 0xfffe:
				code = "Unicode";
				break;
			case 0xfeff:
				code = "UTF-16BE";
				break;
			default:
				code = "GBK";
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			bin.close();
		}

		return code;
	}
这样,我的编码问题就解决了。感谢这位贡献者,虽然可能还有缺陷,但是在我这够用了。

三、关于检索的一些认识

之前一直很困惑一个问题,场景如下,

比如文本“Hello ,I am Chinese”,

建索引之后我查询“hello”、“Chinese”都是可以查到的,但我查询“Chine”为何就是查不到呢?难道是我写错了?

后来我想明白了,因为我查询的时候也用了分析器Analyzer。根据我的理解,我觉得Lucene是这样一个过程,在建立索引的时候首先分词,上述文本会被解析成“Hello”、“I”、“am”、“Chinese”,(当然可能I和am会被解析器去掉,这些价值不大,这里假设他们是有意义的)。

然后我查询的时候是用QueryParser加Analyzer查询的,也就是说,我输入Chinese的时候,首先也经历了分词的过程,会将我的关键字解析成“Chinese”,然后去搜索,是可以的,而当我输入“Chine”的时候,解析器只会解析成“Chine”,这个在索引里是没有的!当然查不到。如果想要输入Chine也能查到的话,可能需要额外的操作,比如换一种查询方式之类的,而我并没有去做,所以引起了这些困惑。

四、其他格式文本的检索

Lucene是不关心源文件的文件格式的,也就是说,得自己将不同格式的文档转换成纯文本,需要自己去写不同格式的解析器,而不是直接拿过来建索引。

以上是我目前对Lucene的理解,不知道是不是都对,仅供参考,希望读到此文的朋友能给出一点意见建议,共同学习。

五、下面附上代码:

环境是Windows下Lucene4.10.0+Myeclipse2013+JDK1.7,4GRam。

1、建立索引(这里都是txt)

/**
 * LuceneTest 
 * com.lucene.sheen.mine  
 */
package com.lucene.sheen.mine;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
import java.util.Date;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Field.Store;
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.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;

import com.chenlb.mmseg4j.analysis.MMSegAnalyzer;

/**
 * @author Sheen 2014-9-10
 * 
 */
public class MyIndex {

	/**
	 * @param args
	 * @throws Exception
	 */
	public static void main(String[] args) throws Exception {
		String docPath = "resource\\data";
		String indexPath = "resource\\index";

		File docFile = new File(docPath);
		if (!docFile.exists() || !docFile.canRead()) {
			System.out.println("您所选择的文件夹不存在或者没有访问权限!文件路径:"
					+ docFile.getAbsolutePath());
			System.exit(1);
		}
		Date start = new Date();

		Directory indexDir = FSDirectory.open(new File(indexPath));
		Analyzer analyzer = new MMSegAnalyzer();
		IndexWriterConfig iwc = new IndexWriterConfig(Version.LUCENE_4_10_0,
				analyzer);
		iwc.setRAMBufferSizeMB(200).setMaxBufferedDocs(20000);
		iwc.setOpenMode(OpenMode.CREATE);
		IndexWriter writer = new IndexWriter(indexDir, iwc);

		MemoryMXBean memorymbean = ManagementFactory.getMemoryMXBean();
		MemoryUsage usage = memorymbean.getHeapMemoryUsage();
		System.out.println("INIT HEAP: " + usage.getInit());
		System.out.println("MAX HEAP: " + usage.getMax());
		System.out.println("USE HEAP: " + usage.getUsed());

		indexDoc(writer, docFile);
		writer.close();
		Date end = new Date();
		seeVMStatus();
		System.out.println("所有文件建立索引完毕,耗时:"
				+ (double) (end.getTime() - start.getTime()) / (1000 * 60)
				+ "min");
	}

	static void indexDoc(IndexWriter writer, File file) throws Exception {
		if (file.canRead()) {
			if (file.isDirectory()) {
				File[] files = file.listFiles();
				for (File thisFile : files) {
					indexDoc(writer, thisFile);
				}
			} else {
				String code = codeString(file.getAbsolutePath());
				System.out.println("**********文件:" + file.getAbsolutePath()
						+ "正在建立索引********************");
				System.out.println("字符编码:" + code);
				seeVMStatus();
				BufferedReader reader = null;
				try {
					Field pathField = new StringField("path", file.getPath(),
							Field.Store.YES);
					reader = new BufferedReader(new InputStreamReader(
							new FileInputStream(file), code));
					String line = null;
					long fileSize = 0;
					while ((line = reader.readLine()) != null) {
						fileSize += line.getBytes().length;
						Document doc = new Document();
						doc.add(pathField);
						Field textField = new TextField("contents", line,
								Store.YES);
						doc.add(textField);
						writer.addDocument(doc);

					}
					System.out.println("TotalSize:" + fileSize / (1024 * 1024)
							+ "M");
					System.out.println("建立索引完毕\n");

				} catch (Exception e) {
					e.printStackTrace();
				} finally {
					reader.close();
				}
			}

		}
	}

	/**
	 * 查看虚拟机内存信息
	 */
	public static void seeVMStatus() {
		MemoryMXBean memorymbean = ManagementFactory.getMemoryMXBean();
		System.out.println("JVM Full Information:");
		System.out.println("Heap Memory Usage: "
				+ memorymbean.getHeapMemoryUsage());
		System.out.println("Non-Heap Memory Usage: "
				+ memorymbean.getNonHeapMemoryUsage());
	}

	
	/**
	 * 查询字符编码
	 * @param fileName
	 * @return UTF-8/Unicode/UTF-16BE/GBK
	 * @throws Exception
	 */
	public static String codeStringPlus(String fileName) throws Exception {
		BufferedInputStream bin = null;
		String code = null;

		try {
			bin = new BufferedInputStream(new FileInputStream(fileName));
			int p = (bin.read() << 8) + bin.read();
			switch (p) {
			case 0xefbb:
				code = "UTF-8";
				break;
			case 0xfffe:
				code = "Unicode";
				break;
			case 0xfeff:
				code = "UTF-16BE";
				break;
			default:
				code = "GBK";
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			bin.close();
		}

		return code;
	}
	/**
	 * 查询字符编码是UTF-8还是GBK
	 * @param fileName
	 * @return UTF-8/GBK
	 * @throws Exception
	 */
	public static String codeString(String fileName) throws Exception {
		BufferedInputStream bin = null;
		String code = null;
		try {
			bin = new BufferedInputStream(new FileInputStream(fileName));
			int p = (bin.read() << 8) + bin.read();
			switch (p) {
			case 0xefbb:
				code = "UTF-8";
				break;
			default:
				code = "GBK";
			}
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			bin.close();
		}
		return code;
	}


}
2、查询

/**
 * LuceneTest 
 * com.lucene.sheen.mine  
 */
package com.lucene.sheen.mine;

import java.io.File;
import java.io.IOException;
import java.util.Date;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
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.FSDirectory;

import com.chenlb.mmseg4j.analysis.MMSegAnalyzer;

/**
 * @author Sheen  2014-9-10
 *
 */
public class MySearcher {

	/**
	 * @param args
	 * @throws IOException 
	 * @throws ParseException 
	 */
	public static void main(String[] args) throws IOException, ParseException {
		String index = "resource\\index";
		String field = "contents";
		String queryString = "870270291";
		
		IndexReader reader = DirectoryReader.open(FSDirectory.open(new File(index)));
		IndexSearcher searcher = new IndexSearcher(reader);
		
		Analyzer analyzer = new MMSegAnalyzer();
		
		QueryParser parser = new QueryParser(field, analyzer);
		Query query = parser.parse(queryString);
		System.out.println("查询关键字:"+query.toString());
		Date start = new Date();
		TopDocs results = searcher.search(query, 20);
		ScoreDoc[] hits = results.scoreDocs;
		for(ScoreDoc sdoc : hits){
			Document doc = searcher.doc(sdoc.doc);
			System.out.println("查询结果:");
			System.out.println(sdoc.score);
			System.out.println(doc.get("path"));
			System.out.println(new String(doc.get("contents").getBytes(),"UTF-8"));
		}
		Date end = new Date();
		System.out.println("耗时:"+(end.getTime()-start.getTime()));
	}

}




阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页