kakiro-webカキローウェブ

システム開発情報とコンピューター書籍の紹介サイト

【Lucene】基本処理 インデックスの検索

文書データをインデックスに登録する処理が完成したら、次にそのインデックスから目的のキーワードを含む文書データを検索する処理を作成します。

インデックスの検索を行う処理の基本的な流れを理解するために、簡単なサンプルプログラムを以下に示します。

ここではインデックスの登録のページで紹介した処理により登録したインデックスから検索を行っています。

import java.io.File;
import java.util.List;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexableField;
import org.apache.lucene.queryparser.classic.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.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;

public class SearchIndex {

    public static void main(String[] args) {
        try {
            SearchIndex si = new SearchIndex();
            si.main();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void main() throws Exception {
        //検索を行うインデックスが登録されているディレクトリのパス
        String indexPath = "index";

        Directory dir = null;
        IndexReader reader = null;
        Analyzer analyzer = null;

        try {
            dir = FSDirectory.open(new File(indexPath));//---(1)

            reader = DirectoryReader.open(dir);//---(2)

            IndexSearcher searcher = new IndexSearcher(reader);//---(3)

            analyzer = new StandardAnalyzer(Version.LUCENE_46);//---(4)

            QueryParser queryParser = new QueryParser(Version.LUCENE_46, "txt1", analyzer);//---(5)

            Query query = queryParser.parse("abc");

            TopDocs topDocs = searcher.search(query, 5);//---(6)

            ScoreDoc[] scoreDocs = topDocs.scoreDocs;//---(7)

            for (ScoreDoc scoreDoc : scoreDocs) {
                int docId = scoreDoc.doc;//---(8)

                System.out.println("---------- docId:" + docId + " ----------");

                Document doc = searcher.doc(docId);//---(9)

                List<IndexableField> indexableFieldList = doc.getFields();//---(10)

                for (IndexableField indexableField : indexableFieldList) {
                    String fieldName = indexableField.name();//---(11)

                    System.out.println("----- fieldName:" + fieldName + " -----");

                    String fieldText = indexableField.stringValue();//---(12)

                    System.out.println("fieldText:" + fieldText);
                }
            }
        } finally {
            if (analyzer != null) {
                analyzer.close();
            }

            if (reader != null) {
                reader.close();
            }

            if (dir != null) {
                dir.close();
            }
        }
    }

}

上記プログラムの解説を行います。

(1)

検索を行うインデックスの読み込み先はDirectoryで指定を行います。

ここではファイルシステム上のインデックスを読み込みますので、FSDirectoryを使用してインデックスの読み込み先のディレクトリのパスを指定し、Directoryの生成を行っています。

Directoryにはいくつかの種類があり、メモリ上にインデックスを登録するRAMDirectoryというものもあります。

(2)

インデックスの読み込みを行うにはIndexReaderを使用します。

ここではDirectoryReaderのopenメソッドに(1)で生成したDirectoryを指定して、IndexReaderの生成を行っています。

(3)

インデックスの検索を行うにはIndexSearcherを使用します。

コンストラクタでは(2)で生成したIndexReaderを指定しています。

(4)

検索条件に指定された文字列からキーワードの抽出を行うにはAnalyzerを使用します。

Analyzerにはいくつかの種類があり、使用するものによって文字列からキーワードを抽出する方法が変わります。

ここでは標準的なAnalyzerとなるStandardAnalyzerを使用しています。

コンストラクタでは使用するLuceneのバージョンを指定しています。

Analyzerは重要な役割を持つものになりますので、別途解説を行います。

(5)

検索条件の指定にはQueryを使用するのですが、検索条件に指定された文字列からQueryを生成するのにはQueryParserを使用します。

コンストラクタでは使用するLuceneのバージョン、検索を行うフィールドの名前、(4)で生成したAnalyzerを指定しています。

生成したQueryParserのparseメソッドに検索条件となる文字列を指定して、Queryの生成を行っています。

Queryは重要な役割を持つものになりますので、別途解説を行います。

(6)

(3)で生成したIndexSearcherのsearchメソッドに(5)で生成したQueryを指定して、インデックスの検索を行っています。

第2引数は検索結果として取得するDocumentの数を指定しています。

検索結果はTopDocsという形で取得できます。

(7)

検索結果となるTopDocsからDocumentを1つずつ取得するために、TopDocsのフィールドscoreDocsからScoreDocの配列を取得しています。

(8)

ScoreDocの配列をループして、ScoreDocからDocumentのIDを取得しています。

DocumentのIDはDocumentを一意に識別するための値で、インデックスの登録時に0から振られる連番です。

(9)

(3)で生成したIndexSearcherのdocメソッドに(8)で取得したDocumentのIDを指定することで、Documentの取得を行うことができます。

この処理は内部的にはIndexSearcherの内部に保持されているIndexReaderのdocumentメソッドにDocumentのIDを指定して、Documentの取得を行うようになっています。

(10)

Documentに設定されているフィールドを1つずつ取得するために、DocumentのgetFieldsメソッドを呼び出してIndexableFieldのリストを取得しています。

IndexableFieldは全てのフィールドが継承いているFieldクラスが実装しているインターフェースになります。

(11)

IndexableFieldのリストをループして、IndexableFieldのnameメソッドからフィールドの名前を取得しています。

(12)

同ループのIndexableFieldのstringValueメソッドからフィールドの値をString型で取得しています。

このサンプルプログラムの実行結果は以下のようになります。

---------- docId:1 ----------
----- fieldName:txt1 -----
fieldText:abc def ghi
----- fieldName:str1 -----
fieldText:jkl mno pqr

(5)で検索条件として、フィールド名「txt1」に値「abc」を持つものをAnalyzerで解析して検索するように指定しましたので、インデックスに登録されているフィールドの値に「abc」を持つDocumentが検索されます。

インデックスに登録されているフィールドの値に「abcd」を持つDocumentのほうも検索されるようにするには、ワイルドカードの指定が必要となってくるのですが、この辺りの検索条件の指定に関する詳細は、別途Queryの解説で行います。