Lucene5学习之多线程创建索引

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

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

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

   indexWriter添加索引文档是通过addDocument方法实现的,下面是addDocument方法的截图:
Lucene5学习之多线程创建索引
 我们发现内部实际调用的是updateDocument方法,继续跟进updateDocument方法,

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

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

Lucene5学习之多线程创建索引
 

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

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

Java代码  Lucene5学习之多线程创建索引
  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代码  Lucene5学习之多线程创建索引
  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代码  Lucene5学习之多线程创建索引
  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代码  Lucene5学习之多线程创建索引
  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个文件夹分别存储索引文件,如图:

Lucene5学习之多线程创建索引
 
Lucene5学习之多线程创建索引
 

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

 

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

或者加裙
Lucene5学习之多线程创建索引一起交流学习!

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

上一篇:Linux有问必答:Ubuntu如何使用命令行移除PPA仓库


下一篇:Spring Boot入门(12)实现页面访问量统计功能