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

来源:互联网 发布:淘宝图片盗用投诉处理 编辑:程序博客网 时间:2024/05/03 05:52

这几天因为一个小项目用到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()));}}




1 0