Search-Engine/Lucene 2019. 5. 25. 21:04

루씬은 색인 요청이 올때마다 새로운 세그먼트가 추가된다. 그리고 일정한 주기로 세그먼트들을 병합하는 과정을 갖는다. 만약 이러한 루씬에 인메모리버퍼가 하는 역할은 무엇일까? 우선 인메모리버퍼가 없는 루씬을 가정한다면, 만약 순간적으로 대용량의 데이터의 색인요청이 많아질 경우 세그먼트(역색인 파일)의 개수가 너무 많아져서 문제가 될 수 있다. 파일이 갑자기 많아지고 이는 당연히 색인에 지연이 생길 것이고 최종적으로 서비스 장애로 이어질 것이다. 하지만 실제적으로 루씬은 색인 작업이 요청되면 전달된 데이터는 일단 인메모리버퍼에 순서대로 쌓이고 버퍼가 일정크기 이상의 데이터가 쌓였다면 그때 한번에 모아서 색인처리를 한다. 즉, 버퍼가 일종의 큐역할을 하는 것이다. 버퍼에 모여 한번에 처리된 데이터는 즉시 세그먼트 형태로 생성되고 디스크로 동기화된다. 하지만 디스크에 물리적으로 동기화하는 일련의 과정은 운영체제 입장에서 비용이 큰 연산이기에 세그먼트가 생성될때마다 물리적인 동기화를 할 경우 성능이 급격히 나빠질 수 있다. 루씬은 이러한 문제점을 해결하기 위해 무거운 fsync 방식을 이용해 디스크 동기화를 하는 대신 상대적으로 가벼운 write 방식을 이용해 쓰기 과정을 수행한다. 이러한 방식으로 쓰기 성능을 높이고 이후 일정한 주기에 따라 물리적인 디스크 동기화 작업을 수행한다.

 

write() 함수

일반적으로 파일을 저장할 때 사용하는 함수다. 운영체제 내부 커널에는 시스템 캐시가 존재하는데 write() 함수를 이용하면 일단 시스템 캐시에만 기록되고 리턴된다. 이후 실제 데이터는 특정한 주기에 따라 물리적인 디스크로 기록된다. 물리적인 디스크 쓰기 작업을 수행하지 않기 때문에 빠른 처리가 가능한 반면 최악의 경우 시스템이 비정상 종료될 경우에는 데이터 유실이 일어날 수도 있다.

 

fsync() 함수

저수준의 파일 입출력 함수다. 내부 시스템 캐시의 데이터와 물리적인 디스크의 데이터를 동기화하기 위한 목적으로 사용된다. 실제 물리적인 디스크로 쓰는 작업을 수행하기 때문에 상대적으로 많은 리소스가 소모된다.

 

이러한 인메모리 버퍼 기반의 처리 과정을 루씬에서는 Flush라고 부른다. 데이터의 변경사항을 일단 버퍼에 모아두었다가 일정 주기에 한번씩 세그먼트를 생성하고 상대적으로 낮은 비용으로 디스크에 동기화 하는 작업까지 수행한다. 일단 Flush 처리에 의해 세그먼트가 생성되면 커널 시스템 캐시에 세그먼트가 캐시되어 읽기가 가능해진다. 커널 시스템 캐시에 캐시가 생성되면 루씬의 openIfChanged()을 이용해 IndexSearcher에서도 읽을 수 있는 상태가 된다.

 

openIfChanged() 함수

루씬에서는 IndexSearcher 객체가 생성되고 나면 이후 변경된 사항들을 기본적으로 인지하지 못한다. 기존 IndexSearcher를 Close하고 다시 생성하면 변경된 사항을 인지하는 것이 가능하지만 문서의 추가나 변경이 빈번하게 일어날 경우 많은 리소스가 필요해지기 때문에 권장하지 않는다. 이때 사용하는 것이 openIfChanged() 함수다. 일정 주기마다 문서가 업데이트된다면 openIfChanged()함수를 이용해 좀더 효율적으로 리소스를 사용할 수 있다.

 

하지만 최악의 경우에는 Flush만으로는 100% 데이터의 유실을 보장할 수 없다고 했다. 즉, fsync() 함수를 이용하여 언젠가는 반드시 동기화를 해야한다. 이러한 작업을 Commit이라고 한다. 매번 Commit하는 것이 아니고 Flush 작업을 몇번 한 이후에 일정 주기로 Commit작업을 통해 물리적인 디스크로 기록 작업을 수행해야한다.

 

아무리 루씬이 세그먼트 단위 검색을 지원하지만 시간이 지날수록 세그먼트 수가 많아지면 커밋 포인트의 부하도 증가하고 여러개의 세그먼트를 검색해야하기 때문에 검색 성능도 저하된다.그래서 일정주기 동안 여러개의 세그먼트는 하나의 세그먼트로 병합이 된다. 이러한 병합 처리에 여러 장점이 존재한다.

 

 

병합의 장점

  • 검색 성능 향상 : 검색 요청이 들어오면 루씬 내부에 존재하는 모든 세그먼트를 검색해야하는데, 각 세그먼트는 순차적으로 검색되므로 세그먼트를 병합하여 세그먼트 수를 줄이면 순차 검색 횟수도 줄어든다.
  • 디스크 용량 최소화 : 삭제되는 문서의 경우 병합 작업 전에는 삭제 플래그 값을 가지고 삭제되지 않고 물리적인 디스크에 남아있는다. 이러한 삭제 플래그를 가진 문서는 병합 작업을 시작해야 비로소 삭제된다.

 

이러한 병합 작업은 Commit 작업을 반드시 동반해야한다.

 

루씬 Flush 작업

  • 세그먼트가 생성된 후 검색이 가능해지도록 수행하는 작업
  • write() 함수로 동기화가 수행됬기 때문에 커널 시스템 캐시에만 데이터가 생성된다.이를 통해 유저 모드에서 파일을 열어 사용하는 것이 가능해진다.
  • 물리적으로 디스크에 쓰여진 상태는 아니다.

루씬 Commit 작업

  • 커널 시스템 캐시의 내용을 물리적인 디스크로 쓰는 작업
  • 실제 물리적인 디스크에 데이터가 기록되기 때문에 많은 리소스 필요

루씬 Merge 작업

  • 다수의 세그먼트를 하나로 통합하는 작업
  • Merge 과정을 통해 삭제 플래그 값을 가진 데이터가 실제 물리적으로 삭제 처리된다.
  • 검색할 세그먼트의 수가 줄어들기 때문에 검색 성능이 향상된다.

 

posted by 여성게
:
Search-Engine/Lucene 2019. 2. 2. 13:23

Lucene - 유사어,동의어필터(SynonymFilter)를 이용한 커스텀 Analyzer



Lucene에는 사용자가 입력한 질의 혹은 색인 할때의 토큰화 과정에서 여러가지 필터를 등록할 수 있다. 토큰의 종류는 아주 많다. StopFiler(불용어처리,불용어처리 단어의 리스트가 필요),SynonymFiler 등 의 필터들이 존재한다. 그 말은 단순히 토큰화된 텀들을 그대로 사용하는 것이 아니라 전처리,후처리를 필터를 이용해서 처리하여 토큰화된 텀에게 여러가지 효과?를 적용할 수 있는 것이다. 여기서는 간단히 유사어필터를 이용한 Custom한 분석기를 만들어 볼 것이며, 유사어 필터의 특징을 간단히 설명할 것이다.









1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
public class SynonymAnalyzerTest {
 
    
 
    
 
    public void testJumps(String text) throws IOException {
 
        System.out.println("Analyzing \"" +text+ "\"");
 
        System.out.println("\n");
 
        
 
        SynonymAnalyzer analyzer = new SynonymAnalyzer();
 
        
 
        String name = analyzer.getClass().getSimpleName();
 
        System.out.println("  "+name+"  ");
 
        System.out.print("    ");
 
        AnalyzerUtils.displayTokens(analyzer,text);
 
        
 
        System.out.println("\n");
 
        
 
    }
 
    
 
    
 
    public static void main(String[] args) throws IOException {
 
        // TODO Auto-generated method stub
 
        SynonymAnalyzerTest t = new SynonymAnalyzerTest();
 
        t.testJumps("나는 jumps 할거야");
 
    }
 
}
 
class SynonymAnalyzer extends Analyzer{
 
    
 
    @Override
 
    protected TokenStreamComponents createComponents(String fieldName) {
 
        // TODO Auto-generated method stub
 
        
 
        SynonymMap.Builder builder = new SynonymMap.Builder(true);
 
        builder.add(new CharsRef("JUPMS"), new CharsRef("점프,뛰다"), true);
 
        
 
        SynonymMap map = null;
 
        
 
        try {
 
            map=builder.build();
 
        }catch (Exception e) {
 
            // TODO: handle exception
 
            e.printStackTrace();
 
        }
 
        
 
        Tokenizer tokenizer = new StandardTokenizer();
 
        TokenStream filter = new LowerCaseFilter(tokenizer);
 
        filter = new SynonymFilter(filter,map,true);
 
        return new TokenStreamComponents(tokenizer,filter);
 
    }
 
    
 
}
 
class AnalyzerUtils{
 
    public static void displayTokens(Analyzer analyzer,String text) throws IOException {
 
        displayTokens(analyzer.tokenStream("content"new StringReader(text)));
 
    }
 
    
 
    public static void displayTokens(TokenStream stream) throws IOException {
 
        
 
        //텀 속성확인
 
        CharTermAttribute cattr = stream.addAttribute(CharTermAttribute.class);
 
        
 
        //위치증가값 속성 확인
 
        PositionIncrementAttribute postAtrr = stream.addAttribute(PositionIncrementAttribute.class);
 
        //오프셋위치확인
 
        OffsetAttribute offsetAttr = stream.addAttribute(OffsetAttribute.class);
 
        //텀타입 속성 확인
 
        TypeAttribute typeAttr = stream.addAttribute(TypeAttribute.class);
 
        
 
        //stream.incrementToken을 위해 필요
 
        stream.reset();
 
        
 
        int position = 0;
 
        
 
        while (stream.incrementToken()) {
 
            int increment = postAtrr.getPositionIncrement();
 
            
 
            position = position + increment;
 
            System.out.println();
 
            System.out.print(position + ": ");
 
            System.out.print("[ "+cattr.toString()+" : " + offsetAttr.startOffset()+"->"+offsetAttr.endOffset()+"                         : "+typeAttr.type()+" ]");
 
        }
 
 
 
        stream.end();
 
        stream.close();
 
        
 
    }
 
}
cs



-> 이 소스를 간단히 설명하면 커스텀한 분석기를 만들고 그 분석기를 이용해 분석된 사용자 입력 문장을 결과로 뿌려주는 역할을 하는 소스이다.

     1)사용자 정의 분석기

- 지금 작성한 커스텀 분석기는 StandardTokenizer에 SynonymFiler를 붙인 것이다. 여기서 빌더패턴을 이용하여 SynonymMap이란 객체를 다루고 있는데, 이것은 유사어 필터에게 유사어 목록이 담긴 맵을 전달해주기 위한 과정이다. 그리고 사용할 Tokenizer 클래스를 생성하고 사용할 필터의 input으로 토크나이저 객체를 전달해준다. 그리고 유사어 목록이 담긴 맵을 전달해주고, 원 단어를 저장할 것인가 안할 것인가를 지정하는 boolean타입의 매개변수까지 전달을 해준다. 그리고 마지막으로 TokenStreamComponents를 리턴해준다. 여기서 하나빼먹은 설명은 filter는 여러개가 될 수 있다는 점이다. 그래서 유사어 필터전 모든 텀을 소문자로 바꿔주는 LowerCaseFilter를 적용했다. 그런데 조금 설명이 필요한 점이라면 컴포지트 패턴을 이용하여 필터를 이어붙이고 있다는 점이다


   2)분석기 적용 결과

- 분석기의 tokenStream 메소드를 호출하면 최종적인 처리가된 TokenStream객체를 리턴해준다. 이 TokenStream 객체를 이용하여 분석된 결과를 출력할 수 있다.(최종적으로 색인에 들어가는 데이터는 TokenStream에 텀과 여러가지 메타데이터가 담기는 데이터이다.) 나머지 소스는 주석으로 충분히 예측가능할 것이다.






마지막 결과를 확인하면 JUMPS라는 단어가 소문자로 되어 유사어 필터가 적용되는 것을 볼 수 있다. 하지만 조금 특이한 점이 있다.


<결과>

Analyzing "나는 JUMPS 할거야"



  SynonymAnalyzer  

    

1: [ 나는 : 0->2 : <HANGUL> ]

2: [ jumps : 3->8 : <ALPHANUM> ]

2: [ 점프,뛰다 : 3->8 : SYNONYM ]

3: [ 할거야 : 9->12 : <HANGUL> ]


원단어와 유사어 처리된 단어가 위치 값이 같은 것이다. 즉, 색인에는 원단어는 물론 유사어까지 같은 포지션을 갖고 색인된다는 것이다. 이 말은 색인과정에서 유사어 필터를 등록한다면 검색에서는 유사어가 포함이 되어 있는 구문으로 구문검색을 해도 색인했던 원문 Document가 검색될 수 있다는 점이다. 아주 좋은 기능일 것 같다. 하지만 유사어 필터는 결코 가벼운 작업이 아니기에 꼭 색인 혹은 검색 둘중하나의 과정에만 적용시키면 된다. 보통 색인과 검색에 둘다 유사어 필터가 담긴 분석기를 사용하기도 하는데, 나중에 아주 데이터가 커지고 애플리케이션이 커지면 영향을 미칠 수도 있을 것같다.


posted by 여성게
:
Search-Engine/Lucene 2019. 1. 29. 23:04

Lucene - 분석기(Analyzer)로 분석한 토큰(Token)결과 출력




루씬에서 색인을 하기위해서는 선행과정이 있다. 물론 문서안에 정의된 여러개의 필드에 적용한 속성에 따라 다르긴 하지만 ANALYZE속성을 적용한 필드인 경우에는 색인하기 이전에 텍스트를 토큰으로 추출하고 그 토큰에 여러가지 메타정보(start,end 정수/위치증가값 등등의 데이터)를 섞은 텀으로 만든 후에 색인에 들어간다. 여기에서 보여줄 예제는 색인을 위한 텍스트에 분석기의 분석과정을 적용 후에 어떻게 토큰이 분리되는지 확인하는 간단한 예제이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
package com.lucene.study;
 
 
 
import org.apache.lucene.analysis.core.SimpleAnalyzer;
 
import org.apache.lucene.analysis.core.StopAnalyzer;
 
import org.apache.lucene.analysis.core.WhitespaceAnalyzer;
 
import org.apache.lucene.analysis.standard.StandardAnalyzer;
 
import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
 
import org.apache.lucene.analysis.tokenattributes.CharTermAttributeImpl;
 
import org.apache.lucene.analysis.tokenattributes.OffsetAttribute;
 
import org.apache.lucene.analysis.tokenattributes.PositionIncrementAttribute;
 
import org.apache.lucene.analysis.tokenattributes.TypeAttribute;
 
import org.apache.lucene.util.AttributeImpl;
 
 
 
import java.io.IOException;
 
import java.io.StringReader;
 
 
 
 
 
import org.apache.lucene.analysis.*;
 
 
 
public class AnalyzerTest {
 
    
 
    
 
    private static final String[] examples = {"The quick brown fox jumped over the lazy dog","XY&Z Corporation - zyz@example.com","안녕하세요? 피자 주문하려고 하는데요."};
 
    
 
    private static final Analyzer[] analyzers = new Analyzer[] {new WhitespaceAnalyzer(),new SimpleAnalyzer(),new StopAnalyzer(),new StandardAnalyzer()};
 
    
 
    
 
    public static void analyze(String text) throws IOException {
 
        System.out.println("Analyzing \"" +text+ "\"");
 
        System.out.println("\n");
 
        for(Analyzer analyzer : analyzers) {
 
            
 
            String name = analyzer.getClass().getSimpleName();
 
            System.out.println("  "+name+"  ");
 
            System.out.print("    ");
 
            AnalyzerUtils.displayTokens(analyzer,text);
 
            
 
            System.out.println("\n");
 
        }
 
    }
 
    public static void main(String[] args) throws IOException {
 
        // TODO Auto-generated method stub
 
        String[] strings = examples;
 
        
 
        for(String text:strings) {
 
            analyze(text);
 
        }
 
    }
 
 
 
}
 
 
 
class AnalyzerUtils{
 
    public static void displayTokens(Analyzer analyzer,String text) throws IOException {
 
        displayTokens(analyzer.tokenStream("content"new StringReader(text)));
 
    }
 
    
 
    public static void displayTokens(TokenStream stream) throws IOException {
 
        
 
        //텀 속성확인
 
        CharTermAttribute cattr = stream.addAttribute(CharTermAttribute.class);
 
        
 
        //위치증가값 속성 확인
 
        PositionIncrementAttribute postAtrr = stream.addAttribute(PositionIncrementAttribute.class);
 
        //오프셋위치확인
 
        OffsetAttribute offsetAttr = stream.addAttribute(OffsetAttribute.class);
 
        //텀타입 속성 확인
 
        TypeAttribute typeAttr = stream.addAttribute(TypeAttribute.class);
 
        
 
        
 
        stream.reset();
 
        
 
        int position = 0;
 
        
 
        while (stream.incrementToken()) {
 
            int increment = postAtrr.getPositionIncrement();
 
            
 
            position = position + increment;
 
            System.out.println();
 
            System.out.print(position + ": ");
 
            System.out.print("[ "+cattr.toString()+" : " + offsetAttr.startOffset()+"->"+offsetAttr.endOffset()+" : "+typeAttr.type()+" ]");
 
        }
 
 
 
        stream.end();
 
        stream.close();
 
        
 
    }
 
}
cs



posted by 여성게
:
Search-Engine/Lucene 2019. 1. 3. 21:07

Lucene library를 이용한 간단한 색인/검색(루씬 라이브러리이용)



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
package com.lucene.study;
 
import java.io.File;
import java.io.FileFilter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
 
import org.apache.lucene.analysis.standard.StandardAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
 
//yeoseong_yoon,2019/1/2->txt파일을 색인하는 유틸클래스
public class Indexer {
    
    private IndexWriter writer;
    
    //루씬의 IndexWriter 생성
    public Indexer(String indexDir) throws IOException {
        Directory dir = FSDirectory.open(new File(indexDir).toPath());
        
        writer = new IndexWriter(dir,new IndexWriterConfig(new StandardAnalyzer()));
    }
    
    public void close() throws IOException {
        writer.close();
    }
    
    public int index(String dataDir,FileFilter filter) throws IOException {
        
        File[] files = new File(dataDir).listFiles();
        
        for(File f : files) {
            if(!f.isDirectory() &&
                    !f.isHidden() &&
                    f.exists() &&
                    f.canRead() &&
                    (filter == null || filter.accept(f))) {
                
                indexFile(f);
            }
        }
        
        //색인된 문서건수 리턴
        return writer.numDocs();
    }
    
    private void indexFile(File file) throws IOException {
        System.out.println("Indexing "+file.getCanonicalPath());
        Document doc = getDocument(file);
        writer.addDocument(doc);
    }
    
    @SuppressWarnings("deprecation")
    protected Document getDocument(File file) throws IOException {
        Document doc = new Document();
        //파일의 내용추가
        doc.add(new Field("content"new FileReader(file)));
        //파일 이름추가
        doc.add(new Field("filename",file.getName(),Field.Store.YES,Field.Index.NOT_ANALYZED));
        //파일 전체경로 추가
        doc.add(new Field("fullpath",file.getCanonicalPath(),Field.Store.YES,Field.Index.NOT_ANALYZED));
        
        return doc;
    }
    
    //FileFilter를 이용하여 해당 확장자만 걸러낸다.
    private static class TextFilesFilter implements FileFilter{
        public boolean accept(File path) {
            return path.getName().toLowerCase().endsWith(".txt");
        }
    }
    public static void main(String[] args) throws IOException {
        // TODO Auto-generated method stub
        if(args.length!=2) {
            throw new IllegalArgumentException("please input index dir & data dir");
        }
        
        String indexDir = args[0];
        String dataDir = args[1];
        
        long start = System.currentTimeMillis();
        Indexer indexer = new Indexer(indexDir);
        int numIndexed = 0;
        
        try {
            numIndexed = indexer.index(dataDir, new TextFilesFilter());
        }catch (Exception e) {
            // TODO: handle exception
            System.out.println(e.getMessage());
        }finally {
            indexer.close();
        }
        
        long end = System.currentTimeMillis();
        
        System.out.println("Indexing " + numIndexed + " files took " + (end-start) + " milliseconds");
    }
 
}
 
cs


=>특정 디렉토리에 들어있는 txt파일을 특정 디렉토리 위치에 색인하는 코드이다.(실행시 arguments로 2개의 인자를 전달해야한다.)




1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
package com.lucene.study;
 
import java.io.File;
import java.io.IOException;
 
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.queryparser.classic.ParseException;
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;
 
public class Searcher {
    
    public static void search(String indexDir, String query) throws IOException, ParseException {
        
        Directory dir = FSDirectory.open(new File(indexDir).toPath());
        
        //색인을 연다.
        IndexSearcher indexSearcher = new IndexSearcher(DirectoryReader.open(dir));
        
        //질의를 분석한다.
        QueryParser parser = new QueryParser("content",
                                            new StandardAnalyzer());
        
        Query q = parser.parse(query);
        
        long start = System.currentTimeMillis();
        
        TopDocs hits = indexSearcher.search(q, 10);
        
        long end = System.currentTimeMillis();
        
        System.out.println("Found "+ hits.totalHits +" document(s) (in "+(end-start) 
                +" millisecond) that matched query '" + q + "' :");
        
        for(ScoreDoc scoreDoc : hits.scoreDocs) {
            Document doc = indexSearcher.doc(scoreDoc.doc);
            System.out.println(String.format("content - %s\nfullpath = %s"
                                            ,doc.get("content"
                                            ,doc.get("fullpath")));
        }
        
    }
 
    public static void main(String[] args) throws IOException, ParseException {
        // TODO Auto-generated method stub
        if(args.length !=2) {
            throw new IllegalArgumentException("please input index dir & search query String");
        }
        
        String indexDir = args[0];
        String query = args[1];
        search(indexDir, query);
    }
 
}
 
cs

=>위에서 색인한 색인 목록에서 검색어에 해당하는 문서를 찾는 코드이다. 이도 동일하게 2개의 아규먼트를 전달해야한다.




2개의 간단한 색인/검색을 예제로 짜보았다. 이 코드를 짜기전에 루씬 라이브러리를 dependency 해야하는 선과정이 있다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!-- START - Lucene Dependencies -->
<dependency>
      <groupId>org.apache.lucene</groupId>
      <artifactId>lucene-core</artifactId>
      <version>5.3.0</version>
</dependency>
<dependency>
      <groupId>org.apache.lucene</groupId>
      <artifactId>lucene-analyzers-common</artifactId>
      <version>5.3.0</version>
</dependency>
<dependency>
      <groupId>org.apache.lucene</groupId>
      <artifactId>lucene-queryparser</artifactId>
      <version>5.3.0</version>
</dependency>
<!-- END - Lucene Dependencies -->
cs


posted by 여성게
: