kakiro-webカキローウェブ

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

【Lucene】基本処理 インデックスの確認

インデックスの登録のページで省略していました、登録したインデックスの確認処理を以下に示します。

import java.io.File;
import java.util.Iterator;
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.document.Field;
import org.apache.lucene.document.StringField;
import org.apache.lucene.document.TextField;
import org.apache.lucene.index.AtomicReader;
import org.apache.lucene.index.AtomicReaderContext;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.Fields;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.index.IndexWriterConfig.OpenMode;
import org.apache.lucene.index.Terms;
import org.apache.lucene.index.TermsEnum;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.BytesRef;
import org.apache.lucene.util.Version;

public class WriteIndex {

    public static void main(String[] args) {
        //(略)※別途記載
    }

    private void main() throws Exception {
        //(略)※別途記載
    }

    private void checkIndex(IndexWriter writer) throws Exception {
        IndexReader reader = null;

        try {
            reader = DirectoryReader.open(writer, false);//---(1)

            List<AtomicReaderContext> atomicReaderContextList = reader.leaves();//---(2)

            for (AtomicReaderContext atomicReaderContext : atomicReaderContextList) {
                AtomicReader atomicReader = atomicReaderContext.reader();//---(3)

                Fields fields = atomicReader.fields();//---(4)

                Iterator<String> itrFieldName = fields.iterator();//---(5)

                while (itrFieldName.hasNext()) {
                    String fieldName = itrFieldName.next();//---(6)

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

                    Terms terms = fields.terms(fieldName);//---(7)

                    TermsEnum termsEnum = terms.iterator(null);//---(8)

                    BytesRef bytesRef = null;

                    while ((bytesRef = termsEnum.next()) != null) {
                        String fieldText = bytesRef.utf8ToString();//---(9)

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

}

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

(1)

登録されているインデックスの読み込みを行うにはIndexReaderを使用します。

ここではDirectoryReaderのopenメソッドにIndexWriterを指定して、登録を行っていたインデックスと同じ参照先から読み込むようにしています。

第2引数は削除されているインデックスの扱いを指定するもののようなのですが、ここでは特に意識する必要もなさそうで、falseにしたほうがパフォーマンスがよいとドキュメントに記載がありますので、falseを指定しています。

このサンプルプログラムではどちらを指定しても実行結果に変わりはありません。

(2)

ここの処理はちょっとややこしいのですが、IndexReaderにはインデックスの保存先にアクセスを行うCompositeReaderを継承したもの(DirectoryReaderのようなもの)と、インデックスのデータを読み込むAtomicReaderを継承したものの2種類あり、ここではAtomicReaderのほうを取得しにいっています。

AtomicReaderは階層構造になっていて、取得結果が複数になることもあるようです。

取得結果はAtomicReaderContextのリストになってますが、AtomicReaderContextはIndexReaderContextを継承したクラスで、IndexReaderContextはIndexReaderの階層情報を保持するものとなっています。

ここではIndexReaderのleavesメソッドを呼び出していますが、内部的にはIndexReaderからIndexReaderContextを取得し、IndexReaderContextのleavesメソッドが呼び出されているようになっています。

(3)

AtomicReaderContextのリストをループして、AtomicReaderContextからAtomicReaderを取得しています。

現在の階層を示すIndexReaderContextから、その階層に対するIndexReaderを取得しているイメージです。

(4)

AtomicReaderから読み込みが行えるインデックスのフィールド情報となるFieldsを、fieldsメソッドを呼び出して取得しています。

尚、IndexReaderから全ての階層のFieldsをまとめて取得できる、MultiFieldsのgetFieldsメソッドというものもあるのですが、内部的にはここで行っているようなループ処理を行っていて、自分でループ処理を行ったほうがパフォーマンスがよいとドキュメントに記載がありましたので、サンプルプログラムはループ処理にしました。

(5)

Fieldsのiteratorメソッドを呼び出して、読み込み先のインデックスにあるフィールド名のIteratorを取得しています。

(6)

Iteratorをループして、フィールド名を1つずつ取得しています。

(7)

Fieldsからフィールド名に対するフィールドの値となるTermsを取得しています。

文書データを登録するときにフィールド名に対するフィールドの値のテキストデータはAnalyzerによって1つ1つのキーワードに分割されるのですが、その1つのキーワードはTermと呼ばれており、TermsはそのTermが集まったものとなります。

(8)

Termsから1つ1つのTermを取得するには、iteratorメソッドを呼び出してTermsEnumを取得する必要があります。

iteratorメソッドの引数には再利用したいTermsEnumを指定できるのですが、ここでは特にありませんのでnullを指定しています。

(9)

TermsEnumをループしてTermを1つずつ取得しています。

Termの取得結果はBytesRefというものになります。BytesRefのutf8ToStringメソッドを呼び出すことで、Termの文字列表現が取得できます。

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

----- fieldName:str1 -----
fieldText:gh ij kl
fieldText:jkl mno pqr
fieldText:mnop qrst uvwx
----- fieldName:txt1 -----
fieldText:ab
fieldText:abc
fieldText:abcd
fieldText:cd
fieldText:def
fieldText:ef
fieldText:efgh
fieldText:ghi
fieldText:ijkl

StringFieldを使用してフィールド名「str1」でインデックスの登録を行ったフィールドの値は、そのままの文字列で登録が行われています。

TextFieldを使用してフィールド名「txt1」でインデックスの登録を行ったフィールドの値は、半角スペースで分割されて登録が行われています。

StringFieldはAnalyzerによる解析が行われていないためそのままの文字列となり、TextFieldはAnalyzerによる解析が行われたため分割された結果となっています。

ひとまずここではDocumentとFieldを使用してインデックスに登録した文書データは、同じフィールド名のものはまとめられて、フィールド名に対するキーワードの形で登録されることを押さえておいてください。

Documentは以下のように登録されています。

Document ID フィールド「txt1」のキーワード フィールド「str1」のキーワード
0 ab gh ij kl
cd
ef
1 abc jkl mno pqr
def
ghi
2 abcd mnop qrst uvwx
efgh
ijkl

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