全文检索Lucene(二)—索引库维护详解编程语言

维护索引库
1,创建索引库
2,删除索引库
3,更新索引库
4,索引库的优化

索引设置的一些建议:
1) 尽量减少不必要的存储
2) 不需要检索的内容不要建立索引
3) 非文本格式需要提前转化
4)需要整体存放的内容不要分词

数据与Document、Field的转换
我们在应用程序中使用对象表示数据。在数据库中使用的是表记录,所以存在来回转换的问题。同样,要索引库中使用的是Document,也存在来回转换的问题。
对于一个要进行搜索的实体对象,我们会写一个对应的工具类,其中有两个方法:

Document Object2Document(Object object); // 对象Document 
Object Document2Object(Document doc); // Document对象 

在转换时,对象中的属性对应Document中的Field。由于Lucene只处理文本,所有所有的属性值在存储前都要先转成字符串。使用构造方法:Field(String name, String value, Store store, Index index)。
Store 指定当前字段的数据要不要存到索引库中
Index 指定当前字段的数据是否可以被搜索(是否更新词汇表)
Store与Index都是枚举类型。Store:指定是否把当前属性值的原始内容存储到索引库中。如果存储(YES),在搜索出相应数据时这个属性就有原始的值;如果不存储(NO),得到的数据的这个属性的值为null。Index:指定是否建立索引(词汇表)。建立索引才能被搜索到。不可以不存储也不建立索引(没有意义)。

NumericUtils与DateTools
如果属性的类型不是字符串,则要先进转换:如果是数字类型,使用NumericUtils。如果是日期类型,则使用DataTools。

代码示例:
Article.java

package com.my.bean; 
 
public class Article { 
 
    private Integer id; 
    private String title; 
    private String content; 
 
    public Integer getId() { 
        return id; 
    } 
    public void setId(Integer id) { 
        this.id = id; 
    } 
    public String getTitle() { 
        return title; 
    } 
    public void setTitle(String title) { 
        this.title = title; 
    } 
    public String getContent() { 
        return content; 
    } 
    public void setContent(String content) { 
        this.content = content; 
    } 
 
 
 
} 

QueryPageResult.java

package com.my.bean; 
 
import java.util.List; 
 
public class QueryPageResult { 
    private int count; 
    private List list; 
 
    public QueryPageResult(int count, List list) { 
        this.count = count; 
        this.list = list; 
    } 
 
    public int getCount() { 
        return count; 
    } 
 
    public void setCount(int count) { 
        this.count = count; 
    } 
 
    public List getList() { 
        return list; 
    } 
 
    public void setList(List list) { 
        this.list = list; 
    } 
 
} 

ArticleDocumentUtils.java

package com.my.utils; 
 
import org.apache.lucene.document.Document; 
import org.apache.lucene.document.Field; 
import org.apache.lucene.document.Field.Index; 
import org.apache.lucene.document.Field.Store; 
 
import com.my.bean.Article; 
 
public class ArticleDocumentUtils { 
 
    public static Document article2Document(Article article) { 
        Document doc = new Document(); 
 
        String idStr = article.getId().toString(); // 要把Integer的id值转为String型 
        doc.add(new Field("id", idStr, Store.YES, Index.NOT_ANALYZED)); 
        doc.add(new Field("title", article.getTitle(), Store.YES, Index.ANALYZED)); 
        doc.add(new Field("content", article.getContent(), Store.YES, Index.ANALYZED)); 
 
        return doc; 
    } 
 
    public static Article document2Article(Document doc) { 
        Article article = new Article(); 
 
        Integer id = Integer.parseInt(doc.get("id")); // 要把String型的值转为Integer型 
        article.setId(id); 
        article.setTitle(doc.get("title")); 
        article.setContent(doc.get("content")); 
 
        return article; 
    } 
 
} 

Configuration.java

package com.my.utils; 
 
import java.io.File; 
import java.io.IOException; 
 
import org.apache.lucene.analysis.Analyzer; 
import org.apache.lucene.analysis.standard.StandardAnalyzer; 
import org.apache.lucene.store.Directory; 
import org.apache.lucene.store.FSDirectory; 
import org.apache.lucene.util.Version; 
 
public class Configuration { 
 
    private static Directory directory; 
    private static Analyzer analyzer; 
 
    static { 
        // 读取配置文件,并初始化配置(这里只模拟一下) 
        try { 
            directory = FSDirectory.open(new File("./indexDir")); 
            analyzer = new StandardAnalyzer(Version.LUCENE_30); 
        } catch (IOException e) { 
            throw new RuntimeException(e); 
        } 
    } 
 
    public static Directory getDirectory() { 
        return directory; 
    } 
 
    public static Analyzer getAnalyzer() { 
        return analyzer; 
    } 
 
} 

LuceneUtils.java

package com.my.utils; 
 
import org.apache.lucene.index.IndexWriter; 
import org.apache.lucene.index.IndexWriter.MaxFieldLength; 
 
public class LuceneUtils { 
 
    private static IndexWriter indexWriter; 
 
    static { 
        // 在加载类时初始化一次 
        try { 
            indexWriter = new IndexWriter(Configuration.getDirectory(), Configuration.getAnalyzer(), MaxFieldLength.LIMITED); 
            System.out.println("-- 已经初始化IndexWriter --"); 
        } catch (Exception e) { 
            throw new RuntimeException(e); 
        } 
 
        // 在程序退出前关闭 
        Runtime.getRuntime().addShutdownHook(new Thread() { 
            @Override 
            public void run() { // 在JVM退出前会执行这个run()方法 
                try { 
                    indexWriter.close(); 
                    System.out.println("-- IndexWriter已关闭 --"); 
                } catch (Exception e) { 
                    throw new RuntimeException(e); 
                } 
            } 
        }); 
    } 
 
    public static IndexWriter getIndexWriter() { 
        return indexWriter; 
    } 
 
 
} 

ArticleIndexDao.java

package com.my.indexdao; 
 
import java.io.IOException; 
import java.util.ArrayList; 
import java.util.List; 
 
import org.apache.lucene.document.Document; 
import org.apache.lucene.index.Term; 
import org.apache.lucene.queryParser.MultiFieldQueryParser; 
import org.apache.lucene.queryParser.QueryParser; 
import org.apache.lucene.search.IndexSearcher; 
import org.apache.lucene.search.Query; 
import org.apache.lucene.search.ScoreDoc; 
import org.apache.lucene.search.TopDocs; 
import org.apache.lucene.util.Version; 
 
import com.my.bean.Article; 
import com.my.bean.QueryPageResult; 
import com.my.utils.ArticleDocumentUtils; 
import com.my.utils.Configuration; 
import com.my.utils.LuceneUtils; 
 
public class ArticleIndexDao { 
 
    /** 
     * 创建索引(保存到索引库) 
     *  
     * @param article 
     */ 
    public void save(Article article) { 
        // 1,把Article转成Document 
        Document doc = ArticleDocumentUtils.article2Document(article); 
 
        // 2,添加索引库中 
        try { 
            LuceneUtils.getIndexWriter().addDocument(doc); // 保存 
            LuceneUtils.getIndexWriter().commit(); // 提交更改 
        } catch (Exception e) { 
            throw new RuntimeException(e); 
        } 
    } 
 
    /** 
     * 删除索引 
     *  
     * Term:是指某字段中的某个关键词(在目录中出现的关键词) 
     *  
     * @param id 
     */ 
    public void delete(Integer id) { 
        try { 
            Term term = new Term("id", id.toString()); 
 
            LuceneUtils.getIndexWriter().deleteDocuments(term); // 删除所有包含指定term的数据 
            LuceneUtils.getIndexWriter().commit(); // 提交更改 
        } catch (Exception e) { 
            throw new RuntimeException(e); 
        } 
    } 
 
    /** 
     * 更新索引 
     *  
     * @param article 
     */ 
    public void update(Article article) { 
        try { 
            Term term = new Term("id", article.getId().toString()); 
            Document doc = ArticleDocumentUtils.article2Document(article); 
 
            LuceneUtils.getIndexWriter().updateDocument(term, doc); // 更新 
            LuceneUtils.getIndexWriter().commit(); // 提交更改 
 
            // // 更新就是先删除再创建 
            // indexWriter.deleteDocuments(term); 
            // indexWriter.addDocument(doc); 
        } catch (Exception e) { 
            throw new RuntimeException(e); 
        } 
    } 
 
 
} 

IndexDaoTest.java

package com.my.indexdao; 
 
import java.util.List; 
 
import org.junit.Test; 
 
import com.my.bean.Article; 
import com.my.bean.QueryPageResult; 
 
public class IndexDaoTest { 
 
    private ArticleIndexDao articleIndexDao = new ArticleIndexDao(); 
 
    @Test 
    public void testSave_1() { 
        // 模拟一条刚保存到数据库中的数据 
        Article article = new Article(); 
        article.setId(1); 
        article.setTitle("Lucene是全文检索的框架"); 
        article.setContent("如果信息检索系统在用户发出了检索请求后再去互联网上找答案,根本无法在有限的时间内返回结果。"); 
 
        // 建立索引 ? 
        articleIndexDao.save(article); 
    } 
 
    @Test 
    public void testSave_25() { 
        for (int i = 1; i <= 25; i++) { 
            // 模拟一条刚保存到数据库中的数据 
            Article article = new Article(); 
            article.setId(i); 
            article.setTitle("Lucene是全文检索的框架"); 
            article.setContent("如果信息检索系统在用户发出了检索请求后再去互联网上找答案,根本无法在有限的时间内返回结果。"); 
 
            // 建立索引 ? 
            articleIndexDao.save(article); 
        } 
    } 
 
    @Test 
    public void testDelete() { 
        articleIndexDao.delete(1); 
    } 
 
    @Test 
    public void testUpdate() { 
        // 模拟一条游离状态的数据 
        Article article = new Article(); 
        article.setId(1); 
        article.setTitle("Lucene是全文检索的框架"); 
        article.setContent("这是更新后的结果"); 
 
        articleIndexDao.update(article); 
    } 
 
 
 
} 

索引库优化:
1,合并索引库文件
核心API

IndexWriter.optimize() 
indexWriter.setMergeFactor(int) 

代码示例
OptimizeTest.java

package com.my.lucene; 
 
import org.apache.lucene.document.Document; 
import org.junit.Test; 
 
import com.my.bean.Article; 
import com.my.utils.ArticleDocumentUtils; 
import com.my.utils.LuceneUtils; 
 
public class OptimizeTest { 
 
    // 合并索引库中的多个小文件为一个大文件 
    @Test 
    public void testOptimize() throws Exception { 
        LuceneUtils.getIndexWriter().optimize(); 
    } 
 
    // 自动合并:在小文件的数量到达多少个后就自动的合并成一个大文件 
    @Test 
    public void testAutoOptimize() throws Exception { 
        // 设置当小文件的数量到达多少个之后就自动的合并,This must never be less than 2. The default value is 10. 
        LuceneUtils.getIndexWriter().setMergeFactor(3); // 设置后是在下一次的增删改操作后才会生效 
 
        // 添加一条索引数据 
        Article article = new Article(); 
        article.setId(1); 
        article.setTitle("Lucene是全文检索的框架"); 
        article.setContent("如果信息检索系统在用户发出了检索请求后再去互联网上找答案,根本无法在有限的时间内返回结果。"); 
        Document doc = ArticleDocumentUtils.article2Document(article); 
        LuceneUtils.getIndexWriter().addDocument(doc); 
    } 
 
 
} 

2,使用RAMDirectory
Lucene的API接口设计的比较通用,输入输出结构都很像数据库的表==>记录==>字段,所以很多传统的应用的文件、数据库等都可以比较方便的映射到Lucene的存储结构/接口中。总体上看:可以先把Lucene当成一个支持全文索引的数据库系统。
Lucene的索引存储位置使用的是一个接口(抽象类),也就可以实现各种各样的实际存储方式(实现类、子类),比如存到文件系统中,存在内存中、存在数据库中等等。Lucene提供了两个子类:FSDirectory与RAMDirectory。
1, FSDirectory:在文件系统中,是真实的文件夹与文件。
2, RAMDirectory:在内存中,是模拟的文件夹与文件。与FSDirectory相比:1因为没有IO操作,所以速度快。2,因为在内存中,所以在程序退出后索引库数据就不存在了。

代码示例:
DirectoryTest.java

package com.my.lucene; 
 
import java.io.File; 
 
import org.apache.lucene.document.Document; 
import org.apache.lucene.index.IndexWriter; 
import org.apache.lucene.index.IndexWriter.MaxFieldLength; 
import org.apache.lucene.store.Directory; 
import org.apache.lucene.store.FSDirectory; 
import org.apache.lucene.store.RAMDirectory; 
import org.junit.Test; 
 
import com.my.bean.Article; 
import com.my.utils.ArticleDocumentUtils; 
import com.my.utils.Configuration; 
 
public class DirectoryTest { 
 
    @Test 
    public void testRAMDirectory() throws Exception { 
        // 一、程序启动时加载到RAMDirectory中(在构造对象时传递一个参数就可以了) 
        Directory fsDir = FSDirectory.open(new File("./indexDir/")); // 文件系统中的真实的目录与文件:速度慢,能长久保存 
        Directory ramDir = new RAMDirectory(fsDir); // 内存中虚拟出来的目录与文件:速度快,程序退出就没有了 
 
        // ========== 在程序运行的过程,会对RAMDirectory进行操作 ========== 
        IndexWriter ramIndexWriter = new IndexWriter(ramDir, Configuration.getAnalyzer(), MaxFieldLength.LIMITED); 
 
        // 向ramDir中添加一条索引数据 
        Article article = new Article(); 
        article.setId(1); 
        article.setTitle("Lucene是全文检索的框架"); 
        article.setContent("如果信息检索系统在用户发出了检索请求后再去互联网上找答案,根本无法在有限的时间内返回结果。"); 
        Document doc = ArticleDocumentUtils.article2Document(article); 
        ramIndexWriter.addDocument(doc); // 添加 
        ramIndexWriter.close(); 
        // ========== 程序要退出了,对RAMDirectory的操作完毕 ========== 
 
        // 二、退出前把RAMDirectory中数据保存到FSDirectory中 
        // 如果有索引库,就追加,如果没有,就创建 
        // IndexWriter fsIndexWriter = new IndexWriter(fsDir, Configuration.getAnalyzer(), MaxFieldLength.LIMITED); 
        // 第3个参数create: true to create the index or overwrite the existing one; false to append to the existing index,如果在存在,就报错 
        IndexWriter fsIndexWriter = new IndexWriter(fsDir, Configuration.getAnalyzer(), true, MaxFieldLength.LIMITED); 
        fsIndexWriter.addIndexesNoOptimize(ramDir); // 把指定索引库中的数据添加到当前的索引库中 
        // fsIndexWriter.optimize(); 
        fsIndexWriter.close(); 
    } 
 
} 

原创文章,作者:奋斗,如若转载,请注明出处:https://blog.ytso.com/12106.html

(0)
上一篇 2021年7月19日
下一篇 2021年7月19日

相关推荐

发表回复

登录后才能评论