`
enetor
  • 浏览: 184707 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
社区版块
存档分类
最新评论

Lucene学习笔记

阅读更多
最近项目要用到Lucene,网上搜索了一些基础作为学习记录。

1 、整体结构说明

索引和搜索过程图:



API 使用示例图:








1. 索引过程:

1) 有一系列被索引文件

2) 被索引文件经过语法分析和语言处理形成一系列词 (Term) 。

3) 经过索引创建形成词典和反向索引表。

4) 通过索引存储将索引写入硬盘。

2. 搜索过程:

a) 用户输入查询语句。

b) 对查询语句经过语法分析和语言分析得到一系列词 (Term) 。

c) 通过语法分析得到一个查询树。

d) 通过索引存储将索引读入到内存。

e) 利用查询树搜索索引,从而得到每个词 (Term) 的 文档链表,对文档链表进行交,差,并得到结果文档。

f) 将搜索到的结果文档对查询的相关性进行排序。

g) 返回查询结果给用户。






2 、基本使用说明

lucene 索引的单元对象是 Document, 它可以是一个文本、一封 email 或者一个网页等,其中 Document 包含多个 Field ,一个 Field 就是它的一个属性,比如“文件路径”、“作者”,“文件内容”等都是 Field


1 )索引用类:
Java代码

   1. /**
   2.  * 对目录进行Lucene索引
   3.  * @author roy
   4.  *
   5.  */ 
   6. public class Indexer { 
   7.     private static String INDEX_DIR = "G:\\ROY的各种笔记\\索引";       //索引结果存放目录    
   8.     private static String DATA_DIR = "G:\\ROY的各种笔记";                //被索引文件存放的目录 
   9.      
  10.     /**
  11.      * 测试主函数
  12.      * @param args
  13.      * @throws Exception
  14.      */ 
  15.     public static void main(String[] args) throws Exception{ 
  16.         long start = new Date().getTime();    
  17.         int numIndexed = index(new File(INDEX_DIR),new File(DATA_DIR));//调用index方法    
  18.         long end = new Date().getTime();    
  19.         System.out.println("Indexing " + numIndexed + " files took " + (end - start) + " milliseconds");     
  20.     } 
  21.      
  22.     /**
  23.      * 索引dataDir下的.txt文件,并储存在indexDir下,返回索引的文件数量
  24.      * @param indexDir
  25.      * @param dataDir
  26.      * @return
  27.      * @throws IOException
  28.      */ 
  29.     private static int index(File indexDir, File dataDir) throws Exception{ 
  30.         if(!dataDir.exists() || !dataDir.isDirectory()){ 
  31.             throw new Exception("被索引的文件不存在!"); 
  32.         } 
  33.         if(!indexDir.exists() || !indexDir.isDirectory()){ 
  34.             indexDir.mkdirs(); 
  35.         } 
  36.         IndexWriter writer = new IndexWriter( 
  37.                                     FSDirectory.open(indexDir), 
  38.                                     new StandardAnalyzer(Version.LUCENE_30), 
  39.                                     true, 
  40.                                     IndexWriter.MaxFieldLength.UNLIMITED 
  41.                                 ); 
  42.         //按照目录进行递归索引 
  43.         indexDirectory(writer,dataDir); 
  44.         int numIndexed = writer.numDocs(); 
  45.         //对索引后的结果加以优化 
  46.         writer.optimize(); 
  47.         writer.close(); 
  48.         return numIndexed; 
  49.     } 
  50.     /**
  51.      * 循环遍历目录下的所有.doc文件并进行索引  
  52.      * @param writer
  53.      * @param dir
  54.      * @throws IOException
  55.      */ 
  56.     private static void indexDirectory(IndexWriter writer, File dir) throws IOException { 
  57.          File[] files = dir.listFiles(); 
  58.          for (int i = 0; i < files.length; i++) {    
  59.            File f = files[i];    
  60.            if (f.isDirectory()) {   
  61.              indexDirectory(writer, f);  // recurse    
  62.            } else if (f.getName().endsWith(".doc")) { 
  63.             indexFile(writer, f);    
  64.            }    
  65.          }    
  66.        } 
  67.     /**
  68.      * 对单个doc文件进行索引
  69.      * @param writer
  70.      * @param f
  71.      * @throws IOException
  72.      */ 
  73.     private static void indexFile(IndexWriter writer, File f)    
  74.          throws IOException { 
  75.          if (f.isHidden() || !f.exists() || !f.canRead()) {    
  76.            return;    
  77.          } 
  78.          System.out.println("Indexing " + f.getCanonicalPath()); 
  79.          Document doc = new Document(); 
  80.          doc.add(new Field("content",new FileReader(f))); 
  81.          doc.add(new Field("filename",f.getName(),Field.Store.YES, Field.Index.ANALYZED)); 
  82.          doc.add(new Field("path",f.getCanonicalPath(),Field.Store.YES, Field.Index.ANALYZED)); 
  83.          writer.addDocument(doc); 
  84.        }   
  85. } 

/**
* 对目录进行Lucene索引
* @author roy
*
*/
public class Indexer {
private static String INDEX_DIR = "G:\\ROY的各种笔记\\索引"; //索引结果存放目录  
private static String DATA_DIR = "G:\\ROY的各种笔记"; //被索引文件存放的目录

/**
* 测试主函数
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception{
long start = new Date().getTime();  
int numIndexed = index(new File(INDEX_DIR),new File(DATA_DIR));//调用index方法  
long end = new Date().getTime();  
System.out.println("Indexing " + numIndexed + " files took " + (end - start) + " milliseconds");   
}

/**
* 索引dataDir下的.txt文件,并储存在indexDir下,返回索引的文件数量
* @param indexDir
* @param dataDir
* @return
* @throws IOException
*/
private static int index(File indexDir, File dataDir) throws Exception{
if(!dataDir.exists() || !dataDir.isDirectory()){
throw new Exception("被索引的文件不存在!");
}
if(!indexDir.exists() || !indexDir.isDirectory()){
indexDir.mkdirs();
}
IndexWriter writer = new IndexWriter(
FSDirectory.open(indexDir),
new StandardAnalyzer(Version.LUCENE_30),
true,
IndexWriter.MaxFieldLength.UNLIMITED
);
//按照目录进行递归索引
indexDirectory(writer,dataDir);
int numIndexed = writer.numDocs();
//对索引后的结果加以优化
writer.optimize();
writer.close();
return numIndexed;
}
/**
* 循环遍历目录下的所有.doc文件并进行索引 
* @param writer
* @param dir
* @throws IOException
*/
private static void indexDirectory(IndexWriter writer, File dir) throws IOException {
     File[] files = dir.listFiles();
     for (int i = 0; i < files.length; i++) {  
       File f = files[i];  
       if (f.isDirectory()) { 
         indexDirectory(writer, f);  // recurse  
       } else if (f.getName().endsWith(".doc")) {
        indexFile(writer, f);  
       }  
     }  
   }
/**
* 对单个doc文件进行索引
* @param writer
* @param f
* @throws IOException
*/
private static void indexFile(IndexWriter writer, File f)  
     throws IOException {
     if (f.isHidden() || !f.exists() || !f.canRead()) {  
       return;  
     }
     System.out.println("Indexing " + f.getCanonicalPath());
     Document doc = new Document();
     doc.add(new Field("content",new FileReader(f)));
     doc.add(new Field("filename",f.getName(),Field.Store.YES, Field.Index.ANALYZED));
     doc.add(new Field("path",f.getCanonicalPath(),Field.Store.YES, Field.Index.ANALYZED));
     writer.addDocument(doc);
   } 
}



2 )查询用类:
Java代码

   1. /**
   2.  * 对索引结果进行搜索
   3.  * @author roy
   4.  *
   5.  */ 
   6. public class Searcher { 
   7.     private static String INDEX_DIR = "G:\\ROY的各种笔记\\索引";       //索引所在的路径    
   8.     private static String KEYWORD = "自己";                           //关键词    
   9.     private static int TOP_NUM = 100;                               //显示前100条结果 
  10.     /**
  11.      * 测试主函数
  12.      * @param args
  13.      * @throws Exception
  14.      */ 
  15.     public static void main(String[] args) throws Exception{ 
  16.         File indexDir = new File(INDEX_DIR); 
  17.         if (!indexDir.exists() || !indexDir.isDirectory()){ 
  18.             throw new Exception(indexDir + "索引目录不存在!");    
  19.         } 
  20.         search(indexDir,KEYWORD);                                   //调用search方法进行查询 
  21.     } 
  22.     /**
  23.      * 索引查找方法
  24.      * @param indexDir  索引所在的目录
  25.      * @param q         查询的字符串
  26.      * @throws Exception
  27.      */ 
  28.     private static void search(File indexDir, String q) throws Exception {    
  29.              IndexSearcher is = new  IndexSearcher(FSDirectory.open(indexDir),true);    //read-only    
  30.              String field = "content"; 
  31.              //创建查询解析器 
  32.              QueryParser parser = new QueryParser(Version.LUCENE_30, field, new StandardAnalyzer(Version.LUCENE_30));    
  33.              Query query = parser.parse(q); 
  34.              //创建结果收集器 
  35.              TopScoreDocCollector collector = TopScoreDocCollector.create(TOP_NUM ,false);//有变化的地方    
  36.  
  37.              long start = new Date().getTime();// start time    
  38.              is.search(query, collector);    
  39.              ScoreDoc[] hits = collector.topDocs().scoreDocs;    
  40.               
  41.              System.out.println(hits.length);    
  42.              for (int i = 0; i < hits.length; i++) {    
  43.                  Document doc = is.doc(hits[i].doc);//new method is.doc()    
  44.                  System.out.println(doc.getField("filename")+"   "+hits[i].toString()+"  ");    
  45.              }    
  46.              long end = new Date().getTime();//end time    
  47.  
  48.              System.out.println("Found " + collector.getTotalHits() +    
  49.                        " document(s) (in " + (end - start) +    
  50.                        " milliseconds) that matched query '" +    
  51.                        q + "':");    
  52.            } 
  53. } 

/**
* 对索引结果进行搜索
* @author roy
*
*/
public class Searcher {
private static String INDEX_DIR = "G:\\ROY的各种笔记\\索引"; //索引所在的路径  
private static String KEYWORD = "自己"; //关键词  
private static int TOP_NUM = 100; //显示前100条结果
/**
* 测试主函数
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception{
File indexDir = new File(INDEX_DIR);
if (!indexDir.exists() || !indexDir.isDirectory()){
throw new Exception(indexDir + "索引目录不存在!");  
}
search(indexDir,KEYWORD); //调用search方法进行查询
}
/**
* 索引查找方法
* @param indexDir 索引所在的目录
* @param q 查询的字符串
* @throws Exception
*/
private static void search(File indexDir, String q) throws Exception {  
     IndexSearcher is = new  IndexSearcher(FSDirectory.open(indexDir),true); //read-only  
     String field = "content";
     //创建查询解析器
     QueryParser parser = new QueryParser(Version.LUCENE_30, field, new StandardAnalyzer(Version.LUCENE_30));  
     Query query = parser.parse(q);
     //创建结果收集器
     TopScoreDocCollector collector = TopScoreDocCollector.create(TOP_NUM ,false);//有变化的地方  

     long start = new Date().getTime();// start time  
     is.search(query, collector);  
     ScoreDoc[] hits = collector.topDocs().scoreDocs;  
    
     System.out.println(hits.length);  
     for (int i = 0; i < hits.length; i++) {  
         Document doc = is.doc(hits[i].doc);//new method is.doc()  
         System.out.println(doc.getField("filename")+"   "+hits[i].toString()+"  ");  
     }  
     long end = new Date().getTime();//end time  

     System.out.println("Found " + collector.getTotalHits() +  
               " document(s) (in " + (end - start) +  
               " milliseconds) that matched query '" +  
               q + "':");  
   }
}


3 )选择分词器:



分词器是一个搜索引擎的核心之一,好的分词器可以把关键字分解为更接近于用户的搜索目标的词汇。



下面的方法可以显示分词器的分词结果:
Java代码

   1. /**
   2.      * 查看分词后的结果
   3.      * @param analyzer  分词器
   4.      * @param s         需要分词的字符串
   5.      * @throws Exception
   6.      */ 
   7.     public static void showAnalyzerResult(Analyzer analyzer, String s) throws Exception { 
   8.         StringReader reader = new StringReader(s); 
   9.         TokenStream tokenStream = analyzer.tokenStream(s,reader); 
  10.         tokenStream.addAttribute(TermAttribute.class); 
  11.          
  12.         while (tokenStream.incrementToken()) { 
  13.             TermAttribute ta = tokenStream.getAttribute(TermAttribute.class); 
  14.             System.out.println(ta.term()); 
  15.         } 
  16.         System.out.println(); 
  17.     } 

/**
* 查看分词后的结果
* @param analyzer 分词器
* @param s 需要分词的字符串
* @throws Exception
*/
public static void showAnalyzerResult(Analyzer analyzer, String s) throws Exception {
StringReader reader = new StringReader(s);
TokenStream tokenStream = analyzer.tokenStream(s,reader);
tokenStream.addAttribute(TermAttribute.class);

while (tokenStream.incrementToken()) {
TermAttribute ta = tokenStream.getAttribute(TermAttribute.class);
System.out.println(ta.term());
}
System.out.println();
}


4 )索引doc,pdf等文件:



默认情况下 lucene 会索引 lucene 文件,其他文件直接索引会出现大量错误。所以我们需要对 doc,pdf 这些文件进行预处理,先生成 txt 文件才可。

1、doc文件:

先下载最新的POI组件,使用其中的poi-3.6-20091214.jar和poi- scratchpad-3.6-20091214即可实现读取word文件中的纯文本内容
Java代码

   1. InputStream is = new FileInputStream(f); 
   2. WordExtractor wordExtractor = new WordExtractor(is); 
   3. return wordExtractor.getText(); 

InputStream is = new FileInputStream(f);
WordExtractor wordExtractor = new WordExtractor(is);
return wordExtractor.getText();

2、pdf文件:

下载最新的pdf-box,把fontbox-1.1.0.jar、jempbox-1.1.0.jar、pdfbox-1.1.0.jar三个包放进项目路径中


Java代码

   1. InputStream is = new FileInputStream(f); 
   2. PDFTextStripper ts = new PDFTextStripper(); 
   3. PDDocument pDocument = PDDocument.load(is); 
   4. StringWriter writer = new StringWriter(); 
   5. ts.writeText(pDocument,writer); 
   6. is.close(); 
   7. pDocument.close(); 
   8. return  writer.getBuffer().toString(); 

InputStream is = new FileInputStream(f);
PDFTextStripper ts = new PDFTextStripper();
PDDocument pDocument = PDDocument.load(is);
StringWriter writer = new StringWriter();
ts.writeText(pDocument,writer);
is.close();
pDocument.close();
return  writer.getBuffer().toString();

5、查看当前索引库的内容

下载最新Luke工具,解压到lucene文件夹下,在命令行输入:
java -classpath lukeall-0.7.1.jar;lucene-2.0.jar org.getopt.luke.Luke
即可


5 )索引的添加、删除和更新:

1、添加新的索引记录
Java代码

   1. IndexWriter writer = new IndexWriter( 
   2.                 FSDirectory.open(indexFile), 
   3.                 analyzer, 
   4.                 false, 
   5.                 IndexWriter.MaxFieldLength.UNLIMITED 
   6.             ); 

IndexWriter writer = new IndexWriter(
FSDirectory.open(indexFile),
analyzer,
false,
IndexWriter.MaxFieldLength.UNLIMITED
);

在创建索引器的时候指定create参数为true表示新建立索引,false表示使用当前目录下已有的索引。此时为writer添加 document并optimize()即可



2、删除索引
Java代码

   1. /**
   2.      * 暂时删除某个索引
   3.      * @param f
   4.      * @throws Exception
   5.      */ 
   6.     public static void deleteIndexTmp(File indexFile,String targetPath) throws Exception{ 
   7.         IndexReader ir = IndexReader.open(FSDirectory.open(indexFile)); 
   8.         ir.deleteDocuments(new Term("path",targetPath)); 
   9.         ir.close(); 
  10.     }  
  11. /**
  12.      * 恢复某个临时删除的索引
  13.      * @param f
  14.      * @throws Exception
  15.      */ 
  16.     public static void rollbackDelete(File indexFile) throws Exception{ 
  17.         IndexReader ir = IndexReader.open(FSDirectory.open(indexFile)); 
  18.         ir.undeleteAll(); 
  19.         ir.close(); 
  20.     } 
  21.     /**
  22.      * 将标记为删除的索引真正删除
  23.      * @param f
  24.      * @throws Exception
  25.      */ 
  26.     public static void optimizeIndex(File indexFile) throws Exception{ 
  27.         IndexWriter writer = new IndexWriter( 
  28.                 FSDirectory.open(indexFile), 
  29.                 analyzer, 
  30.                 false, 
  31.                 IndexWriter.MaxFieldLength.UNLIMITED 
  32.             ); 
  33.         writer.optimize(); 
  34.         writer.close(); 
  35.     } 

/**
* 暂时删除某个索引
* @param f
* @throws Exception
*/
public static void deleteIndexTmp(File indexFile,String targetPath) throws Exception{
IndexReader ir = IndexReader.open(FSDirectory.open(indexFile));
ir.deleteDocuments(new Term("path",targetPath));
ir.close();
}
/**
* 恢复某个临时删除的索引
* @param f
* @throws Exception
*/
public static void rollbackDelete(File indexFile) throws Exception{
IndexReader ir = IndexReader.open(FSDirectory.open(indexFile));
ir.undeleteAll();
ir.close();
}
/**
* 将标记为删除的索引真正删除
* @param f
* @throws Exception
*/
public static void optimizeIndex(File indexFile) throws Exception{
IndexWriter writer = new IndexWriter(
FSDirectory.open(indexFile),
analyzer,
false,
IndexWriter.MaxFieldLength.UNLIMITED
);
writer.optimize();
writer.close();
}

3、更新索引:

可以先查到旧索引将其删除,然后再新建索引即可。
Lucene3.0以后提供了新的update(Term,Document)方法,封装了上述两个操作。
6 )高级查询:
Java代码

   1. /** *** 一个关键字,对一个字段进行查询 **** */ 
   2. QueryParser qp = new QueryParser("content",analyzer); 
   3. query = qp.parse(keyword); 
   4. Hits hits = searcher.search(query); 
   5.   
   6. /** *** 模糊查询 **** */ 
   7. Term term = new Term("content",keyword); 
   8. FuzzyQuery fq = new FuzzyQuery(term); 
   9. Hits hits = searcher.search(fq); 
  10.   
  11. /** *** 一个关键字,在两个字段中查询 **** */ 
  12. /*
  13.  * 1.BooleanClause.Occur[] 的三种 类型: MUST : + and MUST_NOT : - not SHOULD : or
  14.  * 2.下面查询的意思是:content中 必须包含该关键字,而title有 没有都无所谓
  15.  * 3.下面的这个查询中,Occur[]的 长度必须和Fields[]的 长度一致。每个限制条件对应一个字段
  16.  */ 
  17. BooleanClause.Occur[] flags = new BooleanClause.Occur[]{BooleanClause.Occur.SHOULD,BooleanClause.Occur.MUST}; 
  18. query=MultiFieldQueryParser.parse(keyword,new String[]{"title","content"},flags,analyzer); 
  19.   
  20.   
  21. /** *** 两个(多个)关键 字对两个(多 个)字 段进行查询,默 认匹配规则 **** */ 
  22. /*
  23.  * 1. 关键字的个数必须和字段的个数相等 
  24.  * 2. 由于没有指定匹配规定,默认为"SHOULD" 因 此,下面查询的意思是:"title"中 含有keyword1 或 "content"含有 keyword2. 
  25.  * 在此例中,把 keyword1和keyword2相 同
  26.  */ 
  27.  query=MultiFieldQueryParser.parse(new String[]{keyword,keyword},new 
  28.  String[]{"title","content"},analyzer); 
  29.   
  30.   
  31. /** ** 两个(多个)关键 字对两个(多 个)字 段进行查询,手 工指定匹配规则 ****/ 
  32. /*
  33.  * 1. 必须 关键字的个数 == 字 段名的个数 == 匹 配规则的个数
  34.  * 2.下面查询的意思是:"title"必 须不含有keyword1,并 且"content"中 必须含有 keyword2
  35.  */ 
  36.  BooleanClause.Occur[] flags = new 
  37.  BooleanClause.Occur[]{BooleanClause.Occur.MUST_NOT,BooleanClause.Occur.MUST}; 
  38.  query=MultiFieldQueryParser.parse(new String[]{keyword,keyword},new 
  39.  String[]{"title","content"},flags,analyzer); 
  40.   
  41.   
  42. /** *** 对日期型字段进行查询 **** */ 
  43.   
  44. /** *** 对数字范围进行查询 **** */ 
  45. /*
  46.  * 1. 两个条件必须是同一个字段 
  47.  * 2.前面一个条件必须比后面一个条件小,否则找不到数据
  48.  *  3.new RangeQuery中 的第三个参数,表示是否包含"=" true: >= 或 & lt;= false: > 或 < 
  49.  * 4.找出 55>=id>=53 or 60>=id>=57:
  50.  */ 
  51. Term lowerTerm1 = new Term("id","53"); 
  52. Term upperTerm1 = new Term("id","55"); 
  53. RangeQuery rq1 = new RangeQuery(lowerTerm1,upperTerm1,true); 
  54.   
  55. Term lowerTerm2 = new Term("id","57"); 
  56. Term upperTerm2 = new Term("id","60"); 
  57. RangeQuery rq2 = new RangeQuery(lowerTerm2,upperTerm2,true); 
  58.   
  59. BooleanQuery bq = new BooleanQuery(); 
  60. bq.add(rq1,BooleanClause.Occur.SHOULD); 
  61. bq.add(rq2,BooleanClause.Occur.SHOULD); 
  62. Hits hits = searcher.search(bq); 
  • 大小: 46.8 KB
  • 大小: 40.8 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics