Статьи

Введение в Apache Lucene для полнотекстового поиска

В этом уроке я бы хотел немного рассказать об Apache Lucene . Lucene — это проект с открытым исходным кодом, который предоставляет технологии индексации и поиска на основе Java. Используя его API, легко реализовать полнотекстовый поиск . Я буду иметь дело с версией Java Lucene , но имейте в виду, что есть также порт .NET под названием Lucene.NET , а также несколько полезных подпроектов.

Недавно я прочитал отличное руководство по этому проекту, но никакого реального кода не было представлено. Поэтому я решил предоставить пример кода, который поможет вам начать работу с Lucene. Приложение, которое мы создадим, позволит вам индексировать ваши собственные файлы исходного кода и искать конкретные ключевые слова.

Прежде всего, давайте загрузим последнюю стабильную версию с одного из загрузочных зеркал Apache. Я буду использовать версию 3.0.1, поэтому я скачал пакет lucene-3.0.1.tar.gz (обратите внимание, что версии .tar.gz значительно меньше, чем соответствующие версии .zip). Распакуйте архив и найдите файл lucene-core-3.0.1.jar, который будет использоваться позже. Кроме того, убедитесь, что страница JavaDoc API Lucene открыта в вашем браузере (документы также включены в tarball для автономного использования). Затем настройте новый проект Eclipse, скажем, с именем «LuceneIntroProject» и убедитесь, что вышеупомянутый JAR включен в путь к классам проекта.

Прежде чем мы начнем выполнять поисковые запросы, нам нужно создать индекс, по которому будут выполняться запросы. Это будет сделано с помощью класса IndexWriter , который создает и поддерживает индекс. IndexWriter получает Document s в качестве входных данных, где документы являются единицей индексации и поиска. Каждый документ на самом деле представляет собой набор полей, и каждое поле имеет имя и текстовое значение. Для создания IndexWriter требуется анализатор . Этот класс является абстрактным, и конкретной реализацией, которую мы будем использовать, является SimpleAnalyzer .

Уже достаточно разговоров, давайте создадим класс с именем «SimpleFileIndexer» и убедимся, что основной метод включен. Вот исходный код для этого класса:

01
02
03
04
05
06
07
08
09
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
package com.javacodegeeks.lucene;
 
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
 
import org.apache.lucene.analysis.SimpleAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.store.FSDirectory;
 
public class SimpleFileIndexer {
     
    public static void main(String[] args) throws Exception {
         
        File indexDir = new File("C:/index/");
        File dataDir = new File("C:/programs/eclipse/workspace/");
        String suffix = "java";
         
        SimpleFileIndexer indexer = new SimpleFileIndexer();
         
        int numIndex = indexer.index(indexDir, dataDir, suffix);
         
        System.out.println("Total files indexed " + numIndex);
         
    }
     
    private int index(File indexDir, File dataDir, String suffix) throws Exception {
         
        IndexWriter indexWriter = new IndexWriter(
                FSDirectory.open(indexDir),
                new SimpleAnalyzer(),
                true,
                IndexWriter.MaxFieldLength.LIMITED);
        indexWriter.setUseCompoundFile(false);
         
        indexDirectory(indexWriter, dataDir, suffix);
         
        int numIndexed = indexWriter.maxDoc();
        indexWriter.optimize();
        indexWriter.close();
         
        return numIndexed;
         
    }
     
    private void indexDirectory(IndexWriter indexWriter, File dataDir,
           String suffix) throws IOException {
 
        File[] files = dataDir.listFiles();
        for (int i = 0; i < files.length; i++) {
            File f = files[i];
            if (f.isDirectory()) {
                indexDirectory(indexWriter, f, suffix);
            }
            else {
                indexFileWithIndexWriter(indexWriter, f, suffix);
            }
        }
 
    }
     
    private void indexFileWithIndexWriter(IndexWriter indexWriter, File f,
            String suffix) throws IOException {
 
        if (f.isHidden() || f.isDirectory() || !f.canRead() || !f.exists()) {
            return;
        }
        if (suffix!=null && !f.getName().endsWith(suffix)) {
            return;
        }
        System.out.println("Indexing file " + f.getCanonicalPath());
         
        Document doc = new Document();
        doc.add(new Field("contents", new FileReader(f)));       
        doc.add(new Field("filename", f.getCanonicalPath(),
           Field.Store.YES, Field.Index.ANALYZED));
         
        indexWriter.addDocument(doc);
 
    }
 
}

Давайте немного поговорим об этом классе. Мы указываем местоположение индекса, т.е. где данные индекса будут сохранены на диске («c: / index /»). Затем мы предоставляем каталог данных, то есть каталог, который будет рекурсивно сканироваться на наличие входных файлов. Для этого я выбрал всю свою рабочую область Eclipse («C: / Programs / Eclipse / Workspace /»). Поскольку мы хотим индексировать только файлы исходного кода Java, я также добавил поле суффикса. Очевидно, вы можете настроить эти значения в соответствии с вашими потребностями поиска. Метод «index» учитывает предыдущие параметры и использует новый экземпляр IndexWriter для индексации каталога. Метод indexDirectory использует простой алгоритм рекурсии для сканирования всех каталогов на наличие файлов с суффиксом .java. Для каждого файла, который соответствует критериям, новый документ создается в «indexFileWithIndexWriter», и соответствующие поля заполняются. Если вы запустите класс как приложение Java через Eclipse, входной каталог будет проиндексирован, а выходной каталог будет выглядеть так, как показано на следующем рисунке: Хорошо, мы закончили с индексацией, давайте перейдем к поисковой части уравнения. Для этого нужен класс IndexSearcher , который реализует основные методы поиска. Для каждого поиска необходим новый объект Query (SQL кто-нибудь?), Который можно получить из экземпляра QueryParser . Обратите внимание, что QueryParser должен быть создан с использованием того же типа Analyzer, с которым был создан индекс, в нашем случае с использованием SimpleAnalyzer. Версия также используется в качестве аргумента конструктора и является классом, который «используется определенными классами для соответствия совместимости версий между выпусками Lucene», согласно JavaDocs. Существование чего-то подобного меня смущает, но что угодно, давайте использовать подходящую версию для нашего приложения ( Lucene_30 ). Когда поиск выполняется IndexSearcher, в результате выполнения возвращается объект TopDocs . Этот класс просто представляет результаты поиска и позволяет нам получать объекты ScoreDoc . Используя ScoreDocs, мы находим Документы, которые соответствуют нашим критериям поиска, и из этих Документов мы получаем требуемую информацию. Давайте посмотрим все это в действии. Создайте класс с именем «SimpleSearcher» и убедитесь, что включен основной метод. Исходный код этого класса следующий:

01
02
03
04
05
06
07
08
09
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
package com.javacodegeeks.lucene;
 
import java.io.File;
 
import org.apache.lucene.analysis.SimpleAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.queryParser.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;
import org.apache.lucene.util.Version;
 
public class SimpleSearcher {
     
    public static void main(String[] args) throws Exception {
         
        File indexDir = new File("c:/index/");
        String query = "lucene";
        int hits = 100;
         
        SimpleSearcher searcher = new SimpleSearcher();
        searcher.searchIndex(indexDir, query, hits);
         
    }
     
    private void searchIndex(File indexDir, String queryStr, int maxHits)
            throws Exception {
         
        Directory directory = FSDirectory.open(indexDir);
 
        IndexSearcher searcher = new IndexSearcher(directory);
        QueryParser parser = new QueryParser(Version.LUCENE_30,
             "contents", new SimpleAnalyzer());
        Query query = parser.parse(queryStr);
         
        TopDocs topDocs = searcher.search(query, maxHits);
         
        ScoreDoc[] hits = topDocs.scoreDocs;
        for (int i = 0; i < hits.length; i++) {
            int docId = hits[i].doc;
            Document d = searcher.doc(docId);
            System.out.println(d.get("filename"));
        }
         
        System.out.println("Found " + hits.length);
         
    }
 
}

Мы предоставляем каталог индекса, строку поискового запроса и максимальное количество обращений, а затем вызываем метод «searchIndex». В этом методе мы создаем IndexSearcher, QueryParser и объект Query. Обратите внимание, что QueryParser использует имя поля, которое мы использовали для создания документов с помощью IndexWriter («содержимое»), и снова, что используется тот же тип анализатора (SimpleAnalyzer). Мы выполняем поиск и для каждого Документа, в котором найдено совпадение, извлекаем значение поля, содержащего имя файла («имя файла»), и печатаем его. Вот и все, давайте выполним фактический поиск. Запустите его как приложение Java, и вы увидите имена файлов, которые содержат указанную вами строку запроса.

Проект Eclipse для этого руководства, включая библиотеку зависимостей, можно скачать здесь .

ОБНОВЛЕНИЕ: Вы также можете проверить наш следующий пост, «Вы имели в виду» функцию Apache Lucene Spell-Checker .

Наслаждайтесь!

Статьи по Теме :