본문 바로가기

Lucene

[lucene] Token과 Tokenizer

Token과 TokenStream

단어가 분석기를 거쳐서 토큰으로 변환되고, 토큰은 다시 텀으로 변환되어 색인에 저장된다.
하나의 토큰은 텍스트에서 얻어낸 하나의 단어를 의미하며, 시작위치, 끝위치, 토큰종류, 위치증가값등의 메타 정보를 갖는다.
(the quick brown fox 같은 경우 각 토큰은 이전 토큰에 대해 한 단어만큼 뒤에 있기 때문에 모두 각각 1씩의 위치 증가값을 갖는다.)

토큰이 텀의 형태로 색인에 전달되는데 토큰의 단어와 위치 증가값만 사용된다.

토큰의 위치증가값이 1보다 큰 경우에는 단어와 단어가 떨어져 있다는 것이고, 0이라면 토큰의 위치가
이전 토큰과 같다는 의미이다.

TokenStream에는 Tokenizer와 TokenFilter가 있다.
Tokenizer는 글자 단위로 토큰을 만들어내고, TokenFilter는 토큰 단위로 처리한다.

public class SimpleKeywordAnalyzer extends Analyzer {

    public TokenStream tokenStream(String fieldName,  Reader reader) {
        return new CharTokenizer(reader) {
            protected boolean isTokenChar(char c) {
                return true;
            }
        };
    }

}


public TokenStream tokenStream(String fieldName, Reader content) {
 return new KoreanStemFilter(new WhitespaceTokenizer(content));
}

TokenFilter는 TokenStream을 입력받아 TokenStream이 토큰을 뽑아내면 그 토큰에 새로운 토큰을 더하가너
삭제하고 변경할 수 있다.


보통 맨 처음 Tokenizer를 사용하고, 몇가지 TokenFilter를 추가하는 방법이 일반적이다.


토큰을 확인할때
TokenStream stream = analyzer.tokenStream("prd_name", new StringReader(text));
Token token = stream.next();

로 알아보는데..

analyzer가 위에 SimpleKeywordAnalyzer일 경우 한번 보면..

stream.next(); 에서 next() 메서드는

TokenStream 클래스에 있다.

(사실상 return new CharTokenizer.... 에 의해 CharTokenizer의 next() 메서드이지만
오버라이드 하지 않았으므로, TokenStream의 next()가 실행되는 것..)

  public Token next() throws IOException {
    Token result = next(new Token());

    if (result != null) {
      Payload p = result.getPayload();
      if (p != null) {
        result.setPayload((Payload) p.clone());
      }
    }

    return result;
  }

보면 next(Token token) 이런 메서드를 호출 하는데 이것은

TokenStream 클래스에서

  public Token next(Token result) throws IOException {
    return next();
  }

로 정의되어 있고 이것은 CharTokenizer 에서 오버라이드 하고 있다. (CharTokenizer도 TokenStream의 하위 클래스)

public final Token next(Token token) throws IOException {
    token.clear();
    int length = 0;
    int start = bufferIndex;
    char[] buffer = token.termBuffer();
    while (true) {

      if (bufferIndex >= dataLen) {
        offset += dataLen;
        dataLen = input.read(ioBuffer);
        if (dataLen == -1) {
          if (length > 0)
            break;
          else
            return null;
        }
        bufferIndex = 0;
      }

      final char c = ioBuffer[bufferIndex++];

      if (isTokenChar(c)) {               // if it's a token char

        if (length == 0)              // start of token
          start = offset + bufferIndex - 1;
        else if (length == buffer.length)
          buffer = token.resizeTermBuffer(1+length);

        buffer[length++] = normalize(c); // buffer it, normalized

        if (length == MAX_WORD_LEN)     // buffer overflow!
          break;

      } else if (length > 0)             // at non-Letter w/ chars
        break;                           // return 'em
    }

    token.termLength = length;
    token.startOffset = start;
    token.endOffset = start+length;
    return token;
  }

이 메서드에서 isTokenChar() 메서드는

public TokenStream tokenStream(String fieldName,  Reader reader) {
        return new CharTokenizer(reader) {
            protected boolean isTokenChar(char c) {
                return true;
            }
        };
    }

여기서 오버라이드 하고 있다.

이런식으로 하위에서 필요한 메서드를 오버라이드 해서 쓰는 방식으로 구현되어 있다..