본문 바로가기

Lucene

[lucene] Analyzer와 Filter (2.4.0)


예전에 작성해 놓은 Analyzer와 Filter 포스트는 1.4.X 버젼대의 소스입니다.
예전에는 Analyzer의 형식이

public TokenStream tokenStream(String fieldName, Reader content) {
  return new GSKoreanSynonymFilter(
      new GSKoreanStopWordFilter(
       new GSKoreanSeperatorWordFilter(
        new GSKoreanSeperatorNameWordFilter(
         new GSKoreanTokenizer(content){} //GSKoreanTokenizer
        ) // GSKoreanSeperatorNameWordFilter
       ) //GSKoreanSeperatorWordFilter
     ),
    engine);
 }

이런 식이었는데 새버젼 소스를 받아서 열어보니 많이 바뀌었습니다.
아직 기존의 Analyzer를 새 버젼에 맞춰서 변경시키지는 못 했는데, 일단 어느 부분이 바뀌었는지 간략하게나마
알아봤으면 좋겠습니다.

간단한 모양을 하고 있는 StopAnalyzer를 열어보았습니다.

  public TokenStream tokenStream(String fieldName, Reader reader) {
    return new StopFilter(new LowerCaseTokenizer(reader), stopWords);
  }

기존에 사용하던 방식의 tokenStream 메서드도 있습니다. 버젼이 올라갔다고 이걸 그냥 없애버리면 아무래도 버젼을 업그레이드 하기 어렵겠죠.. ^^

그리고 새로운 메서드와 내부클래스가 눈에 들어옵니다.

  private class SavedStreams {
    Tokenizer source;
    TokenStream result;
  };

  public TokenStream reusableTokenStream(String fieldName, Reader reader) throws IOException {
    SavedStreams streams = (SavedStreams) getPreviousTokenStream();
    if (streams == null) {
      streams = new SavedStreams();
      streams.source = new LowerCaseTokenizer(reader);
      streams.result = new StopFilter(streams.source, stopWords);
      setPreviousTokenStream(streams);
    } else
      streams.source.reset(reader);
    return streams.result;
  }


reusableTokenStream 이 메서드인데요, 그냥 한번 훑어보니 뭔가 tokenStream을 저장해놓고 그것을 재 사용하는 방식을 쓰는 것 같습니다.

일단은 최상위 추상 클래스인 Analyzer에 정의 되어 있는 getPreviousTokenStream(); 메서드를 호출하여
뭔가 예전의 TokenStream을 가져옵니다. 위 메서드는 아래와 같이 되어 있습니다.

 private ThreadLocal tokenStreams = new ThreadLocal();

  protected Object getPreviousTokenStream() {
    return tokenStreams.get();
  }

  protected void setPreviousTokenStream(Object obj) {
    tokenStreams.set(obj);
  }

ThreadLocal 이라는 녀석이 저의 짧은 지식으로는 수행되는 쓰레드별로 별도의 저장소를 만들어
쓰레드당 고유의 저장소에서 객첼르 저장하고 사용하는 방식을 제공하는 그런거로 알고 있는데 일단 이것은 좀 더 공부가 필요 할 듯 하다. ㅠㅠ

아무튼, 싱글턴 이라는 것을 기억해두고 계속 진행해 보면, 그렇게해서 저장되어 있는 (공유되고 있는) SavedStream이 없을 경우에는 새로운 SavedStream을 생성 시킵니다.
그리고 tokenizer를 SavedStreams.source에 set하고
SavedStreams.result 에 TokenStream (FilterClass들) 들을 set 한 후 1.4.X 버젼과 비슷한 방식으로 사용을 하게 됩니다. 그리고 마지막에는
setPreviousTokenStream(streams);
메서드를 통해 생성했던 SavedStreams 객체를 ThreadLocal에 넣어놓습니다.

그리고, 만약 streams != null 이면
streams.source.reset(reader); 를 통해서 contents를 reset하고
TokenStream을 리턴합니다.

쓰레드가 살아있는 동안 TokenStream을 새로 생성 할 필요없이..
다시 가져와 사용 할 수 있다.. 라는 정도로 이해하면 무리가 없을지 잘 모르겠습니다. ^^
파싱하려는 내용이 변경되어도 streams.source.reset(reader); 을 통해서 contents를 reset해주고 있기
때문에 문제는 없을 것 같구요.


그 다음에 살펴 볼 부분이 next(final Token token) 메서드입니다.
2.4.0으로 넘어오면서 final 이 붙어버렸습니다.

일단 쉬운 비교를 위해서 예전과 소스를 비교해보겠습니다.

예전:

public final Token next(Token result) throws IOException {
    // return the first non-stop word found
    int skippedPositions = 0;
    while((result = input.next(result)) != null) {
      if (!stopWords.contains(result.termBuffer(), 0, result.termLength)) {
        if (enablePositionIncrements) {
          result.setPositionIncrement(result.getPositionIncrement() + skippedPositions);
        }
        return result;
      }
      skippedPositions += result.getPositionIncrement();
    }
    // reached EOS -- return null
    return null;
  }


 

  public final Token next(final Token reusableToken) throws IOException {
    assert reusableToken != null;
    // return the first non-stop word found
    int skippedPositions = 0;
    for (Token nextToken = input.next(reusableToken); nextToken != null; nextToken = input.next(reusableToken)) {
      if (!stopWords.contains(nextToken.termBuffer(), 0, nextToken.termLength())) {
        if (enablePositionIncrements) {
          nextToken.setPositionIncrement(nextToken.getPositionIncrement() + skippedPositions);
        }
        return nextToken;
      }
      skippedPositions += nextToken.getPositionIncrement();
    }
    // reached EOS -- return null
    return null;
  }

bold 처리 된 부분이 바뀐 부분인데요
파라메터가 final로 변경되면서 그 값을 변경하지 못 하기 때문에
내부적으로 nextToken이라는 로컬 객체를 하나 더 생성하여 사용하고 있습니다. reusableToken은 그냥 다음 필터클래스나 토크나이저 클래스에 넘어갈 뿐입니다.

한가지 궁금한 것은.. reusableToken이라는 것이 어디에 사용되는가 하는 건데요...;;;

사실 각각의 필터에서 조작하고 작업하는 대상이 되는 Token은 nextToken일 것인데..
reusableToken은 무엇 때문에 final로 선언되어 파라메터로 넘어가고 있는건지 아직은 잘 모르겠습니다..

나중에는 Tokenizer에서 추출된 Term과 포지션 정보를 set해서 넘겨주는데 사용하고 로직을 보면 새로운 Token 객체의 생성없이 계속 처음에 생성된 그 Token을 재사용하면서 쓰고 있긴한데..

그외 다른 목적에 대해서
나중에 알게 되면 다시 정리하거나.. 혹시 알고 계신분이 있으시면
댓글로라도 답변을 좀 부탁드리고 싶네요. ^^