Эта статья является частью нашего академического курса под названием Apache Lucene Fundamentals .
В этом курсе вы познакомитесь с Lucene. Вы поймете, почему такая библиотека важна, а затем узнаете, как работает поиск в Lucene. Кроме того, вы узнаете, как интегрировать Lucene Search в ваши собственные приложения, чтобы обеспечить надежные возможности поиска. Проверьте это здесь !
Содержание
1. Введение
В предыдущей главе мы изучили различные компоненты поисковой системы Lucene . Мы также создали небольшое поисковое приложение, используя процедуры индексации и поиска lucene. В этой главе мы поговорим о запросах Lucene.
2.Lucene Queries
У Lucene есть собственный синтаксис запросов для запросов к его индексам. Запрос разбит на термины и операторы. Термины бывают двух типов: 1.Одиночные условия и 2. Фразы . Один термин — это одно слово, такое как «тест» или «образец». Фраза — это группа слов, заключенная в двойные кавычки, например, «приветственный люцен» Несколько терминов могут быть объединены вместе с логическими операторами, чтобы сформировать более сложный запрос. С Lucene Java TermQuery является самым примитивным Query. Тогда есть BooleanQuery, PhraseQuery и многие другие подклассы Query на выбор.
Поля При выполнении поиска мы можем указать поле, в котором вы хотите искать. Любое существующее имя поля может быть использовано в качестве имени поля. Синтаксис: FieldName: VALUE . Есть несколько специальных типов полей, которые имеют собственный синтаксис для определения термина запроса. Например, DateTime: ModificationDate:> ‘2010-09-01 12:00:00 ′ Мы расскажем об операциях поиска в этих полях в следующем разделе.
3.Lucene Query API
Когда читаемый человеком запрос анализируется с помощью QueryParser
Lucene, он преобразуется в один конкретный подкласс класса Query. Нам нужно некоторое понимание базовых конкретных подклассов Query. Соответствующие подклассы, их назначение и некоторые примеры выражений для каждого из них перечислены в следующей таблице:
Реализация запроса | Цель | Примеры выражений |
---|---|---|
TermQuery |
Однократный запрос, который фактически является одним словом. | Рейнольдс |
PhraseQuery |
Совпадение нескольких терминов по порядку или в непосредственной близости друг к другу. | «Зажечь впереди» |
RangeQuery |
Сопоставляет документы с терминами между начальным и конечным терминами, включая или исключая конечные точки. | [От А до Я]
{От А до Я} |
WildcardQuery |
Легкий, подобный регулярному выражению синтаксис соответствия терминов. | J * v?
F ?? бар |
PrefixQuery |
Соответствует всем терминам, которые начинаются с указанной строки. | сыр* |
FuzzyQuery |
Алгоритм Левенштейна для сравнения близости. | дерево ~ |
BooleanQuery |
Объединяет другие экземпляры Query в сложные выражения, допускающие логику AND, OR и NOT. | Рейнольдс и «зажечь впереди»
сыр * -чизизис |
Все эти реализации Query
находятся в пакете org.apache.lucene.search
. BooleanQuery
— это особый случай, потому что это контейнер Query
который объединяет другие запросы (включая вложенные BooleanQuery
для сложных выражений).
Вот BooleanQuery
основанный на фрагменте запроса. Здесь мы можем увидеть, как созданный QueryParser запрос эквивалентен созданному API:
01
02
03
04
05
06
07
08
09
10
11
|
public class RulezTest extends TestCase { public void testJavaNotDotNet() throws Exception { BooleanQuery apiQuery = new BooleanQuery(); apiQuery.add( new TermQuery( new Term( "contents" , "java" )), true , false ); apiQuery.add( new TermQuery( new Term( "contents" , "net" )), true , false ); apiQuery.add( new TermQuery( new Term( "contents" , "dot" )), false , true ); Query qpQuery = QueryParser.parse( "java AND net NOT dot" , "contents" , new StandardAnalyzer()); // Query and subclasses behave as expected with .equals assertEquals(qpQuery, apiQuery); } } |
Некоторыми интересными особенностями классов Query
являются их методы toString
. Каждый подкласс Query
генерирует эквивалентный (хотя и не обязательно точный QueryParserexpression
) QueryParserexpression
. Существует два варианта: один является стандартным переопределенным методом Object.toString
, а другой принимает имя поля по умолчанию. В следующем тестовом примере показано, как работают эти два метода, и показано, как возвращается эквивалентное (но не точное) выражение.
1
2
3
4
5
|
public void testToString() throws Exception { Query query = QueryParser.parse( "java AND net NOT dot" , "contents" , new StandardAnalyzer()); assertEquals( "+java +net -dot" , query.toString( "contents" )); assertEquals( "+contents:java +contents:net -contents:dot" , query.toString()); } |
Обратите внимание, что проанализированным выражением было ‘java AND net NOT dot’, но выражение, возвращаемое из методов toString
использовало сокращенный синтаксис ‘+ java + net -dot’. Наш первый тестовый пример ( testJavaNotDotNet
) продемонстрировал, что сами базовые объекты запроса эквивалентны.
Метод no-arg toString
не делает предположений об именах полей для каждого термина и указывает их явно с использованием синтаксиса селектора поля. Использование этих методов toString
удобно для диагностики проблем QueryParser
.
4. Основной поиск
В большинстве случаев вам нужно искать отдельный термин или фразу, представляющую собой группу слов, заключенную в двойные кавычки («пример приложения»). В этих случаях мы будем искать содержимое с этими словами в индексных данных по умолчанию, которые содержат весь соответствующий текст содержимого.
В более сложных ситуациях нам может потребоваться некоторая фильтрация, основанная на типе или месте содержимого, которое мы ищем, или мы хотим искать в определенном поле. Здесь мы можем узнать, как создавать более сложные запросы, которые можно использовать для эффективного поиска контента в огромном хранилище.
4.1.Terms
Предположим, мы хотим выполнить поиск по ключевому слову «блог» в поле тега. Синтаксис будет,
1
|
tag : blog |
Теперь мы будем искать фразу «блог lucene» в поле тега. Для этого синтаксис будет,
1
|
tag : "lucene blog" |
Теперь давайте поищем «блог lucene» в поле тега и «технический блог» в теле,
1
|
tag : "lucene blog" AND body : "technical blog" |
Предположим, что мы хотим найти фразу «блог lucene» в поле тега и «технический блог» в теле или фразу «поиск блога» в поле тега,
1
|
(tag : "lucene blog" AND body : "technical blog" ) OR tag : "searching blog" |
Если мы хотим искать «блог», а не «lucene» в поле тега, синтаксис будет выглядеть примерно так:
1
|
tag : blog -tag : lucene |
4.2.WildcardQuery
Lucene поддерживает поиск по одному и нескольким символам в пределах одного термина (не в запросах фразы).
- Чтобы выполнить поиск по одному символу, используйте «?» символ.
- Чтобы выполнить поиск по нескольким символам, используйте символ «*».
Поиск подстановочного знака одного символа ищет термины, которые совпадают с заменой одного символа. Например, для поиска «текста» или «теста» мы можем использовать поиск: te? T
При поиске с использованием нескольких символов подстановки выполняется поиск 0 или более символов. Например, для поиска теста, тестов или тестера мы можем использовать поиск:
1
|
test * |
Мы также можем использовать поиск по шаблону в середине термина.
1
|
te*t |
Вот пример поиска по шаблону Lucene,
Предположим, у нас есть два файла в каталоге «files».
- Тест-foods.txt
Вот некоторые продукты, которые любит Дерон:
hamburger
french fries
steak
mushrooms
artichokes
- Образец-food.txt
Вот некоторые продукты, которые любит Николь:
apples
bananas
salad
mushrooms
cheese
Теперь посмотрим на класс LuceneWildcardQueryDemo
. Этот класс создает индекс с помощью createIndex()
на основе текстовых файлов, упомянутых выше, и после этого он пытается выполнить 8 подстановочных знаков поиска по этому индексу. Четыре поиска выполняются с использованием класса WildcardQuery
, а остальные четыре поиска выполняются с использованием класса QueryParser
.
Указанные выше 2 файла сначала индексируются с использованием createIndex()
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
public static void createIndex() throws CorruptIndexException, LockObtainFailedException, IOException { Analyzer analyzer = new StandardAnalyzer(); boolean recreateIndexIfExists = true ; IndexWriter indexWriter = new IndexWriter(INDEX_DIRECTORY, analyzer, recreateIndexIfExists); File dir = new File(FILES_TO_INDEX_DIRECTORY); File[] files = dir.listFiles(); for (File file : files) { Document document = new Document(); String path = file.getCanonicalPath(); document.add( new Field(FIELD_PATH, path, Field.Store.YES, Field.Index.UN_TOKENIZED)); Reader reader = new FileReader(file); document.add( new Field(FIELD_CONTENTS, reader)); indexWriter.addDocument(document); } indexWriter.optimize(); indexWriter.close(); } |
Для выполнения операций поиска с использованием анализатора запросов мы можем добавить метод с именем searchIndexWithQueryParser()
,
01
02
03
04
05
06
07
08
09
10
|
public static void searchIndexWithQueryParser(String whichField, String searchString) throws IOException,ParseException { System.out.println( "\\nSearching for '" + searchString + "' using QueryParser" ); Directory directory = FSDirectory.getDirectory(INDEX_DIRECTORY); IndexSearcher indexSearcher = new IndexSearcher(directory); QueryParser queryParser = new QueryParser(whichField, new StandardAnalyzer()); Query query = queryParser.parse(searchString); System.out.println( "Type of query: " + query.getClass().getSimpleName()); Hits hits = indexSearcher.search(query); displayHits(hits); } |
Запросы WildcardQuery
выполняются в searchIndexWithWildcardQuery()
с использованием следующего кода:
1
2
3
4
5
|
Directory directory = FSDirectory.getDirectory(INDEX_DIRECTORY); IndexSearcher indexSearcher = new IndexSearcher(directory); Term term = new Term(whichField, searchString); Query query = new WildcardQuery(term); Hits hits = indexSearcher.search(query); |
Запросы QueryParser
выполняются в searchIndexWithQueryParser()
через:
1
2
3
4
5
|
Directory directory = FSDirectory.getDirectory(INDEX_DIRECTORY); IndexSearcher indexSearcher = new IndexSearcher(directory); QueryParser queryParser = new QueryParser(whichField, new StandardAnalyzer()); Query query = queryParser.parse(searchString); Hits hits = indexSearcher.search(query); |
Класс LuceneWildcardQueryDemo
выполняет восемь подстановочных запросов, как мы видим из его метода main ():
01
02
03
04
05
06
07
08
09
10
11
12
|
searchIndexWithWildcardQuery(FIELD_CONTENTS, "t*t" ); searchIndexWithQueryParser(FIELD_CONTENTS, "t*t" ); searchIndexWithWildcardQuery(FIELD_CONTENTS, "sam*" ); searchIndexWithQueryParser(FIELD_CONTENTS, "sam*" ); searchIndexWithWildcardQuery(FIELD_CONTENTS, "te?t" ); searchIndexWithQueryParser(FIELD_CONTENTS, "te?t" ); searchIndexWithWildcardQuery(FIELD_CONTENTS, "*est" ); try { searchIndexWithQueryParser(FIELD_CONTENTS, "*est" ); } catch (ParseException pe) { pe.printStackTrace(); } |
Наконец, мы напечатаем количество совпадений для каждой поисковой операции, используя такой метод, как,
01
02
03
04
05
06
07
08
09
10
|
public static void displayHits(Hits hits) throws CorruptIndexException, IOException { System.out.println( "Number of hits: " + hits.length()); Iterator<Hit> it = hits.iterator(); while (it.hasNext()) { Hit hit = it.next(); Document document = hit.getDocument(); String path = document.get(FIELD_PATH); System.out.println( "Hit: " + path); } } |
Если мы запустим приведенный выше код, он покажет нам,
1
2
3
|
Searching for 't*t' using WildcardQuery Number of hits: 1 Hit: /home/debarshi/workspace/Test/filesToIndex/test-foods .txt |
1
2
3
4
|
Searching for 't*t' using QueryParser Type of query: WildcardQuery Number of hits: 1 Hit: /home/debarshi/workspace/Test/filesToIndex/test-foods .txt |
1
2
3
|
Searching for 'sam*' using WildcardQuery Number of hits: 1 Hit: /home/debarshi/workspace/Test/filesToIndex/sample-foods .txt |
1
2
3
4
|
Searching for 'sam*' using QueryParser Type of query: PrefixQuery Number of hits: 1 Hit: /home/debarshi/workspace/Test/filesToIndex/sample-foods .txt |
1
2
3
|
Searching for 'te?t' using WildcardQuery Number of hits: 1 Hit: /home/debarshi/workspace/Test/filesToIndex/test-foods .txt |
1
2
3
4
|
Searching for 'te?t' using QueryParser Type of query: WildcardQuery Number of hits: 1 Hit: /home/debarshi/workspace/Test/filesToIndex/test-foods .txt |
1
2
3
|
Searching for '*est' using WildcardQuery Number of hits: 1 Hit: /home/debarshi/workspace/Test/filesToIndex/test-foods .txt |
1
2
3
4
5
6
|
Searching for '*est' using QueryParser org.apache.lucene.queryParser.ParseException: Cannot parse '*est' : '*' or '?' not allowed as first character in WildcardQuery at org.apache.lucene.queryParser.QueryParser.parse(QueryParser.java:175) at LuceneWildcardQueryDemo.searchIndexWithQueryParser(LuceneWildcardQueryDemo.java:81) at LuceneWildcardQueryDemo.main(LuceneWildcardQueryDemo.java:46) |
- Первый запрос использует объект
WildcardQuery
с «t * t». Поскольку «t * t» соответствует «test» в индексе, этот запрос возвращает 1 попадание. - Второй запрос использует
QueryParser
для запроса «t * t». Методparse()
QueryParser возвращаетWildcardQuery
, а запрос возвращает 1 попадание, поскольку он в основном идентичен первому запросу. - Третий запрос использует объект
WildcardQuery
с «sam *». Этот шаблонный запрос получает одно попадание, поскольку «sam *» соответствует «sample». - Четвертый запрос использует
QueryParser
с «sam *». Однако обратите внимание, что методparse()
вPrefixQuery
возвращаетPrefixQuery
а неWildcardQuery
. Поскольку звездочка находится в конце «Сэм *». Этот PrefixQuery для «sam *» получает удар, поскольку «sam *» соответствует «sample». - Пятый запрос — это
WildcardQuery
который использует вопросительный знак в своем поисковом слове «te? T». Знак вопроса может соответствовать одному символу. Поскольку «te? T» соответствует «test», поиск возвращает 1 попадание. - Шестой запрос использует
QueryParser
с «te? T». Методparse()
QueryParser возвращаетWildcardQuery
, и он попадает при попадании, как и пятый запрос. - Седьмой запрос — это
WildcardQuery
для «* est». Он получает одно совпадение, так как «test» соответствует «* est». В целом, не рекомендуется выполнять запросы, в которых первый символ является подстановочным знаком. - Восьмой запрос — это запрос
QueryParser
для «* est». Обратите внимание, что объектQueryParser
даже не позволяет нам выполнять запрос, где первый символ — звездочка. Выдает исключение разбора.
4.3. Булевы операторы
Булевы операторы позволяют объединять термины с помощью логических операторов. Lucene поддерживает AND, «+», OR, NOT и «-» в качестве логических операторов (Примечание: логические операторы должны быть ALL CAPS).
Оператор OR является оператором соединения по умолчанию. Это означает, что если между двумя членами нет логического оператора, используется оператор OR. Оператор OR связывает два термина и находит соответствующий документ, если в документе существует какой-либо из этих терминов. Это эквивалентно объединению с использованием множеств. Символ || может использоваться вместо слова ИЛИ.
Для поиска документов, которые содержат «jakarta apache» или просто «jakarta», используйте запрос:
1
|
"jakarta apache" jakarta |
или же
1
|
"jakarta apache" OR jakarta |
И
Оператор AND сопоставляет документы, где оба термина существуют в любом месте текста одного документа. Это эквивалентно пересечению с использованием множеств. Символ && можно использовать вместо слова AND.
Для поиска документов, которые содержат «jakarta apache» и «Apache Lucene», используйте запрос:
1
|
"jakarta apache" AND "Apache Lucene" |
+
«+» Или требуемый оператор требует, чтобы термин после символа «+» существовал где-то в поле одного документа.
Для поиска документов, которые должны содержать «Джакарта» и могут содержать «Люцен», используйте запрос:
1
|
+jakarta lucene |
НЕ
Оператор NOT исключает документы, содержащие термин после NOT. Это эквивалентно разнице в использовании наборов. Символ ! может использоваться вместо слова НЕ.
Для поиска документов, которые содержат «jakarta apache», но не «Apache Lucene», используйте запрос:
1
|
"jakarta apache" NOT "Apache Lucene" |
Примечание. Оператор NOT нельзя использовать только с одним термином. Например, следующий поиск не даст результатов:
1
|
NOT "jakarta apache" |
«-»
Оператор «-» или запрещающий исключает документы, содержащие термин после символа «-».
Для поиска документов, которые содержат «jakarta apache», но не «Apache Lucene», используйте запрос:
1
|
"jakarta apache" - "Apache Lucene" |
4.3.Grouping
Lucene поддерживает использование скобок для группировки предложений для формирования подзапросов. Это может быть очень полезно, если вы хотите контролировать логическую логику для запроса.
Для поиска «Джакарта» или «Apache» и «веб-сайт» используйте запрос:
1
|
(jakarta OR apache) AND website |
Это устраняет любую путаницу и гарантирует, что веб-сайт должен существовать и могут существовать термины джакарта или апач.
Группировка полей
Lucene поддерживает использование скобок для группировки нескольких предложений в одно поле.
Чтобы найти заголовок, содержащий слово «возврат» и фразу «розовая пантера», используйте запрос:
1
|
title:(+ return + "pink panther" ) |
Экранирование специальных персонажей
Lucene поддерживает экранирование специальных символов, которые являются частью синтаксиса запроса. Текущий список специальных символов:
+ — && || ! () {} [] ^ ”~ *? : \
Чтобы убежать от этого символа, используйте «\» (обратный слеш) перед символом. Например, для поиска (1 + 1): 2 используйте запрос:
1
|
\\(1\\+1\\)\\:2 |
4.4.PhraseQuery
PhraseQuery
в Lucene соответствует документам, содержащим определенную последовательность терминов. PhraseQuery
использует информацию о положении термина, которая хранится в индексе.
Количество других слов, разрешенных между словами в фразе запроса, называется «отстой». Это можно установить, вызвав метод setSlop. Если ноль, то это точный поиск фразы. Для больших значений это работает как оператор WITHIN или NEAR.
На самом деле наклон является расстоянием редактирования, где единицы соответствуют перемещениям терминов в фразе запроса вне позиции. Например, для переключения порядка двух слов требуется два хода (первый шаг помещает слова друг на друга), поэтому, чтобы разрешить перестановку фраз, наклон должен быть не менее двух.
Более точные совпадения имеют более высокий рейтинг, чем небрежные совпадения, поэтому результаты поиска сортируются по точности. Отклонение по умолчанию равно нулю, что требует точных совпадений.
PhraseQuery
также поддерживает множественные фразы.
Фразовый запрос может быть объединен с другими терминами или запросами с помощью BooleanQuery
. Максимальное количество пунктов по умолчанию ограничено 1024.
В предыдущем примере запроса с подстановочными знаками lucene мы выполняли операции поиска на основе двух текстовых файлов. Теперь мы попытаемся найти подходящие фразы, используя PhraseQuery в lucene.
Для этого вместо метода searchIndexWithWildcardQuery()
мы введем новый метод searchIndexWithPhraseQuery()
который принимает две строки, представляющие слова в документе, и значение slop. Он создает PhraseQuery
, добавляя два объекта Term на основе поля «содержимое» и параметров string1 и string2. После этого он устанавливает значение PhraseQuery
объекта PhraseQuery
с помощью setSlop()
PhraseQuery. Поиск осуществляется путем передачи объекта PhraseQuery в метод PhraseQuery
IndexSearcher. Вот код,
01
02
03
04
05
06
07
08
09
10
11
12
13
|
public static void searchIndexWithPhraseQuery(String string1, String string2, int slop) throws IOException,ParseException { Directory directory = FSDirectory.getDirectory(INDEX_DIRECTORY); IndexSearcher indexSearcher = new IndexSearcher(directory); Term term1 = new Term(FIELD_CONTENTS, string1); Term term2 = new Term(FIELD_CONTENTS, string2); PhraseQuery phraseQuery = new PhraseQuery(); phraseQuery.add(term1); phraseQuery.add(term2); phraseQuery.setSlop(slop); displayQuery(phraseQuery); Hits hits = indexSearcher.search(phraseQuery); displayHits(hits); } |
И мы вызываем этот метод из main()
,
1
2
3
4
5
6
7
8
9
|
searchIndexWithPhraseQuery( "french" , "fries" , 0 ); searchIndexWithPhraseQuery( "hamburger" , "steak" , 0 ); searchIndexWithPhraseQuery( "hamburger" , "steak" , 1 ); searchIndexWithPhraseQuery( "hamburger" , "steak" , 2 ); searchIndexWithPhraseQuery( "hamburger" , "steak" , 3 ); searchIndexWithQueryParser( "french fries" ); // BooleanQuery searchIndexWithQueryParser( "\\" french fries\\ "" ); // PhaseQuery searchIndexWithQueryParser( "\\" hamburger steak\\ "~1" ); // PhaseQuery searchIndexWithQueryParser( "\\" hamburger steak\\ "~2" ); // PhaseQuery |
Первый запрос ищет слова «french» и «fries» с наклоном 0, что означает, что поиск по фразе заканчивается поиском «french fries», где «french» и «fries» находятся рядом друг с другом. Так как это существует в test-foods.txt, мы получаем 1 попадание.
Во втором запросе мы ищем «гамбургер» и «стейк» с уклоном 0. Поскольку «гамбургер» и «стейк» не существуют рядом друг с другом ни в одном документе, мы получаем 0 совпадений. Третий запрос также включает в себя поиск «гамбургер» и «стейк», но с уклоном 1. Эти слова не находятся в пределах 1 слова друг от друга, поэтому мы получаем 0 хитов.
Четвёртый запрос ищет слова «гамбургер» и «стейк» с уклоном 2. В файле test-foods.txt у нас есть слова «… стейк из гамбургера с картофелем фри…». Поскольку «гамбургер» и «стейк» находятся в двух словах друг от друга, мы получаем 1 удар. Запрос пятой фразы — это тот же поиск, но с отставанием 3. Поскольку «гамбургер» и «стейк» соединяют три слова друг с другом (это два слова друг от друга), мы получаем удар 1.
Следующие четыре запроса используют QueryParser
. Обратите внимание, что в первом из запросов QueryParser
мы получаем BooleanQuery
а не PhraseQuery
. Это потому, что мы передали метод parse()
QueryParser «картофель фри», а не «\» картофель фри \ ». Если мы хотим, чтобы QueryParser генерировал PhraseQuery, строку поиска необходимо заключить в двойные кавычки. Следующий запрос выполняет поиск «\» french fries \ »», и мы видим, что он генерирует PhraseQuery
(со значением по умолчанию 0) и получает 1 попадание в ответ на запрос.
Последние два QueryParser
демонстрируют установку значений QueryParser
. Мы можем видеть, что значения slop могут быть установлены после двойных кавычек строки поиска с тильдой (~), следующей за номером slop.
4.5.RangeQuery
Query
который соответствует документам в исключительном диапазоне условий. Это позволяет сопоставлять документы, значения полей которых находятся между нижней и верхней RangeQuery
указанными в RangeQuery
. Запросы диапазона могут включать или исключать верхнюю и нижнюю границы. Сортировка производится лексикографически (совокупность элементов, упорядоченных (отсортированных) в порядке словаря).
Теперь, если мы хотим реализовать RangeQuery
для операций поиска Lucene, мы должны добавить метод с именем like, searchIndexWithRangeQuery()
, который в основном является конструктором, который требует Term
указывающий начало диапазона, Term
указывающий конец диапазона и логическое значение, указывающее, включает ли поиск начальные и конечные значения («истина») или исключают начальные и конечные значения («ложь»). Код выглядит так:
01
02
03
04
05
06
07
08
09
10
11
|
public static void searchIndexWithRangeQuery(String whichField, String start, String end, boolean inclusive) throws IOException, ParseException { System.out.println( "\\nSearching for range '" + start + " to " + end + "' using RangeQuery" ); Directory directory = FSDirectory.getDirectory(INDEX_DIRECTORY); IndexSearcher indexSearcher = new IndexSearcher(directory); Term startTerm = new Term(whichField, start); Term endTerm = new Term(whichField, end); Query query = new RangeQuery(startTerm, endTerm, inclusive); Hits hits = indexSearcher.search(query); displayHits(hits); } |
Теперь мы будем вызывать вышеуказанный метод,
1
2
3
4
5
6
7
8
9
|
searchIndexWithRangeQuery(FIELD_LAST_MODIFIED, "2014-04-01-00-00-00" , "2014-04-01-23-59-59" , INCLUSIVE); searchIndexWithRangeQuery(FIELD_LAST_MODIFIED, "2014-04-02-00-00-00" , "2014-04-02-23-59-59" , INCLUSIVE); searchIndexWithRangeQuery(FIELD_LAST_MODIFIED, "2014-04-01-00-00-00" , "2014-04-01-21-21-02" , INCLUSIVE); searchIndexWithRangeQuery(FIELD_LAST_MODIFIED, "2014-04-01-00-00-00" , "2014-04-01-21-21-02" , EXCLUSIVE); // equivalent range searches using QueryParser searchIndexWithQueryParser(FIELD_LAST_MODIFIED, "[2014-04-01-00-00-00 TO 2014-04-01-23-59-59]" ); searchIndexWithQueryParser(FIELD_LAST_MODIFIED, "[2014-04-02-00-00-00 TO 2014-04-02-23-59-59]" ); searchIndexWithQueryParser(FIELD_LAST_MODIFIED, "[2014-04-01-00-00-00 TO 2014-04-01-21-21-02]" ); searchIndexWithQueryParser(FIELD_LAST_MODIFIED, "{2014-04-01-00-00-00 TO 2014-04-01-21-21-02}" ); |
Наконец, есть небольшое изменение в createIndex()
. Мы добавили некоторые операции с датой и временем и напечатали время последнего изменения файлов индексации.
В верхней части вывода консоли мы видим, что два файла индексируются и что время «последнего изменения» для этих файлов равно «2014-04-01-21-21-02» (для test-foods.txt ) и «2014-04-01-21-21-38» (для sample-foods.txt).
В первом запросе диапазона мы ищем все файлы, которые были последний раз изменены 1 апреля 2014 года. Это возвращает 2 совпадения, так как оба файла были последний раз изменены в эту дату. Во втором запросе диапазона мы ищем все файлы, которые были последний раз изменены 2 апреля 2014 года. Это возвращает 0 совпадений, так как оба документа были последний раз изменены 1 апреля 2014 года.
Далее ищем в диапазоне от 2014-04-01-00-00-00 до 2014-04-01-21-21-02 включительно. Так как test-foods.txt был последний раз изменен в 2014-04-01-21-21-02 и запрос диапазона включает это значение, мы получаем один поисковый запрос. После этого мы ищем в диапазоне от 2014-04-01-00-00-00 до 2014-04-01-21-21-02, исключительно. Так как test-foods.txt был последний раз изменен в 2014-04-01-21-21-02 и запрос диапазона не включает это значение (так как оно исключено), этот поиск возвращает 0 совпадений.
После этого наши следующие четыре поиска показывают эквивалентные поиски, выполненные с использованием объекта QueryParser
. Обратите внимание, что метод parse()
в RangeQuery
возвращает объект ConstantScoreRangeQuery
а не объект RangeQuery
для каждого из этих запросов, как мы можем видеть из вывода консоли для этих запросов.
4.6.Префиксный запрос
Query
, соответствующий документам, содержащим термины с указанным префиксом. PrefixQuery
QueryParser
для ввода, например, nam *.
Мы попытаемся выполнить поиск определенного термина с его префиксом, используя запрос префикса, используя два текстовых файла (test-foods.txt & sample-foods.txt).
Для этого мы добавим метод с именем searchIndexWithPrefixQuery()
который будет искать индекс (который будет создан createIndex()
) с использованием PrefixQuery
. Этот метод принимает два параметра: один — имя поля, а другой — строку поиска.
01
02
03
04
05
06
07
08
09
10
|
public static void searchIndexWithPrefixQuery(String whichField, String searchString) throws IOException, ParseException { System.out.println( "\\nSearching for '" + searchString + "' using PrefixQuery" ); Directory directory = FSDirectory.getDirectory(INDEX_DIRECTORY); IndexSearcher indexSearcher = new IndexSearcher(directory); Term term = new Term(whichField, searchString); Query query = new PrefixQuery(term); Hits hits = indexSearcher.search(query); displayHits(hits); } |
Далее мы будем вызывать этот метод из метода main()
программы —
1
2
|
searchIndexWithPrefixQuery(FIELD_CONTENTS, "test" ); searchIndexWithPrefixQuery(FIELD_CONTENTS, "tes*" ); |
В первом запросе строка запроса не содержит звездочку. Поэтому, если мы напечатаем тип запроса метода parse()
TermQuery
, он напечатает TermQuery
вместо PrefixQuery
.
В запросе secong звездочка указывает QueryParser
что это запрос с префиксом, поэтому он возвращает объект PrefixQuery
из метода parse()
. Это приводит к поиску префикса «tes» в проиндексированном содержимом. Это приводит к 1 попаданию, так как «тест» находится в индексе.
4.7.Нечетный запрос
Нечеткий запрос основан на алгоритме Дамерау-Левенштейна (оптимальное выравнивание строк). FuzzyQuery
сопоставляет термины «близко» к указанному базовому термину: мы указываем максимально допустимое расстояние редактирования, и любые термины в пределах этого расстояния редактирования от базового термина (а затем документы, содержащие эти термины) сопоставляются.
Синтаксис QueryParser
— это термин ~ или термин ~ N, где N — максимально допустимое количество правок (для более ранних выпусков N — запутанное число с плавающей точкой между 0,0 и 1,0, что переводит в эквивалентную максимальную дистанцию редактирования через хитрую формулу).
FuzzyQuery
подходит для сопоставления собственных имен: мы можем искать lucene ~ 1, и он будет соответствовать luccene (вставить c), lucee (удалить n), lukene (заменить c на k) и множеству других «близких» терминов. При максимальном расстоянии редактирования 2 мы можем иметь до 2 вставок, удалений или замен. Оценка для каждого совпадения основана на расстоянии редактирования этого термина; поэтому точное совпадение набрано больше всего; изменить расстояние 1, ниже; и т.п.
QueryParser
поддерживает нечеткие запросы, используя QueryParser
тильду для термина. Например, при поиске wuzza ~ будут найдены документы, содержащие «fuzzy» и «wuzzy». Расстояние редактирования влияет на оценку, так что меньшие расстояния редактирования дают больший балл.