分布式--Lucene 全文检索

本文涉及的产品
.cn 域名,1个 12个月
简介: 1. Lucene 官网1). 概述Lucene是一款高性能的、可扩展的信息检索(IR)工具库。信息检索是指文档搜索、文档内信息搜索或者文档相关的元数据搜索等操作。

1. Lucene 官网

1). 概述

Lucene是一款高性能的、可扩展的信息检索(IR)工具库。信息检索是指文档搜索、文档内信息搜索或者文档相关的元数据搜索等操作。Lucene工具包下载

2). 索引过程:

①获取内容
②建立文档
获取原始内容后,就需要对这些内容进行索引,必须首先将这些内容转换成部件(通常称为文档),以供搜索引擎使用。文档主要包括几个带值的域,比如标题、正文、摘要、作者和链接。
③文档分析
搜索引擎不能直接对文本进行索引:确切地说,必须将文本分割成一系列被称为语汇单元的独立的原子元素。每一个语汇单元大致与语言中的“单词”对应起来。
④文档索引
在索引步骤中,文档被加入到索引列表。

3). 搜索组件

搜索处理过程就是从索引中查找单词,从而找到包含该单词的文档。搜索质量主要由查准率和查全率来衡量。查全率用来衡量搜索系统查找相关文档的能力;而查准率用来衡量搜索系统过滤非相关文档的能力。
①用户搜索界面
Lucene不提供默认的用户搜索界面,需要自己开发。
②建立查询
用户从搜索界面提交一个搜索请求,通常以HTML表单或者Ajax请求的形式由浏览器提交到你的搜索引擎服务器。然后将这个请求转换成搜索引擎使用的查询对象格式,这称为建立查询。
③搜索查询
查询检索索引并返回与查询语句匹配的文档,结果返回时按照查询请求来排序。
④展现结果
一旦获得匹配查询语句并排好序的文档结果集,接下来就得用直观的、经济的方式为用户展现结果。

4). 索引过程的核心类
IndexWriter 
Directory 
Analyzer 
Document 
Field

①IndexWriter
索引过程的核心组件。这个类负责创建新索引或者打开已有索引,以及向索引中添加、删除或更新被索引文档的信息。可以把IndexWriter看作这样一个对象:它为你提供针对索引文件的写入操作,但不能用于读取或搜索索引。IndexWriter需要开辟一定空间来存储索引,该功能可以由Directory完成。
②Directory
该类描述了Lucene索引的存放位置。它是一个抽象类,它的子类负责具体指定索引的存储路径。用FSDirectory.open方法来获取真实文件在文件系统的存储路径,然后将它们一次传递给IndexWriter类构造方法。IndexWriter不能直接索引文本,这需要先由Analyzer将文本分割成独立的单词才行。
③Analyzer
文本文件在被索引之前,需要经过Analyzer(分析器)处理。Analyzer是由IndexWriter的构造方法来指定的,它负责从被索引文本文件中提取语汇单元,并提出剩下的无用信息。如果被索引内容不是纯文本文件,那就需要先将其转换为文本文档。对于要将Lucene集成到应用程序的开发人员来说,选择什么样Analyzer是程序设计中非常关键的一步。分析器的分析对象为文档,该文档包含一些分离的能被索引的域。
④Document
Document对象代表一些域(Field)的集合。文档的域代表文档或者文档相关的一些元数据。元数据(如作者、标题、主题和修改日期等)都作为文档的不同域单独存储并被索引。Document对象的结构比较简单,为一个包含多个Filed对象容器;Field是指包含能被索引的文本内容的类。
⑤Field
索引中的每个文档都包含一个或多个不同命名的域,这些域包含在Field类中。每个域都有一个域名和对应的域值,以及一组选项来精确控制Lucene索引操作各个域值。

5).搜索过程中的核心类
IndexSearcher 
Term 
Query 
TermQuery 
TopDocs

①IndexSearcher
该类用于搜索由IndexWriter类创建的索引,它是连接索引的中心环节。可以将IndexSearcher类看作是一个以只读方式打开索引的类。它需要利用Directory实例来掌控前期创建的索引,然后才能提供大量的搜索方法。
②Term
Term对象是搜索功能的基本单元。Term对象包含一对字符串元素:域名和单词(或域名文本值)。
③Query
包含了一些非常有用的方法,TermQuery是它的一个子类。
④TermQuery
该类提供最基本的查询,用来匹配指定域中包含特定项的文档。
⑤TopDocs
该类是一个简单的指针容器,指针一般指向前N个排名的搜索结果,搜索结果即匹配查询条件的文档。

6). 域索引选项
  • Index.ANALYZED:使用分析器将值域分解成独立的语汇单元流,并使每个语汇单元能被搜索。该选项使用于普通文本域(如正文、标题、摘要等)。
  • Index.NOT_ANALYZED:对域进行索引,但不对String值进行分析。该操作实际上将值域作为单一语汇单元并使之能被搜索。该选项适用于索引那些不能被分解的值域,如URL、文件路径、日期、人名、社保号码和电话号码等。该选项尤其适用于“精确匹配”搜索。
  • Index.ANALYZED_NO_NORMS:不会在索引中存储norms信息。
  • Index.NOT_ANALYZED_NO_NORMS:不存储norms。用于在搜索期间节省索引空间和减少内存耗费。
7).域存储选项
  • Store.YES:指定存储域值。该情况下,原始的字符串值全部被保存在索引中,并可以由IndexReader类恢复。该选项对于需要展示搜索结果的一些域很有用(如URL、标题或数据库主键)。如果索引的大小在搜索程序考虑之列的话,不要存储太大的域值,因为存储这些域值会消耗掉索引的存储空间。
  • Store.NO:指定不存储域值。
8). Lucene 并发处理规则

任意数量的制度只读的IndexReader类都可以同时打开一个索引。在单个JVM内,利用资源和发挥效率最好的办法是用多线程共享单个的IndexReader实例。
对于一个索引来说,一次只能打开一个Writer。lucene采用文件锁来提供保障。一旦建立起IndexWriter对象,系统会分配一个锁,该锁只有当IndexWriter对象被关闭时才会释放。
IndexReader 对象甚至可以在IndexWriter对象正在修改索引时打开。每个IndexReader对象将向索引展示自己被打开的时间点。该对象只有在IndexWriter对象提交修改或自己被重新打开后才能获知索引的修改情况。在已经有IndexReader对象被打开的情况下,打开新的IndexReader时采用参数cache=true,这样新的IndexReader会持续检查索引的情况。
任何多个线程都可以共享同一个IndexReader类或IndexWriter类。这些类不仅是线程安全的,而且是线程友好的。

9). IndexReader和IndexWriter删除文档的区别
  • IndexReader能够根据文档号删除文档。Indexwriter不能进行这样的操作,因为文档号可能因为段合并操作而立即产生变化。
  • IndexReader 可以通过Term对象删除文档,与IndexWriter类似。但IndexReader会返回被删除的文档号,而IndexWriter不能。IndexReader可以立即决定删除哪个文档,因此就能够对这些文档数量进行计算;而IndexWriter仅仅是将被删除的Term进行缓存,后续在进行实际的删除操作。
  • 如果程序使用相同的reader进行搜索的话,IndexReader的删除操作会即使生效。IndexWriter的删除操作必须等到程序打开一个新Reader时才能被感知。
  • IndexWriter可以通过Query对象执行删除操作,但IndexReader则不行。

2. 子类

1). Directory子类
  • SimpleFSDirectory
    最简单的Directory子类,使用java.io.* API将文件存入文件系统,不能很好的支持多线程
    NIOFSDirectory
  • 使用java.nio.* API 将文件保存至文件系统。能很好支持除Windows之外的多线程操作,原因是Sun的JRE在windows平台上长期存在的问题。
  • NMapDirectory
    使用内存映射 I/O进行文件访问。对于64位JRE来说是一个很好选择,对于32位JRE并且索引尺寸相对较小时也可以使用该类
  • RAMDirectory
    将所有的文件都存入RAM中,但是不推荐使用于较多索引的情况。会造成资源的浪费,以及因为它使用1024字节大小的内部缓冲器。
  • FileSwitchDirectory
    使用两个文件目录,根据文件拓展名在两个目录之间切换使用
2). 核心锁实现
  • NativeFSLockFactory
    FSDirectory的默认锁,使用java.nio本地操作系统锁,在JVM还存在的情况下不会释放剩余的被锁文件。但该锁可能无法与一些共享文件系统很好地协同,特别是NFS文件系统
  • SimpleFSLockFactory
    使用Java的File.createNewFile API,它比NativeFSLockFactory更易于在不同文件系统间移植。
  • SingleInstanceLockFactory
    在内存中创建一个完全的锁,该类是RAMDirectory默认的锁实现子类。在程序知道所有IndexWriter将在同一个JVM实例化时使用该类
  • NoLockFactory
    完全关闭锁机制。只有在程序确认不需要使用Lucene通畅的锁保护机制时才能使用它。
3).搜索类
  • TermQuery:对索引中特定项进行搜索,查询值区分大小写。
  • TermRangeQuery:索引中各个Term对象会按照字典排序顺序进行排列,并允许在Lucene的TermRangeQuery对象提供的范围内进行文本项的直接搜索。
  • NumericRangeQuery:在指定的数字范围内搜索。和TermQuery类一样,newIntRange方法中的两个Boolean参数表示搜索范围是(用true表示)否(用false表示)包含起点和终点。
  • PrefixQuery:搜索包含以指定字符串开头的项的文档。
  • BooleanQuery:可以将各种查询类型组合成复杂的查询方式。
    • BooleanClause.Occur.MUST : 只有匹配该查询语句的文档才在考虑之列。
    • BooleanClause.Occur.SHOULD: 该项只是可选项。
    • BooleanClause.Occur.MUST_NOT: 意味着搜索结果不会包含任何匹配该查询子举的文档。默认允许包含1024个查询子句,超过最大值时,程序会抛出TooManyClauses异常。
  • PhraseQuery:根据位置信息定位某个距离范围内的项所对应的文档。在匹配的情况下,两个项的位置之间所允许的最大间隔距离称为slop,这里的距离是指项若要按顺序组成给定短语锁需要移动位置的次数。
  • WildcardQuery:使用不完整的、缺少某些字母的项进行查询。*代表0个或者多个字母,?代表0个或者1个字母。
  • FuzzyQuery:用于匹配与指定项相似的项。
  • MatchAllDocsQuery:匹配索引中的所有文档。
4). 常用分析器
  • WhitespaceAnalyzer:通过空格来分割文本信息,而并不对生成的语汇单元进行其他的规范化处理。
  • SimpleAnalyzer:首先通过非字母字符来分割文本信息,然后将语汇单元统一为小写形式。会去掉数字类型的字符,但会保留其他字符。
  • StopAnalyzer:会去除英文中的常用单词(如 the、a等)。
  • StandardAnalyzer:包含大量的逻辑操作来识别某些种类的语汇单元,比如公司名称、E-mail地址以及主机名称等。还会将语汇单元转换为小写形式,并去除停用词和标点符号。
5). 语汇单元属性
  • TermAttribute:语汇单元对应的文本
  • PositionIncrementAttribute:位置增量(默认值为1)
  • OffsetAttriute:起始字符和终止字符的偏移量
  • TypeAttribute:语汇单元类型(默认为单词)
  • FlagsAttribute:自定义标志位
  • PayloadAttribute:每个语汇单元的byte[]类型有效负载
6). 主要可用分析器
  • WhitespaceAnalyzer:根据空格拆分语汇单元
  • SimpleAnalyzer:根据非字母字符拆分文本,将其转换为小写形式
  • StopAnalyzer:根据非字母字符拆分文本,然后小写化,再移除停用词
  • KeywordAnalyzer:将整个文本作为一个单一语汇单元处理
  • StandardAnalyzer:基于复杂的语法来生成语汇单元,该语法能识别E-mail地址、首字母缩写词词、韩语/汉语/日语等字符、字母数字等,还能完成小写转换和移除停用词。
7).索引文件格式
  • 数据结构
    索引包含了存储的文档(document)正排、倒排信息,用于文本搜索。索引又分为多个段(segments),每个新添加的doc都会存到一个新segment中,不同的segments又会合并成一个segment。segment存储着具体的documents,每个doc有一系列的字段组成,一个field的值是多个词(term),一个term是以一些bytes。其递进关系如下:
    index -> segments -> documents -> fields -> terms
  • 文件格式
    • 全局文件
      • segments_N:记录索引的段数、各段名、各段中文档数、删除数和更新数。可能有多个segments_N文件,最大的N的segments_N是有效文件。
      • segments.gen:记录当前index的代数(generation),即segments_N的最大N。
      • write.lock:阻止多给我IndexWriter同时修改索引,一次只能有一个IndexWriter。
    • 段文件
      • 段描述:
        xxx.si:段的元数据,如此段的文档及相关文件
        xxx.del:删除的doc
      • field信息:
        xxx.fnm:field names,field名称、索引方式。存储域文件的信息
        xxx.fdx:field index,索引xxx.fdt。存储域数据的指针
        xxx.fdt:field data,存储stored fields
      • term信息
        xxx.tip:term index,xxx.tim的索引,实现对xxx.tim的随机存取
        xxx.tim:term directory,按字典顺序排列的terms,其值指向.doc/.pos
        xxx.doc:倒排列表,term所在的docs、在doc中的频率
        xxx.pos:倒排列表,term在doc中的位置
        xxx.pay:payloads and offsets,term在doc中的offset
      • term vector
        term vector 用于打分,存储StoreTermVectors的field
        xxx.tvx:term vector index,每个doc 在xxx.tvd、xxx.tvf中的位置
        xxx.tvd:term vector data file,每个doc的term vector field信息在xxx.tvf中的位置
        xxx.tvf:term vector fields,field的term列表及各term的频率、位置或者偏移
      • 归一化
        xxx.nvm:norms metadata
        xxx.nvd:norms data
      • doc values
        xxx.dvm:DocValues metadata
        xxx.dvd:DocValues data
      • 复合文件
        xxx.cfs,xxx.cfe:复合索引的文件,在系统上虚拟的一个文件,用于频繁的文件句柄
      • 词频文件
        xxx.frq:词频文件,包含文档列表以及每一个term和其词频

3. Luence 使用

1). 全文检索过程
img_442ef4b28bb6edc54dd780958fbad686.png
图1.png
2). 创建文档对象

获取原始内容的目的是为了索引,在索引前,需要将原始内容创建成文档(Document),文档中包括一个一个的域(Field),域中存储内容;我们可以将磁盘上的一个文件当成一个 document, Document 中包括一些Field(file_name 文件名称, file_path文件路径, file_size 文件大小, file_content 文件内容);一个 Document 可以有多个 Field,同一个Document,可以有相同的 Field(域名和域值都相同);每一个 Document 都有一个唯一的编号,就是文档 id;


img_64bc397764f3b485fce8bb161fd23e91.png
图2.png
3). 分析文档

将原始内容创建为包含域(Field)的文档(document),需要再对域中的内容进行分析,分析的过程是经过对原始文档提取单词,将字母转为小写,去除标点符号,去除停用词等过程生成最终的语汇单元,可以将语汇单元理解为一个一个的单词;每一个单词叫做一个Term,不同的域中拆分出来的相同的单词是不同的term; term中包含两部分,一部分是文档的域名, 另一部分是单词的内容。

Field 域的属性

  • 是否分析: 是否对域的内容进行分词处理;
  • 是否索引: 将 Field 分析后的词或整个 Field 值进行索引,只有建立索引,才能搜索到;
  • 是否存储: 存储在文档中的 Field 才可以从 Document 中获取;


    img_e5ffe3eac613a3d20c4ea21806fa5505.png
    图3.png
4). 创建索引

对所有文档分析得出的语汇单元进行索引,索引的目的是为了搜索,最终要实现只搜索被索引的语汇单元从而找到 Document,这种索引的结构叫倒排索引结构;传统方法是根据文件找到该文件的内容,在文件内容中匹配搜索关键字,这种方法是顺序扫描方法,数据量大,搜索慢;倒排索引结构是根据内容(词语)找文档; 顺序扫描方法是根据文档查找里面的内容。


img_59c559666e949be10e925862c580a2f8.png
图4.png

I. 导入jar包
commons-io-2.5.jar、lucene-analyzers-common-7.3.0.jar、lucene-core-7.3.0.jar、lucene-queryparser-7.3.0.jar


img_a6d9d2bab1b04f38678631645eeacac4.png
图5.png

II. 测试代码

/**
 *  Lucene索引 测试
 * 使用到的Jar包:
 * commons-io-2.5.jar
 * lucene-analyzers-common-7.3.0.jar
 * lucene-core-7.3.0.jar
 * lucene-queryparser-7.3.0.jar
 * 
 * @author mazaiting
 */
public class IndexTest {
    
    /**
     * 测试创建索引
     * @throws IOException
     */
    @Test
    public void createIndexTest() throws IOException {
        // 指定索引库的存放位置(Directory 对象)
        Path path = FileSystems.getDefault().getPath("D:\\distribution\\lucene");
        // 1. 创建Directory对象
        // FSDirectory磁盘存储; Directory 保存索引
        Directory directory = FSDirectory.open(path);
        // 2. 指定分词器
        // 基于复杂的语法来生成语汇单元,该语法能识别E-mail地址、首字母缩写词词、
        // 韩语/汉语/日语等字符、字母数字等,还能完成小写转换和移除停用词
        Analyzer analyzer = new StandardAnalyzer();
        // IndexWriter配置对象
        IndexWriterConfig config = new IndexWriterConfig(analyzer);
        // 3. 创建IndexWriter对象
        IndexWriter indexWriter = new IndexWriter(directory, config);
        
        // 4. 指定原始文件的目录
        File fileDir = new File("G:\\test");
        // 获取文件夹和文件列表
        File[] fileList = fileDir.listFiles();
        
        // 遍历
        for (File file : fileList) {
            // 判断是否为路径,如果不是路径则执行里面的内容
            if (!file.isDirectory()) {
                // 5. 创建文档对象
                Document document = new Document();
                
                // 文件名称
                // 分词 索引 存储
                String fileName = file.getName();
                Field fileNameField = new TextField("fileName", fileName, Store.YES);
                
                // 文件大小
                // 分词 索引 存储
                long fileSize = FileUtils.sizeOf(file);
                Field fileSizeField = new TextField("fileSize", String.valueOf(fileSize), Store.YES);
                
                // 文件路径
                // 不分词 不索引 存储
                String filePath = file.getPath();
                Field filePathField = new StoredField("filePath", filePath);
                
                // 文件内容
                String fileContent = FileUtils.readFileToString(file, "UTF-8");
                Field fileContentField = new TextField("fileContent", fileContent, Store.YES);
                        
                // 添加字段
                document.add(fileNameField);
                document.add(fileSizeField);
                document.add(filePathField);
                document.add(fileContentField);
                
                // 使用IndexWriter对象将Document对象写入到索引库
                indexWriter.addDocument(document);
            }
        }
        // 关闭IndexWriter对象
        indexWriter.close();
    }   
    
}

III. 执行测试代码
G:\\test目录文件如下:

img_4df0648b551266487c837684597111e3.png
图6.png

在路径D:\distribution\lucene\多了些文件

img_277d2aae02f602345b303003bc05f139.png
图7.png
5). 查询索引

用户输入查询关键字执行搜索前,需要先创建一个查询对象,查询对象中可以指定查询要搜索的 Field 文档域,查询关键字等,查询对象会生成具体的查询语法;根据查询语法在倒排索引词典表中分别找出对应搜索词的索引,从而找到索引所链接的文档链表。


img_4314ef89412e29c77bec0be98cc3334f.png
图7.png

I. 测试代码

/**
 * Lucene索引 测试
 * 使用到的Jar包:
 * commons-io-2.5.jar
 * lucene-analyzers-common-7.3.0.jar
 * lucene-core-7.3.0.jar
 * lucene-queryparser-7.3.0.jar
 * 
 * @author mazaiting
 */
public class IndexTest {
    /**
     * 查询索引
     * 步骤:
     *  1. 创建一个Directory对象,用于指定索引库存放的位置
     *  2. 创建一个IndexReader对象,需要指定Directory对象,用于读取索引库中的文件
     *  3. 创建一个IndexSearcher对象,需要指定IndexReader对象
     *  4. 创建一个TermQuery对象,指定查询的域和查询的关键词
     *  5. 执行查询
     *  6. 返回查询结果,遍历查询结果并输出
     *  7. 关闭IndexReader    
     * @throws IOException 
     */
    @Test
    public void searchIndexTest() throws IOException {
        // Directory, 指定索引库存放的位置
        Path path = FileSystems.getDefault().getPath("D:\\distribution\\lucene");
        Directory directory = FSDirectory.open(path);
        // IndexReader, 读取索引库中的文件
        IndexReader indexReader = DirectoryReader.open(directory);
        // IndexSearcher, 用于查询
        IndexSearcher indexSearcher = new IndexSearcher(indexReader);
        // TermQuery, 指定查询的域和查询的关键词
        Query query = new TermQuery(new Term("fileName", "java.txt"));
        // 执行查询
        TopDocs topDocs = indexSearcher.search(query, 100);
        // 获取数组
        ScoreDoc[] scoreDocs =  topDocs.scoreDocs;
        System.out.println(scoreDocs.length);
        // 遍历结果文档
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 获取文档id
            int docId = scoreDoc.doc;
            // 通过id从索引中获取对应的文档
            Document document = indexReader.document(docId);
            // 获取文件名称
            String fileName = document.get("fileName");
            // 获取文件路径
            String filePath = document.get("filePath");
            // 获取文件大小
            String fileSize = document.get("fileSize");
            // 获取文件内容
            String fileContent = document.get("fileContent");
            System.out.println("==========================================");
            System.out.println("文件名:" + fileName + "\n"
                    + "文件大小: " + fileSize + "\n"
                    + "文件路径:" + filePath + "\n"
                    + "文件内容:" + fileContent);
            
        }
        // 关闭IndexReader
        indexReader.close();
    }
    
}

II. 执行测试代码
打印结果:


img_1e5e8888b742538333f7087928789e9b.png
图8.png
6). 分词器

支持中文的分词器: IKAnalyzer
从一个 Reader 字符流开始,创建一个基于 Reader 的 Tokenizer分词器,经过三个 TokenFilter,生成语汇单元 Tokens。

img_0cd99e77d1e635e1f888da5ebbc2e87f.png
图9.png

I. 代码

/**
 * Lucene索引 测试
 * 使用到的Jar包:
 * commons-io-2.5.jar
 * lucene-analyzers-common-7.3.0.jar
 * lucene-core-7.3.0.jar
 * lucene-queryparser-7.3.0.jar
 * 
 * @author mazaiting
 */
public class IndexTest {
    
    /**
     * 查看标准分词器的分词效果
     * @throws IOException 
     */
    @Test
    public void analyzerTest() throws IOException {
        // 创建一个标准分词器对象
        Analyzer analyzer = new StandardAnalyzer();
        // 获得TokenStream对象
        // 参数1: 字段名,可以随便给;参数2: 要分析的文本内容
        TokenStream tokenStream = analyzer.tokenStream("test", 
                "The Spring Framework provides a comprehensive programming and configuration model.");
        // 添加引用,可以获得每个关键字
        CharTermAttribute charTermAttribute = tokenStream.addAttribute(CharTermAttribute.class);
        // 添加一个偏移量的引用,记录了关键词的开始位置及结束位置
        OffsetAttribute offsetAttribute = tokenStream.addAttribute(OffsetAttribute.class);
        // 将指针调整到列表的头部
        tokenStream.reset();
        // 遍历关键词列表,通过incrementToken方法判断列表是否结束
        while (tokenStream.incrementToken()) {
            // 关键词其实位置
            System.out.println("start->" + offsetAttribute.startOffset());
            // 取关键词
            System.out.println(charTermAttribute);
            // 结束位置
            System.out.println("end->" + offsetAttribute.endOffset());          
        }
        // 关闭
        tokenStream.close();
        analyzer.close();
    }
}

II. 执行测试


img_f1b883735f87d2cfeb785e9f7e181c98.png
图10.png
7). 索引库维护工具类
/**
 * 索引库维护工具类
 * @author mazaiting
 */
public class LuceneManager {
    /**
     * 获取IndexWriter对象
     * @return
     */
    public IndexWriter getIndexWriter() {
        try {
            // 获取索引库路径
            Path path = FileSystems.getDefault().getPath("D:\\distribution\\lucene");
            // 创建索引库字典
            Directory directory = FSDirectory.open(path);
            // 创建分析器
            Analyzer analyzer = new StandardAnalyzer();
            // 创建IndexWriter配置
            IndexWriterConfig config = new IndexWriterConfig(analyzer);
            return new IndexWriter(directory, config);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }
    
    /**
     * 全部删除 
     * @throws IOException
     */
    @Test
    public void delAllTest() throws IOException {
        IndexWriter writer = getIndexWriter();
        writer.deleteAll();
        writer.close();
    }
    
    /**
     * 根据条件删除
     * @throws IOException 
     */
    @Test
    public void delTest() throws IOException {
        IndexWriter writer = getIndexWriter();
        Query query = new TermQuery(new Term("fileName", "java"));
        writer.deleteDocuments(query);
        writer.close();
    }
    
    /**
     * 更新
     * @throws IOException 
     */
    @Test
    public void update() throws IOException {
        IndexWriter writer = getIndexWriter();
        Document document = new Document();
        document.add(new TextField("fileName", "测试文件名", Store.YES));
        document.add(new TextField("fileContent", "测试文件内容", Store.YES));
        
        // 将lucene删除, 然后添加
        writer.updateDocument(new Term("fileName", "lucene"), document);
        writer.close();
    }
    
}
8). 索引库查询

对要搜索的信息创建 Query 查询对象,Lucene会根据 Query 查询对象生成最终的查询语法;
可通过两种方法创建查询对象:

  • 使用 Lucene 提供的 Query子类;
  • 使用 QueryParse 解析查询表达式, 需要加入lucene-queryparser-7.3.0.jar
/**
 * 索引库维护工具类
 * 
 * @author mazaiting
 */
public class LuceneManager {
    /**
     * 获取IndexSearcher
     * 
     * @return
     */
    public IndexSearcher getIndexSearcher() {
        try {
            // 获取索引库路径
            Path path = FileSystems.getDefault().getPath("D:\\distribution\\lucene");
            // 创建索引库字典
            Directory directory = FSDirectory.open(path);
            // 创建索引读取者
            IndexReader indexReader = DirectoryReader.open(directory);
            return new IndexSearcher(indexReader);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 获取执行结果
     * 
     * @throws IOException
     */
    public void printResult(IndexSearcher indexSearcher, Query query) throws IOException {
        // 执行查询
        TopDocs topDocs = indexSearcher.search(new TermQuery(new Term("fileName")), 10);
        // 获取数组
        ScoreDoc[] scoreDocs = topDocs.scoreDocs;
        System.out.println(scoreDocs.length);
        // 遍历结果文档
        for (ScoreDoc scoreDoc : scoreDocs) {
            // 获取文档id
            int docId = scoreDoc.doc;
            // 通过id从索引中获取对应的文档
            Document document = indexSearcher.doc(docId);
            // 获取文件名称
            String fileName = document.get("fileName");
            // 获取文件路径
            String filePath = document.get("filePath");
            // 获取文件大小
            String fileSize = document.get("fileSize");
            // 获取文件内容
            String fileContent = document.get("fileContent");
            System.out.println("==========================================");
            System.out.println("文件名:" + fileName + "\n" + "文件大小: " + fileSize + "\n" + "文件路径:" + filePath + "\n"
                    + "文件内容:" + fileContent);

        }
    }
    
    /**
     * 查询所有
     * @throws IOException 
     */
    @Test
    public void matchAllDocsQueryTest() throws IOException {
        // 获取查询索引对象
        IndexSearcher indexSearcher = getIndexSearcher();
        // 查询所有
        Query query = new MatchAllDocsQuery();
        // 打印结果
        printResult(indexSearcher, query);
        // 关闭资源
        indexSearcher.getIndexReader().close();
    }
    
    /**
     * 组合查询
     * @throws IOException 
     */
    @Test
    public void boolQueryTest() throws IOException {
        // 创建搜索
        IndexSearcher indexSearcher = getIndexSearcher();
        // 创建查询
        Query query1 = new TermQuery(new Term("fileName", "java.txt"));
        Query query2 = new TermQuery(new Term("fileName", "c.txt"));
        // 构建表达式 
        // Occur.MUST: 必须满足此条件, 相当于 and
        // Occur.SHOULD: 应该满足此条件, 但是不满足也可以, 相当于 or
        // Occur.MUST_NOT: 必须不满足, 相当于 not
        BooleanClause clause1 = new BooleanClause(query1, Occur.SHOULD);
        // Build模式创建
        BooleanQuery.Builder builder = new BooleanQuery.Builder();
        // 添加表达式
        builder.add(clause1);
        // 添加查询
        builder.add(query2, Occur.SHOULD);
        // 打印
        printResult(indexSearcher, builder.build());
        // 关闭资源
        indexSearcher.getIndexReader().close();
    }
    
    /**
     * 使用QueryParser解析查询表达式
     * @throws ParseException 
     * @throws IOException 
     */
    @Test
    public void queryParserTest() throws ParseException, IOException {
        IndexSearcher indexSearcher = getIndexSearcher();
        // 创建QueryParser对象,其中参数一:字段名,参数而分词器
        QueryParser queryParser = new QueryParser("fileName", new StandardAnalyzer());
        // 此时:表示使用默认域:fileName
        // 查询fileContent域
        Query query = queryParser.parse("fileContent:apache");
        // 打印
        printResult(indexSearcher, query);
        // 关闭资源
        indexSearcher.getIndexReader().close();
    }
    
    /**
     * 指定多个默认搜索域
     * @throws ParseException 
     * @throws IOException 
     */
    @Test
    public void multiFieldQueryParser() throws ParseException, IOException {
        IndexSearcher indexSearcher = getIndexSearcher();
        // 指定多个默认搜索域
        String[] fields = {"fileName", "fileContent"};
        // 创建MultiFieldQueryParser对象
        MultiFieldQueryParser queryParser = new MultiFieldQueryParser(fields, new StandardAnalyzer());
        // 创建查询
        Query query = queryParser.parse("apache");
        // 输出查询条件
        System.out.println(query);
        // 执行查询
        printResult(indexSearcher, query);
        // 关闭资源
        indexSearcher.getIndexReader().close();
    }
}

代码下载

目录
相关文章
|
4月前
|
运维 监控 Java
在大数据场景下,Elasticsearch作为分布式搜索与分析引擎,因其扩展性和易用性成为全文检索首选。
【7月更文挑战第1天】在大数据场景下,Elasticsearch作为分布式搜索与分析引擎,因其扩展性和易用性成为全文检索首选。本文讲解如何在Java中集成Elasticsearch,包括安装配置、使用RestHighLevelClient连接、创建索引和文档操作,以及全文检索查询。此外,还涉及高级查询、性能优化和故障排查,帮助开发者高效处理非结构化数据。
71 0
|
1月前
|
NoSQL Java Redis
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
Redis分布式锁在高并发场景下是重要的技术手段,但其实现过程中常遇到五大深坑:**原子性问题**、**连接耗尽问题**、**锁过期问题**、**锁失效问题**以及**锁分段问题**。这些问题不仅影响系统的稳定性和性能,还可能导致数据不一致。尼恩在实际项目中总结了这些坑,并提供了详细的解决方案,包括使用Lua脚本保证原子性、设置合理的锁过期时间和使用看门狗机制、以及通过锁分段提升性能。这些经验和技巧对面试和实际开发都有很大帮助,值得深入学习和实践。
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
|
3月前
|
NoSQL Redis
基于Redis的高可用分布式锁——RedLock
这篇文章介绍了基于Redis的高可用分布式锁RedLock的概念、工作流程、获取和释放锁的方法,以及RedLock相比单机锁在高可用性上的优势,同时指出了其在某些特殊场景下的不足,并提到了ZooKeeper作为另一种实现分布式锁的方案。
112 2
基于Redis的高可用分布式锁——RedLock
|
3月前
|
缓存 NoSQL Java
SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解分布式情况下如何添加分布式锁 【续篇】
这篇文章是关于如何在SpringBoot应用中整合Redis并处理分布式场景下的缓存问题,包括缓存穿透、缓存雪崩和缓存击穿。文章详细讨论了在分布式情况下如何添加分布式锁来解决缓存击穿问题,提供了加锁和解锁的实现过程,并展示了使用JMeter进行压力测试来验证锁机制有效性的方法。
SpringBoot整合Redis、以及缓存穿透、缓存雪崩、缓存击穿的理解分布式情况下如何添加分布式锁 【续篇】
|
7天前
|
NoSQL Redis
Redis分布式锁如何实现 ?
Redis分布式锁通过SETNX指令实现,确保仅在键不存在时设置值。此机制用于控制多个线程对共享资源的访问,避免并发冲突。然而,实际应用中需解决死锁、锁超时、归一化、可重入及阻塞等问题,以确保系统的稳定性和可靠性。解决方案包括设置锁超时、引入Watch Dog机制、使用ThreadLocal绑定加解锁操作、实现计数器支持可重入锁以及采用自旋锁思想处理阻塞请求。
41 16
|
1月前
|
缓存 NoSQL Java
大数据-50 Redis 分布式锁 乐观锁 Watch SETNX Lua Redisson分布式锁 Java实现分布式锁
大数据-50 Redis 分布式锁 乐观锁 Watch SETNX Lua Redisson分布式锁 Java实现分布式锁
59 3
大数据-50 Redis 分布式锁 乐观锁 Watch SETNX Lua Redisson分布式锁 Java实现分布式锁
|
1月前
|
NoSQL Redis 数据库
计数器 分布式锁 redis实现
【10月更文挑战第5天】
47 1
|
1月前
|
NoSQL 算法 关系型数据库
Redis分布式锁
【10月更文挑战第1天】分布式锁用于在多进程环境中保护共享资源,防止并发冲突。通常借助外部系统如Redis或Zookeeper实现。通过`SETNX`命令加锁,并设置过期时间防止死锁。为避免误删他人锁,加锁时附带唯一标识,解锁前验证。面对锁提前过期的问题,可使用守护线程自动续期。在Redis集群中,需考虑主从同步延迟导致的锁丢失问题,Redlock算法可提高锁的可靠性。
73 4
|
1月前
|
存储 缓存 NoSQL
大数据-38 Redis 高并发下的分布式缓存 Redis简介 缓存场景 读写模式 旁路模式 穿透模式 缓存模式 基本概念等
大数据-38 Redis 高并发下的分布式缓存 Redis简介 缓存场景 读写模式 旁路模式 穿透模式 缓存模式 基本概念等
61 4

热门文章

最新文章