Lucene5学习之多线程创建索引

    昨晚睡觉前把多线程创建索引demo写好了,今天早上7点多就起来,趁着劲头赶紧记录分享一下,这样对那些同样对Lucene感兴趣的童鞋也有所帮助。

    我们都知道Lucene的IndexWriter在构造初始化的时候会去获取索引目录的写锁writerLock,加锁的目的就是保证同时只能有一个IndexWriter实例在往索引目录中写数据,具体看截图:


 而在多线程环境下,光保证只有IndexWriter实例能得到锁还不行,还必须保证每次只能有一个线程能获取到writerLock,Lucene内部是如何实现的呢?请看源码:

   indexWriter添加索引文档是通过addDocument方法实现的,下面是addDocument方法的截图:

 我们发现内部实际调用的是updateDocument方法,继续跟进updateDocument方法,


 updateDocument中ensureOpen();首先确保索引目录已经打开,然后通过docWriter.updateDocument(doc, analyzer, term)真正去更新索引,更新成功后触发索引merge事件processEvents(true, false);docWriter是DocumentsWriter类型,真正执行索引写操作的类是DocumentsWriter,IndexWriter只是内部维护了一个DocumentsWriter属性调用它的方法而已,继续跟进DocumentsWriter类的updateDocument方法,如图:


 final ThreadState perThread = flushControl.obtainAndLock();会视图去获取Lock,因为索引写操作不能同时并发执行,没错这里的ThreadState就是NIO里的ReentrantLock,它跟synchronized作用类似,但它比synchronized控制粒度更小更灵活,能手动在方法内部的任意位置打开和解除锁,两者性能且不谈,因为随着JVM对代码的不断优化,两者性能上的差异会越来越小。扯远了,接着前面的继续说,flushControl.obtainAndLock()在获取锁的时候内部实际是通过perThreadPool.getAndLock来获取锁的,perThreadPool并不是什么线程池,准确来说它是一个锁池,池子里维护了N把锁,每个锁与一个线程ID,跟着我继续看源码,你就明白了。

 perThreadPool是如何获取lock的呢?继续看它的getAndLock方法:


 

getAndLock需要传入一个线程,而flushControl.obtainAndLock()在获取锁的时候内部是这样实现的:


 到此,你应该明白了,Lucene内部只是维护了多把锁而已,并没有真的去New Thread,Thread是通过把当前调用线程当作参数传入的,然后分配锁的时候,每个线程只分配一把锁,而每把锁在写索引的时候都会使用ReentrantLock.lock来限制并发写操作,其实每次对于同一个索引目录仍然只能有一个indexWriter在写索引,那Lucene内部维护多把锁有什么意义呢?一个索引目录只能有一把锁,那如果有多个索引目录,每个索引目录发一把锁,N个索引目录同时进行索引写操作就有意义了。把索引数据全部放一个索引目录本身就不现实,再说一个文件夹下能存放的文件最大数量也不是无穷大的,当一个文件夹下的文件数量达到某个数量级会你读写性能都会急剧下降的,所以把索引文件分摊到多个索引目录是明智之举,所以,当你需要索引的数据量很庞大的时候,要想提高索引创建的速度,除了要充分利用RAMDirectory减少与磁盘IO次数外,可以尝试把索引数据分多索引目录存储,个人建议,如果说的不对,请尽情的喷我。下面我贴一个我昨晚写的多线程创建索引的demo,抛个砖引个玉哈!看代码:

Java代码  

  1. package com.yida.framework.lucene5.index;  
  2.   
  3. import java.io.BufferedReader;  
  4. import java.io.IOException;  
  5. import java.io.InputStream;  
  6. import java.io.InputStreamReader;  
  7. import java.nio.charset.StandardCharsets;  
  8. import java.nio.file.FileVisitResult;  
  9. import java.nio.file.Files;  
  10. import java.nio.file.LinkOption;  
  11. import java.nio.file.OpenOption;  
  12. import java.nio.file.Path;  
  13. import java.nio.file.Paths;  
  14. import java.nio.file.SimpleFileVisitor;  
  15. import java.nio.file.attribute.BasicFileAttributes;  
  16. import java.util.concurrent.CountDownLatch;  
  17.   
  18. import org.apache.lucene.analysis.Analyzer;  
  19. import org.apache.lucene.document.Document;  
  20. import org.apache.lucene.document.Field;  
  21. import org.apache.lucene.document.LongField;  
  22. import org.apache.lucene.document.StringField;  
  23. import org.apache.lucene.document.TextField;  
  24. import org.apache.lucene.index.IndexWriter;  
  25. import org.apache.lucene.index.IndexWriterConfig;  
  26. import org.apache.lucene.index.Term;  
  27. import org.apache.lucene.index.IndexWriterConfig.OpenMode;  
  28. import org.apache.lucene.store.FSDirectory;  
  29.   
  30. import com.yida.framework.lucene5.util.LuceneUtils;  
  31.   
  32. /** 
  33.  * 索引创建线程 
  34.  * @author Lanxiaowei 
  35.  * 
  36.  */  
  37. public class IndexCreator implements Runnable {  
  38.     /**需要读取的文件存放目录*/  
  39.     private String docPath;  
  40.     /**索引文件存放目录*/  
  41.     private String luceneDir;  
  42.       
  43.     private int threadCount;  
  44.       
  45.     private final CountDownLatch countDownLatch1;  
  46.   
  47.     private final CountDownLatch countDownLatch2;  
  48.       
  49.     public IndexCreator(String docPath, String luceneDir,int threadCount,CountDownLatch countDownLatch1,CountDownLatch countDownLatch2) {  
  50.         super();  
  51.         this.docPath = docPath;  
  52.         this.luceneDir = luceneDir;  
  53.         this.threadCount = threadCount;  
  54.         this.countDownLatch1 = countDownLatch1;  
  55.         this.countDownLatch2 = countDownLatch2;  
  56.     }  
  57.   
  58.     public void run() {  
  59.         IndexWriter writer = null;  
  60.         try {  
  61.             countDownLatch1.await();  
  62.             Analyzer analyzer = LuceneUtils.analyzer;  
  63.             FSDirectory directory = LuceneUtils.openFSDirectory(luceneDir);  
  64.             IndexWriterConfig config = new IndexWriterConfig(analyzer);  
  65.             config.setOpenMode(OpenMode.CREATE_OR_APPEND);  
  66.             writer = LuceneUtils.getIndexWriter(directory, config);  
  67.             try {  
  68.                 indexDocs(writer, Paths.get(docPath));  
  69.             } catch (IOException e) {  
  70.                 e.printStackTrace();  
  71.             }  
  72.         } catch (InterruptedException e1) {  
  73.             e1.printStackTrace();  
  74.         } finally {  
  75.             LuceneUtils.closeIndexWriter(writer);  
  76.             countDownLatch2.countDown();  
  77.         }  
  78.     }  
  79.       
  80.     /** 
  81.      *  
  82.      * @param writer 
  83.      *            索引写入器 
  84.      * @param path 
  85.      *            文件路径 
  86.      * @throws IOException 
  87.      */  
  88.     public static void indexDocs(final IndexWriter writer, Path path)  
  89.             throws IOException {  
  90.         // 如果是目录,查找目录下的文件  
  91.         if (Files.isDirectory(path, new LinkOption[0])) {  
  92.             System.out.println("directory");  
  93.             Files.walkFileTree(path, new SimpleFileVisitor() {  
  94.                 @Override  
  95.                 public FileVisitResult visitFile(Object file,  
  96.                         BasicFileAttributes attrs) throws IOException {  
  97.                     Path path = (Path)file;  
  98.                     System.out.println(path.getFileName());  
  99.                     indexDoc(writer, path, attrs.lastModifiedTime().toMillis());  
  100.                     return FileVisitResult.CONTINUE;  
  101.                 }  
  102.             });  
  103.         } else {  
  104.             indexDoc(writer, path,  
  105.                     Files.getLastModifiedTime(path, new LinkOption[0])  
  106.                             .toMillis());  
  107.         }  
  108.     }  
  109.   
  110.     /** 
  111.      * 读取文件创建索引 
  112.      *  
  113.      * @param writer 
  114.      *            索引写入器 
  115.      * @param file 
  116.      *            文件路径 
  117.      * @param lastModified 
  118.      *            文件最后一次修改时间 
  119.      * @throws IOException 
  120.      */  
  121.     public static void indexDoc(IndexWriter writer, Path file, long lastModified)  
  122.             throws IOException {  
  123.         InputStream stream = Files.newInputStream(file, new OpenOption[0]);  
  124.         Document doc = new Document();  
  125.   
  126.         Field pathField = new StringField("path", file.toString(),  
  127.                 Field.Store.YES);  
  128.         doc.add(pathField);  
  129.   
  130.         doc.add(new LongField("modified", lastModified, Field.Store.YES));  
  131.         doc.add(new TextField("contents",intputStream2String(stream),Field.Store.YES));  
  132.         //doc.add(new TextField("contents", new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))));  
  133.   
  134.         if (writer.getConfig().getOpenMode() == IndexWriterConfig.OpenMode.CREATE) {  
  135.             System.out.println("adding " + file);  
  136.             writer.addDocument(doc);  
  137.         } else {  
  138.             System.out.println("updating " + file);  
  139.             writer.updateDocument(new Term("path", file.toString()), doc);  
  140.         }  
  141.         writer.commit();  
  142.     }  
  143.       
  144.     /** 
  145.      * InputStream转换成String 
  146.      * @param is    输入流对象 
  147.      * @return 
  148.      */  
  149.     private static String intputStream2String(InputStream is) {  
  150.         BufferedReader bufferReader = null;  
  151.         StringBuilder stringBuilder = new StringBuilder();  
  152.         String line;  
  153.         try {  
  154.             bufferReader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));  
  155.             while ((line = bufferReader.readLine()) != null) {  
  156.                 stringBuilder.append(line + "\r\n");  
  157.             }  
  158.         } catch (IOException e) {  
  159.             e.printStackTrace();  
  160.         } finally {  
  161.             if (bufferReader != null) {  
  162.                 try {  
  163.                     bufferReader.close();  
  164.                 } catch (IOException e) {  
  165.                     e.printStackTrace();  
  166.                 }  
  167.             }  
  168.         }  
  169.         return stringBuilder.toString();  
  170.     }  
  171. }  

 

Java代码  

  1. package com.yida.framework.lucene5.index;  
  2.   
  3. import java.util.concurrent.CountDownLatch;  
  4. import java.util.concurrent.ExecutorService;  
  5. import java.util.concurrent.Executors;  
  6.   
  7. /** 
  8.  * 多线程创建索引 
  9.  * @author Lanxiaowei 
  10.  * 
  11.  */  
  12. public class MultiThreadIndexTest {  
  13.     /** 
  14.      * 创建了5个线程同时创建索引 
  15.      * @param args 
  16.      * @throws InterruptedException  
  17.      */  
  18.     public static void main(String[] args) throws InterruptedException {  
  19.         int threadCount = 5;  
  20.         ExecutorService pool = Executors.newFixedThreadPool(threadCount);  
  21.         CountDownLatch countDownLatch1 = new CountDownLatch(1);  
  22.         CountDownLatch countDownLatch2 = new CountDownLatch(threadCount);  
  23.         for(int i = 0; i < threadCount; i++) {  
  24.             Runnable runnable = new IndexCreator("C:/doc" + (i+1), "C:/lucenedir" + (i+1),threadCount,  
  25.                     countDownLatch1,countDownLatch2);  
  26.             //子线程交给线程池管理  
  27.             pool.execute(runnable);  
  28.         }  
  29.           
  30.         countDownLatch1.countDown();  
  31.         System.out.println("开始创建索引");  
  32.         //等待所有线程都完成  
  33.         countDownLatch2.await();  
  34.         //线程全部完成工作  
  35.         System.out.println("所有线程都创建索引完毕");  
  36.         //释放线程池资源  
  37.         pool.shutdown();  
  38.     }  
  39. }  

 上一篇博客《Lucene5学习之LuceneUtils工具类简单封装》中封装的工具类中获取IndexWriter单例对象有点BUG,我没有把IndexWriter对象跟线程ID关联,所以我这里把我修改后的代码再贴一遍,为我的失误在此给大家道歉,如果还有什么BUG还望大家积极指正,不胜感谢:

Java代码  

  1. package com.yida.framework.lucene5.util;  
  2.   
  3. import java.io.IOException;  
  4. import java.util.concurrent.ExecutorService;  
  5. import java.util.concurrent.locks.Lock;  
  6. import java.util.concurrent.locks.ReentrantLock;  
  7.   
  8. import org.apache.lucene.index.DirectoryReader;  
  9. import org.apache.lucene.index.IndexReader;  
  10. import org.apache.lucene.index.IndexWriter;  
  11. import org.apache.lucene.index.IndexWriterConfig;  
  12. import org.apache.lucene.search.IndexSearcher;  
  13. import org.apache.lucene.store.Directory;  
  14. import org.apache.lucene.store.LockObtainFailedException;  
  15. /** 
  16.  * Lucene索引读写器/查询器单例获取工具类 
  17.  * @author Lanxiaowei 
  18.  * 
  19.  */  
  20. public class LuceneManager {  
  21.     private volatile static LuceneManager singleton;  
  22.       
  23.     private volatile static IndexWriter writer;  
  24.       
  25.     private volatile static IndexReader reader;  
  26.       
  27.     private volatile static IndexSearcher searcher;  
  28.       
  29.     private final Lock writerLock = new ReentrantLock();  
  30.       
  31.     //private final Lock readerLock = new ReentrantLock();  
  32.       
  33.     //private final Lock searcherLock = new ReentrantLock();  
  34.       
  35.   
  36.     private static ThreadLocal<IndexWriter> writerLocal = new ThreadLocal<IndexWriter>();  
  37.   
  38.     private LuceneManager() {}  
  39.   
  40.     public static LuceneManager getInstance() {  
  41.         if (null == singleton) {  
  42.             synchronized (LuceneManager.class) {  
  43.                 if (null == singleton) {  
  44.                     singleton = new LuceneManager();  
  45.                 }  
  46.             }  
  47.         }  
  48.         return singleton;  
  49.     }  
  50.   
  51.     /** 
  52.      * 获取IndexWriter单例对象 
  53.      * @param dir 
  54.      * @param config 
  55.      * @return 
  56.      */  
  57.     public IndexWriter getIndexWriter(Directory dir, IndexWriterConfig config) {  
  58.         if(null == dir) {  
  59.             throw new IllegalArgumentException("Directory can not be null.");  
  60.         }  
  61.         if(null == config) {  
  62.             throw new IllegalArgumentException("IndexWriterConfig can not be null.");  
  63.         }  
  64.         try {  
  65.             writerLock.lock();  
  66.             writer = writerLocal.get();  
  67.             if(null != writer) {  
  68.                 return writer;  
  69.             }  
  70.             if(null == writer){  
  71.                 //如果索引目录被锁,则直接抛异常  
  72.                 if(IndexWriter.isLocked(dir)) {  
  73.                     throw new LockObtainFailedException("Directory of index had been locked.");  
  74.                 }  
  75.                 writer = new IndexWriter(dir, config);  
  76.                 writerLocal.set(writer);  
  77.             }  
  78.         } catch (LockObtainFailedException e) {  
  79.             e.printStackTrace();  
  80.         } catch (IOException e) {  
  81.             e.printStackTrace();  
  82.         } finally {  
  83.             writerLock.unlock();  
  84.         }  
  85.         return writer;  
  86.     }  
  87.       
  88.     /** 
  89.      * 获取IndexWriter[可能为Null] 
  90.      * @return 
  91.      */  
  92.     public IndexWriter getIndexWriter() {  
  93.         return writer;  
  94.     }  
  95.       
  96.     /** 
  97.      * 获取IndexReader对象 
  98.      * @param dir 
  99.      * @param enableNRTReader  是否开启NRTReader 
  100.      * @return 
  101.      */  
  102.     public IndexReader getIndexReader(Directory dir,boolean enableNRTReader) {  
  103.         if(null == dir) {  
  104.             throw new IllegalArgumentException("Directory can not be null.");  
  105.         }  
  106.         try {  
  107.             if(null == reader){  
  108.                 reader = DirectoryReader.open(dir);  
  109.             } else {  
  110.                 if(enableNRTReader && reader instanceof DirectoryReader) {  
  111.                     //开启近实时Reader,能立即看到动态添加/删除的索引变化  
  112.                     reader = DirectoryReader.openIfChanged((DirectoryReader)reader);  
  113.                 }  
  114.             }  
  115.         } catch (IOException e) {  
  116.             e.printStackTrace();  
  117.         }  
  118.         return reader;  
  119.     }  
  120.       
  121.     /** 
  122.      * 获取IndexReader对象(默认不启用NETReader) 
  123.      * @param dir 
  124.      * @return 
  125.      */  
  126.     public IndexReader getIndexReader(Directory dir) {  
  127.         return getIndexReader(dir, false);  
  128.     }  
  129.       
  130.     /** 
  131.      * 获取IndexSearcher对象 
  132.      * @param reader    IndexReader对象实例 
  133.      * @param executor  如果你需要开启多线程查询,请提供ExecutorService对象参数 
  134.      * @return 
  135.      */  
  136.     public IndexSearcher getIndexSearcher(IndexReader reader,ExecutorService executor) {  
  137.         if(null == reader) {  
  138.             throw new IllegalArgumentException("The indexReader can not be null.");  
  139.         }  
  140.         if(null == searcher){  
  141.             searcher = new IndexSearcher(reader);  
  142.         }  
  143.         return searcher;  
  144.     }  
  145.       
  146.     /** 
  147.      * 获取IndexSearcher对象(不支持多线程查询) 
  148.      * @param reader    IndexReader对象实例 
  149.      * @return 
  150.      */  
  151.     public IndexSearcher getIndexSearcher(IndexReader reader) {  
  152.         return getIndexSearcher(reader, null);  
  153.     }  
  154.       
  155.     /** 
  156.      * 关闭IndexWriter 
  157.      * @param writer 
  158.      */  
  159.     public void closeIndexWriter(IndexWriter writer) {  
  160.         if(null != writer) {  
  161.             try {  
  162.                 writer.close();  
  163.                 writer = null;  
  164.                 writerLocal.remove();  
  165.             } catch (IOException e) {  
  166.                 e.printStackTrace();  
  167.             }  
  168.         }  
  169.     }  
  170. }  

 

Java代码  

  1. package com.yida.framework.lucene5.util;  
  2.   
  3. import java.io.IOException;  
  4. import java.nio.file.Paths;  
  5. import java.util.ArrayList;  
  6. import java.util.Collections;  
  7. import java.util.List;  
  8. import java.util.Set;  
  9. import java.util.concurrent.ExecutorService;  
  10.   
  11. import org.ansj.lucene5.AnsjAnalyzer;  
  12. import org.apache.lucene.analysis.Analyzer;  
  13. import org.apache.lucene.document.Document;  
  14. import org.apache.lucene.document.Field;  
  15. import org.apache.lucene.document.TextField;  
  16. import org.apache.lucene.index.IndexReader;  
  17. import org.apache.lucene.index.IndexWriter;  
  18. import org.apache.lucene.index.IndexWriterConfig;  
  19. import org.apache.lucene.index.IndexableField;  
  20. import org.apache.lucene.index.Term;  
  21. import org.apache.lucene.queryparser.classic.QueryParser;  
  22. import org.apache.lucene.search.IndexSearcher;  
  23. import org.apache.lucene.search.Query;  
  24. import org.apache.lucene.search.ScoreDoc;  
  25. import org.apache.lucene.search.TopDocs;  
  26. import org.apache.lucene.search.highlight.Formatter;  
  27. import org.apache.lucene.search.highlight.Fragmenter;  
  28. import org.apache.lucene.search.highlight.Highlighter;  
  29. import org.apache.lucene.search.highlight.InvalidTokenOffsetsException;  
  30. import org.apache.lucene.search.highlight.QueryScorer;  
  31. import org.apache.lucene.search.highlight.Scorer;  
  32. import org.apache.lucene.search.highlight.SimpleFragmenter;  
  33. import org.apache.lucene.search.highlight.SimpleHTMLFormatter;  
  34. import org.apache.lucene.store.Directory;  
  35. import org.apache.lucene.store.FSDirectory;  
  36.   
  37. /** 
  38.  * Lucene工具类(基于Lucene5.0封装) 
  39.  * @author Lanxiaowei 
  40.  * 
  41.  */  
  42. public class LuceneUtils {  
  43.     private static final LuceneManager luceneManager = LuceneManager.getInstance();  
  44.     public static Analyzer analyzer = new AnsjAnalyzer();  
  45.       
  46.     /** 
  47.      * 打开索引目录 
  48.      *  
  49.      * @param luceneDir 
  50.      * @return 
  51.      * @throws IOException 
  52.      */  
  53.     public static FSDirectory openFSDirectory(String luceneDir) {  
  54.         FSDirectory directory = null;  
  55.         try {  
  56.             directory = FSDirectory.open(Paths.get(luceneDir));  
  57.             /** 
  58.              * 注意:isLocked方法内部会试图去获取Lock,如果获取到Lock,会关闭它,否则return false表示索引目录没有被锁, 
  59.              * 这也就是为什么unlock方法被从IndexWriter类中移除的原因 
  60.              */  
  61.             IndexWriter.isLocked(directory);  
  62.         } catch (IOException e) {  
  63.             e.printStackTrace();  
  64.         }  
  65.         return directory;  
  66.     }  
  67.       
  68.     /** 
  69.      * 关闭索引目录并销毁 
  70.      * @param directory 
  71.      * @throws IOException 
  72.      */  
  73.     public static void closeDirectory(Directory directory) throws IOException {  
  74.         if (null != directory) {  
  75.             directory.close();  
  76.             directory = null;  
  77.         }  
  78.     }  
  79.       
  80.     /** 
  81.      * 获取IndexWriter 
  82.      * @param dir 
  83.      * @param config 
  84.      * @return 
  85.      */  
  86.     public static IndexWriter getIndexWriter(Directory dir, IndexWriterConfig config) {  
  87.         return luceneManager.getIndexWriter(dir, config);  
  88.     }  
  89.       
  90.     /** 
  91.      * 获取IndexWriter 
  92.      * @param dir 
  93.      * @param config 
  94.      * @return 
  95.      */  
  96.     public static IndexWriter getIndexWrtier(String directoryPath, IndexWriterConfig config) {  
  97.         FSDirectory directory = openFSDirectory(directoryPath);  
  98.         return luceneManager.getIndexWriter(directory, config);  
  99.     }  
  100.       
  101.     /** 
  102.      * 获取IndexReader 
  103.      * @param dir 
  104.      * @param enableNRTReader  是否开启NRTReader 
  105.      * @return 
  106.      */  
  107.     public static IndexReader getIndexReader(Directory dir,boolean enableNRTReader) {  
  108.         return luceneManager.getIndexReader(dir, enableNRTReader);  
  109.     }  
  110.       
  111.     /** 
  112.      * 获取IndexReader(默认不启用NRTReader) 
  113.      * @param dir 
  114.      * @return 
  115.      */  
  116.     public static IndexReader getIndexReader(Directory dir) {  
  117.         return luceneManager.getIndexReader(dir);  
  118.     }  
  119.       
  120.     /** 
  121.      * 获取IndexSearcher 
  122.      * @param reader    IndexReader对象 
  123.      * @param executor  如果你需要开启多线程查询,请提供ExecutorService对象参数 
  124.      * @return 
  125.      */  
  126.     public static IndexSearcher getIndexSearcher(IndexReader reader,ExecutorService executor) {  
  127.         return luceneManager.getIndexSearcher(reader, executor);  
  128.     }  
  129.       
  130.     /** 
  131.      * 获取IndexSearcher(不支持多线程查询) 
  132.      * @param reader    IndexReader对象 
  133.      * @return 
  134.      */  
  135.     public static IndexSearcher getIndexSearcher(IndexReader reader) {  
  136.         return luceneManager.getIndexSearcher(reader);  
  137.     }  
  138.       
  139.     /** 
  140.      * 创建QueryParser对象 
  141.      * @param field 
  142.      * @param analyzer 
  143.      * @return 
  144.      */  
  145.     public static QueryParser createQueryParser(String field, Analyzer analyzer) {  
  146.         return new QueryParser(field, analyzer);  
  147.     }  
  148.       
  149.     /** 
  150.      * 关闭IndexReader 
  151.      * @param reader 
  152.      */  
  153.     public static void closeIndexReader(IndexReader reader) {  
  154.         if (null != reader) {  
  155.             try {  
  156.                 reader.close();  
  157.                 reader = null;  
  158.             } catch (IOException e) {  
  159.                 e.printStackTrace();  
  160.             }  
  161.         }  
  162.     }  
  163.       
  164.     /** 
  165.      * 关闭IndexWriter 
  166.      * @param writer 
  167.      */  
  168.     public static void closeIndexWriter(IndexWriter writer) {  
  169.         luceneManager.closeIndexWriter(writer);  
  170.     }  
  171.       
  172.     /** 
  173.      * 关闭IndexReader和IndexWriter 
  174.      * @param reader 
  175.      * @param writer 
  176.      */  
  177.     public static void closeAll(IndexReader reader, IndexWriter writer) {  
  178.         closeIndexReader(reader);  
  179.         closeIndexWriter(writer);  
  180.     }  
  181.       
  182.     /** 
  183.      * 删除索引[注意:请自己关闭IndexWriter对象] 
  184.      * @param writer 
  185.      * @param field 
  186.      * @param value 
  187.      */  
  188.     public static void deleteIndex(IndexWriter writer, String field, String value) {  
  189.         try {  
  190.             writer.deleteDocuments(new Term[] {new Term(field,value)});  
  191.         } catch (IOException e) {  
  192.             e.printStackTrace();  
  193.         }  
  194.     }  
  195.       
  196.     /** 
  197.      * 删除索引[注意:请自己关闭IndexWriter对象] 
  198.      * @param writer 
  199.      * @param query 
  200.      */  
  201.     public static void deleteIndex(IndexWriter writer, Query query) {  
  202.         try {  
  203.             writer.deleteDocuments(query);  
  204.         } catch (IOException e) {  
  205.             e.printStackTrace();  
  206.         }  
  207.     }  
  208.       
  209.     /** 
  210.      * 批量删除索引[注意:请自己关闭IndexWriter对象] 
  211.      * @param writer 
  212.      * @param terms 
  213.      */  
  214.     public static void deleteIndexs(IndexWriter writer,Term[] terms) {  
  215.         try {  
  216.             writer.deleteDocuments(terms);  
  217.         } catch (IOException e) {  
  218.             e.printStackTrace();  
  219.         }  
  220.     }  
  221.       
  222.     /** 
  223.      * 批量删除索引[注意:请自己关闭IndexWriter对象] 
  224.      * @param writer 
  225.      * @param querys 
  226.      */  
  227.     public static void deleteIndexs(IndexWriter writer,Query[] querys) {  
  228.         try {  
  229.             writer.deleteDocuments(querys);  
  230.         } catch (IOException e) {  
  231.             e.printStackTrace();  
  232.         }  
  233.     }  
  234.       
  235.     /** 
  236.      * 删除所有索引文档 
  237.      * @param writer 
  238.      */  
  239.     public static void deleteAllIndex(IndexWriter writer) {  
  240.         try {  
  241.             writer.deleteAll();  
  242.         } catch (IOException e) {  
  243.             e.printStackTrace();  
  244.         }  
  245.     }  
  246.       
  247.     /** 
  248.      * 更新索引文档 
  249.      * @param writer 
  250.      * @param term 
  251.      * @param document 
  252.      */  
  253.     public static void updateIndex(IndexWriter writer,Term term,Document document) {  
  254.         try {  
  255.             writer.updateDocument(term, document);  
  256.         } catch (IOException e) {  
  257.             e.printStackTrace();  
  258.         }  
  259.     }  
  260.       
  261.     /** 
  262.      * 更新索引文档 
  263.      * @param writer 
  264.      * @param term 
  265.      * @param document 
  266.      */  
  267.     public static void updateIndex(IndexWriter writer,String field,String value,Document document) {  
  268.         updateIndex(writer, new Term(field, value), document);  
  269.     }  
  270.       
  271.     /** 
  272.      * 添加索引文档 
  273.      * @param writer 
  274.      * @param doc 
  275.      */  
  276.     public static void addIndex(IndexWriter writer, Document document) {  
  277.         updateIndex(writer, null, document);  
  278.     }  
  279.       
  280.     /** 
  281.      * 索引文档查询 
  282.      * @param searcher 
  283.      * @param query 
  284.      * @return 
  285.      */  
  286.     public static List<Document> query(IndexSearcher searcher,Query query) {  
  287.         TopDocs topDocs = null;  
  288.         try {  
  289.             topDocs = searcher.search(query, Integer.MAX_VALUE);  
  290.         } catch (IOException e) {  
  291.             e.printStackTrace();  
  292.         }  
  293.         ScoreDoc[] scores = topDocs.scoreDocs;  
  294.         int length = scores.length;  
  295.         if (length <= 0) {  
  296.             return Collections.emptyList();  
  297.         }  
  298.         List<Document> docList = new ArrayList<Document>();  
  299.         try {  
  300.             for (int i = 0; i < length; i++) {  
  301.                 Document doc = searcher.doc(scores[i].doc);  
  302.                 docList.add(doc);  
  303.             }  
  304.         } catch (IOException e) {  
  305.             e.printStackTrace();  
  306.         }  
  307.         return docList;  
  308.     }  
  309.       
  310.     /** 
  311.      * 返回索引文档的总数[注意:请自己手动关闭IndexReader] 
  312.      * @param reader 
  313.      * @return 
  314.      */  
  315.     public static int getIndexTotalCount(IndexReader reader) {  
  316.         return reader.numDocs();  
  317.     }  
  318.       
  319.     /** 
  320.      * 返回索引文档中最大文档ID[注意:请自己手动关闭IndexReader] 
  321.      * @param reader 
  322.      * @return 
  323.      */  
  324.     public static int getMaxDocId(IndexReader reader) {  
  325.         return reader.maxDoc();  
  326.     }  
  327.       
  328.     /** 
  329.      * 返回已经删除尚未提交的文档总数[注意:请自己手动关闭IndexReader] 
  330.      * @param reader 
  331.      * @return 
  332.      */  
  333.     public static int getDeletedDocNum(IndexReader reader) {  
  334.         return getMaxDocId(reader) - getIndexTotalCount(reader);  
  335.     }  
  336.       
  337.     /** 
  338.      * 根据docId查询索引文档 
  339.      * @param reader         IndexReader对象 
  340.      * @param docID          documentId 
  341.      * @param fieldsToLoad   需要返回的field 
  342.      * @return 
  343.      */  
  344.     public static Document findDocumentByDocId(IndexReader reader,int docID, Set<String> fieldsToLoad) {  
  345.         try {  
  346.             return reader.document(docID, fieldsToLoad);  
  347.         } catch (IOException e) {  
  348.             return null;  
  349.         }  
  350.     }  
  351.       
  352.     /** 
  353.      * 根据docId查询索引文档 
  354.      * @param reader         IndexReader对象 
  355.      * @param docID          documentId 
  356.      * @return 
  357.      */  
  358.     public static Document findDocumentByDocId(IndexReader reader,int docID) {  
  359.         return findDocumentByDocId(reader, docID, null);  
  360.     }  
  361.       
  362.     /** 
  363.      * @Title: createHighlighter 
  364.      * @Description: 创建高亮器 
  365.      * @param query             索引查询对象 
  366.      * @param prefix            高亮前缀字符串 
  367.      * @param stuffix           高亮后缀字符串 
  368.      * @param fragmenterLength  摘要最大长度 
  369.      * @return 
  370.      */  
  371.     public static Highlighter createHighlighter(Query query, String prefix, String stuffix, int fragmenterLength) {  
  372.         Formatter formatter = new SimpleHTMLFormatter((prefix == null || prefix.trim().length() == 0) ?   
  373.             "<font color=\"red\">" : prefix, (stuffix == null || stuffix.trim().length() == 0)?"</font>" : stuffix);  
  374.         Scorer fragmentScorer = new QueryScorer(query);  
  375.         Highlighter highlighter = new Highlighter(formatter, fragmentScorer);  
  376.         Fragmenter fragmenter = new SimpleFragmenter(fragmenterLength <= 0 ? 50 : fragmenterLength);  
  377.         highlighter.setTextFragmenter(fragmenter);  
  378.         return highlighter;  
  379.     }  
  380.       
  381.     /** 
  382.      * @Title: highlight 
  383.      * @Description: 生成高亮文本 
  384.      * @param document          索引文档对象 
  385.      * @param highlighter       高亮器 
  386.      * @param analyzer          索引分词器 
  387.      * @param field             高亮字段 
  388.      * @return 
  389.      * @throws IOException 
  390.      * @throws InvalidTokenOffsetsException 
  391.      */  
  392.     public static String highlight(Document document,Highlighter highlighter,Analyzer analyzer,String field) throws IOException {  
  393.         List<IndexableField> list = document.getFields();  
  394.         for (IndexableField fieldable : list) {  
  395.             String fieldValue = fieldable.stringValue();  
  396.             if(fieldable.name().equals(field)) {  
  397.                 try {  
  398.                     fieldValue = highlighter.getBestFragment(analyzer, field, fieldValue);  
  399.                 } catch (InvalidTokenOffsetsException e) {  
  400.                     fieldValue = fieldable.stringValue();  
  401.                 }  
  402.                 return (fieldValue == null || fieldValue.trim().length() == 0)? fieldable.stringValue() : fieldValue;  
  403.             }  
  404.         }  
  405.         return null;  
  406.     }  
  407.       
  408.     /** 
  409.      * @Title: searchTotalRecord 
  410.      * @Description: 获取符合条件的总记录数 
  411.      * @param query 
  412.      * @return 
  413.      * @throws IOException 
  414.      */  
  415.     public static int searchTotalRecord(IndexSearcher search,Query query) {  
  416.         ScoreDoc[] docs = null;  
  417.         try {  
  418.             TopDocs topDocs = search.search(query, Integer.MAX_VALUE);  
  419.             if(topDocs == null || topDocs.scoreDocs == null || topDocs.scoreDocs.length == 0) {  
  420.                 return 0;  
  421.             }  
  422.             docs = topDocs.scoreDocs;  
  423.         } catch (IOException e) {  
  424.             e.printStackTrace();  
  425.         }  
  426.         return docs.length;  
  427.     }  
  428.       
  429.     /** 
  430.      * @Title: pageQuery 
  431.      * @Description: Lucene分页查询 
  432.      * @param searcher 
  433.      * @param query 
  434.      * @param page 
  435.      * @throws IOException 
  436.      */  
  437.     public static void pageQuery(IndexSearcher searcher,Directory directory,Query query,Page<Document> page) {  
  438.         int totalRecord = searchTotalRecord(searcher,query);  
  439.         //设置总记录数  
  440.         page.setTotalRecord(totalRecord);  
  441.         TopDocs topDocs = null;  
  442.         try {  
  443.             topDocs = searcher.searchAfter(page.getAfterDoc(),query, page.getPageSize());  
  444.         } catch (IOException e) {  
  445.             e.printStackTrace();  
  446.         }  
  447.         List<Document> docList = new ArrayList<Document>();  
  448.         ScoreDoc[] docs = topDocs.scoreDocs;  
  449.         int index = 0;  
  450.         for (ScoreDoc scoreDoc : docs) {  
  451.             int docID = scoreDoc.doc;  
  452.             Document document = null;  
  453.             try {  
  454.                 document = searcher.doc(docID);  
  455.             } catch (IOException e) {  
  456.                 e.printStackTrace();  
  457.             }  
  458.             if(index == docs.length - 1) {  
  459.                 page.setAfterDoc(scoreDoc);  
  460.                 page.setAfterDocId(docID);  
  461.             }  
  462.             docList.add(document);  
  463.             index++;  
  464.         }  
  465.         page.setItems(docList);  
  466.         closeIndexReader(searcher.getIndexReader());  
  467.     }  
  468.       
  469.     /** 
  470.      * @Title: pageQuery 
  471.      * @Description: 分页查询[如果设置了高亮,则会更新索引文档] 
  472.      * @param searcher 
  473.      * @param directory 
  474.      * @param query 
  475.      * @param page 
  476.      * @param highlighterParam 
  477.      * @param writerConfig 
  478.      * @throws IOException 
  479.      */  
  480.     public static void pageQuery(IndexSearcher searcher,Directory directory,Query query,Page<Document> page,HighlighterParam highlighterParam,IndexWriterConfig writerConfig) throws IOException {  
  481.         IndexWriter writer = null;  
  482.         //若未设置高亮  
  483.         if(null == highlighterParam || !highlighterParam.isHighlight()) {  
  484.             pageQuery(searcher,directory,query, page);  
  485.         } else {  
  486.             int totalRecord = searchTotalRecord(searcher,query);  
  487.             System.out.println("totalRecord:" + totalRecord);  
  488.             //设置总记录数  
  489.             page.setTotalRecord(totalRecord);  
  490.             TopDocs topDocs = searcher.searchAfter(page.getAfterDoc(),query, page.getPageSize());  
  491.             List<Document> docList = new ArrayList<Document>();  
  492.             ScoreDoc[] docs = topDocs.scoreDocs;  
  493.             int index = 0;  
  494.             writer = getIndexWriter(directory, writerConfig);  
  495.             for (ScoreDoc scoreDoc : docs) {  
  496.                 int docID = scoreDoc.doc;  
  497.                 Document document = searcher.doc(docID);  
  498.                 String content = document.get(highlighterParam.getFieldName());  
  499.                 if(null != content && content.trim().length() > 0) {  
  500.                     //创建高亮器  
  501.                     Highlighter highlighter = LuceneUtils.createHighlighter(query,   
  502.                         highlighterParam.getPrefix(), highlighterParam.getStuffix(),   
  503.                         highlighterParam.getFragmenterLength());  
  504.                     String text = highlight(document, highlighter, analyzer, highlighterParam.getFieldName());  
  505.                     //若高亮后跟原始文本不相同,表示高亮成功  
  506.                     if(!text.equals(content)) {  
  507.                         Document tempdocument = new Document();  
  508.                         List<IndexableField> indexableFieldList = document.getFields();  
  509.                         if(null != indexableFieldList && indexableFieldList.size() > 0) {  
  510.                             for(IndexableField field : indexableFieldList) {  
  511.                                 if(field.name().equals(highlighterParam.getFieldName())) {  
  512.                                     tempdocument.add(new TextField(field.name(), text, Field.Store.YES));  
  513.                                 } else {  
  514.                                     tempdocument.add(field);  
  515.                                 }  
  516.                             }  
  517.                         }  
  518.                         updateIndex(writer, new Term(highlighterParam.getFieldName(),content), tempdocument);  
  519.                         document = tempdocument;  
  520.                     }  
  521.                 }  
  522.                 if(index == docs.length - 1) {  
  523.                     page.setAfterDoc(scoreDoc);  
  524.                     page.setAfterDocId(docID);  
  525.                 }  
  526.                 docList.add(document);  
  527.                 index++;  
  528.             }  
  529.             page.setItems(docList);  
  530.         }  
  531.         closeIndexReader(searcher.getIndexReader());  
  532.         closeIndexWriter(writer);  
  533.     }  
  534. }  

 demo源码我会在最底下的附件里上传,有需要的请自己下载。demo代码运行时请先在C盘建5个文件夹放需要读取的文件,建5个文件夹分别存储索引文件,如图:


 

 

OK,为了这篇博客已经耗时整整1个小时了,打完收工!下一篇准备说说如何多索引目录多线程查询,敬请期待吧!

 

   如果你还有什么问题请加我Q-Q:7-3-6-0-3-1-3-0-5,

或者加裙
一起交流学习!

转载:http://iamyida.iteye.com/blog/2196855

时间: 2024-10-31 06:29:00

Lucene5学习之多线程创建索引的相关文章

Lucene5学习之多索引目录查询以及多线程查询

   上一篇中我们使用多线程创建了索引,下面我们来试着采用不把多个索引目录里的数据合并到一个新的索引目录的方式去查询索引数据,当然你也可以合并(合并到一个索引目录查询就很简单了),其实很多情况我们都是不合并到一个索引目录的,那多索引目录该如何查询呢,在Lucene5中使用的MultiReader类,在Lucene4时代,使用的是MultiSearcher类.至于Lucene多线程查询,只需要在构建IndexSearcher对象时传入一个ExecutorService线程池管理对象即可,具体请看下

Lucene5学习之创建索引入门示例

    Lucene更新实在太快了,只好紧跟脚步开始学习Lucene5,花了点时间写了一个demo,就是程序根据用户提供的一个文件夹,读取该文件夹下的所有文件,然后读取文件里的内容写入索引.读取文件部分采用的是最新的NIO2.0API,因此,JDK必须使用1.7及以上版本.Lucene5开发压缩包请在Lucene官网下载.不多说了,对于码农来说,最直接的就是上代码. Java代码   package com.yida.framework.lucene5.core;      import jav

Lucene5学习之增量索引(Zoie)

      清明节放假期间,就一直在思考着Lucene增量索引问题,通过google我了解到了Zoie的存在,于是就开始2天的学习Zoie之旅,对Zoie的原理以及使用基本掌握,还特地使用Zoie实现了Lucene索引增量索引.不过Zoie已经好久没更新了,Zoie目前版本只支持到Lucene4.3.0,为此我花了2个多小时,修改了Zoie的源码将它升级使其支持Lucene5.0,我修改过的Zoie最新源码我已经上传到我的百度网盘,下载地址如下:     Zoie-for-Lucene5.0源码

Lucene5学习之Suggest关键字提示

     首先需要搞清楚Suggest模块是用来解决什么问题的?Google我想大家都用过,当我们在搜索输入框里输入搜索关键字的时候,紧贴着输入框下方会弹出一个提示框,提示框里会列出Top N个包含当前用户输入的搜索关键字的搜索热词,如图:       这里说的不是前端的这种JS效果,而说的是输入一个关键字如何获取相关的搜索热词,至于js效果,自己Google jQuery自动补全插件,我以前玩过,这里关注的是提示数据如何获取,当然你也可以使用数据库SQL like "%xxxx%"来

Lucene5学习之分页查询

Java代码   package com.yida.framework.lucene5.core;      import java.io.IOException;   import java.nio.file.Paths;   import java.util.ArrayList;   import java.util.List;      import org.apache.lucene.analysis.standard.StandardAnalyzer;   import org.apa

Lucene5学习之Highlighte关键字高亮

   Google我想大家应该都用过,输入我们的搜索关键字,然后回车,Google就会返回搜索结果,在返回的界面里,会对命中的关键字进行红色字体标注出来,这就是高亮功能.        Lucene5中高亮功能相关API都在org.apache.lucene.search.highlight包下,我们先从简单的高亮器开始即Highlighter        透过Hightlighter类的源码,我们首先需要去了解里面的每个成员变量的含义: Java代码   public static fina

Lucene5学习之SpellCheck拼写纠错

   最近有点累,让这篇又姗姗来迟了,各位不好意思,让你们久等了.趁着周末一个人没什么事,继续Lucene5系列的脚步,今天主题是Suggest模块下另一个功能:拼写纠错.什么叫拼写纠错?大家还是看图吧,这样会比较形象:          看完上面两张图片,我想大家应该已经知道SpellCheck是用来解决问题的了吧.其实这个功能主要目的还是为了提升用户体验问题,当用户输入的搜索关键字里包含了错别字(对于英文来说,就是单词拼写不正确),我们的搜索程序能智能检测出来并给出与用户输入的搜索关键字最相

Lucene5学习之排序-Sort

  这回我们来学习Lucene的排序.机智的少年应该已经发现了,IndexSearcher类的search方法有好几个重载:        Java代码   /** Finds the top <code>n</code>     * hits for <code>query</code>.     *     * @throws BooleanQuery.TooManyClauses If a query would exceed      *      

Lucene5学习之CustomScoreQuery

 虽然前面我们已经集中学习过Query,但CustomScoreQuery当初略过了,今天就来学学这个Query.从类名上看,顾名思义,就大不略的猜得到它的干嘛用的.它是用来进行干预查询权重的,从而影响最终评分的,即评分公式中的queryNorm部分.       一个索引文档的评分高低意味着它的价值大小,有价值的索引文档会优先返回并靠前显示,而影响评分的因素有Term在document中的出现频率,以及term在每个document中的出现频率,Term的权重等等,但这些因素都是固定的,并不会