1. Обзор
Анализаторы Lucene используются для анализа текста при индексировании и поиске документов.
Мы кратко упомянули об анализаторах в нашем вводном туториале .
В этом руководстве мы обсудим часто используемые анализаторы, как создать собственный анализатор и как назначить разные анализаторы для разных полей документа .
2. Зависимости Maven
Во-первых, нам нужно добавить эти зависимости в наш pom.xml
:
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-core</artifactId>
<version>7.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-queryparser</artifactId>
<version>7.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.lucene</groupId>
<artifactId>lucene-analyzers-common</artifactId>
<version>7.4.0</version>
</dependency>
Последнюю версию Lucene можно найти здесь .
3. Анализатор Lucene
Анализаторы Lucene разбивают текст на токены.
Анализаторы в основном состоят из токенизаторов и фильтров. Разные анализаторы состоят из разных комбинаций токенизаторов и фильтров.
Чтобы продемонстрировать разницу между часто используемыми анализаторами, мы будем использовать следующий метод:
public List<String> analyze(String text, Analyzer analyzer) throws IOException{
List<String> result = new ArrayList<String>();
TokenStream tokenStream = analyzer.tokenStream(FIELD_NAME, text);
CharTermAttribute attr = tokenStream.addAttribute(CharTermAttribute.class);
tokenStream.reset();
while(tokenStream.incrementToken()) {
result.add(attr.toString());
}
return result;
}
Этот метод преобразует заданный текст в список токенов с помощью заданного анализатора.
4. Общие анализаторы Lucene
Теперь давайте взглянем на некоторые часто используемые анализаторы Lucene.
4.1. Стандартный анализатор
Мы начнем с StandardAnalyzer
, который является наиболее часто используемым анализатором:
private static final String SAMPLE_TEXT
= "This is foreach.com Lucene Analyzers test";
@Test
public void whenUseStandardAnalyzer_thenAnalyzed() throws IOException {
List<String> result = analyze(SAMPLE_TEXT, new StandardAnalyzer());
assertThat(result,
contains("foreach.com", "lucene", "analyzers","test"));
}
Обратите внимание, что StandardAnalyzer
может распознавать URL-адреса и электронные письма.
Кроме того, он удаляет стоп-слова и переводит сгенерированные токены в нижний регистр.
4.2. StopAnalyzer
StopAnalyzer состоит из LetterTokenizer
, LowerCaseFilter
и StopFilter:
@Test
public void whenUseStopAnalyzer_thenAnalyzed() throws IOException {
List<String> result = analyze(SAMPLE_TEXT, new StopAnalyzer());
assertThat(result,
contains("foreach", "com", "lucene", "analyzers", "test"));
}
В этом примере LetterTokenizer
разбивает текст на небуквенные символы, а StopFilter
удаляет стоп-слова из списка токенов.
Однако, в отличие от StandardAnalyzer
, StopAnalyzer
не может распознавать URL-адреса.
4.3. SimpleAnalyzer
SimpleAnalyzer
состоит из LetterTokenizer
и LowerCaseFilter
:
@Test
public void whenUseSimpleAnalyzer_thenAnalyzed() throws IOException {
List<String> result = analyze(SAMPLE_TEXT, new SimpleAnalyzer());
assertThat(result,
contains("this", "is", "foreach", "com", "lucene", "analyzers", "test"));
}
Здесь SimpleAnalyzer
не удалил стоп-слова. Он также не распознает URL-адреса.
4.4. Анализатор пробелов
WhitespaceAnalyzer использует только WhitespaceTokenizer
, который разбивает текст по пробельным символам :
@Test
public void whenUseWhiteSpaceAnalyzer_thenAnalyzed() throws IOException {
List<String> result = analyze(SAMPLE_TEXT, new WhitespaceAnalyzer());
assertThat(result,
contains("This", "is", "foreach.com", "Lucene", "Analyzers", "test"));
}
4.5. Анализатор ключевых слов
KeywordAnalyzer токенизирует
входные данные в один токен:
@Test
public void whenUseKeywordAnalyzer_thenAnalyzed() throws IOException {
List<String> result = analyze(SAMPLE_TEXT, new KeywordAnalyzer());
assertThat(result, contains("This is foreach.com Lucene Analyzers test"));
}
KeywordAnalyzer полезен для таких полей, как идентификаторы и почтовые индексы .
4.6. Анализаторы языка
Существуют также специальные анализаторы для разных языков, такие как EnglishAnalyzer
, FrenchAnalyzer
и SpanishAnalyzer
:
@Test
public void whenUseEnglishAnalyzer_thenAnalyzed() throws IOException {
List<String> result = analyze(SAMPLE_TEXT, new EnglishAnalyzer());
assertThat(result, contains("foreach.com", "lucen", "analyz", "test"));
}
Здесь мы используем EnglishAnalyzer
, который состоит из StandardTokenizer
, StandardFilter
, EnglishPossessiveFilter
, LowerCaseFilter
, StopFilter
и PorterStemFilter
.
5. Пользовательский анализатор
Далее давайте посмотрим, как создать собственный анализатор. Мы создадим один и тот же пользовательский анализатор двумя разными способами.
В первом примере мы будем использовать построитель CustomAnalyzer
для создания нашего анализатора из предопределенных токенизаторов и фильтров :
@Test
public void whenUseCustomAnalyzerBuilder_thenAnalyzed() throws IOException {
Analyzer analyzer = CustomAnalyzer.builder()
.withTokenizer("standard")
.addTokenFilter("lowercase")
.addTokenFilter("stop")
.addTokenFilter("porterstem")
.addTokenFilter("capitalization")
.build();
List<String> result = analyze(SAMPLE_TEXT, analyzer);
assertThat(result, contains("ForEach.com", "Lucen", "Analyz", "Test"));
}
Наш анализатор очень похож на EnglishAnalyzer
, но вместо этого он пишет токены с большой буквы.
Во втором примере мы создадим тот же анализатор, расширив абстрактный класс Analyzer
и переопределив метод createComponents()
:
public class MyCustomAnalyzer extends Analyzer {
@Override
protected TokenStreamComponents createComponents(String fieldName) {
StandardTokenizer src = new StandardTokenizer();
TokenStream result = new StandardFilter(src);
result = new LowerCaseFilter(result);
result = new StopFilter(result, StandardAnalyzer.STOP_WORDS_SET);
result = new PorterStemFilter(result);
result = new CapitalizationFilter(result);
return new TokenStreamComponents(src, result);
}
}
Мы также можем создать собственный токенизатор или фильтр и при необходимости добавить его в наш собственный анализатор.
Теперь давайте посмотрим на наш пользовательский анализатор в действии — в этом примере мы будем использовать InMemoryLuceneIndex
:
@Test
public void givenTermQuery_whenUseCustomAnalyzer_thenCorrect() {
InMemoryLuceneIndex luceneIndex = new InMemoryLuceneIndex(
new RAMDirectory(), new MyCustomAnalyzer());
luceneIndex.indexDocument("introduction", "introduction to lucene");
luceneIndex.indexDocument("analyzers", "guide to lucene analyzers");
Query query = new TermQuery(new Term("body", "Introduct"));
List<Document> documents = luceneIndex.searchIndex(query);
assertEquals(1, documents.size());
}
6. PerFieldAnalyzerWrapper
Наконец, мы можем назначить разные анализаторы разным полям с помощью PerFieldAnalyzerWrapper
.
Во-первых, нам нужно определить нашу AnalyzerMap
для сопоставления каждого анализатора с определенным полем:
Map<String,Analyzer> analyzerMap = new HashMap<>();
analyzerMap.put("title", new MyCustomAnalyzer());
analyzerMap.put("body", new EnglishAnalyzer());
Мы сопоставили «заголовок» с нашим пользовательским анализатором, а «тело» — с EnglishAnalyzer.
Далее давайте создадим наш PerFieldAnalyzerWrapper
, предоставив AnalyzerMap и
Analyzer
по умолчанию :
PerFieldAnalyzerWrapper wrapper = new PerFieldAnalyzerWrapper(
new StandardAnalyzer(), analyzerMap);
Теперь давайте проверим это:
@Test
public void givenTermQuery_whenUsePerFieldAnalyzerWrapper_thenCorrect() {
InMemoryLuceneIndex luceneIndex = new InMemoryLuceneIndex(new RAMDirectory(), wrapper);
luceneIndex.indexDocument("introduction", "introduction to lucene");
luceneIndex.indexDocument("analyzers", "guide to lucene analyzers");
Query query = new TermQuery(new Term("body", "introduct"));
List<Document> documents = luceneIndex.searchIndex(query);
assertEquals(1, documents.size());
query = new TermQuery(new Term("title", "Introduct"));
documents = luceneIndex.searchIndex(query);
assertEquals(1, documents.size());
}
7. Заключение
Мы обсудили популярные анализаторы Lucene, как создать собственный анализатор и как использовать разные анализаторы для каждого поля.
Полный исходный код можно найти на GitHub .