Cener int>e>gis  
Geometa.info - Das (Schweizer) Suchportal für Geo-Daten und -Dienste Informationen zu diesem DokumentZIPPDF (A4)

[Quelle:  http://www.fh-wedel.de/~si/seminare/ws02/Ausarbeitung/e.lucene/index.html].

Lucene - Eine einfache Suchmaschine für Freitextsuche


Ein Bericht aus dem Seminar "Java und Werkzeuge für das Web" der FH Wedel

Von Andreas Heidenreich.


Gesamtübersicht: Lucene


Einführung in Lucene

Übersicht: Einführung in Lucene


Was ist Lucene?

Jakarta Lucene is a high-performance, full-featured text search engine written entirely in Java. It is a technology suitable for nearly any application that requires full-text search, especially cross-platform.

Jakarta Lucene ist eine leistungsstarke, vollfunktionsfähige Text-Suchengine, geschrieben in Java. Es ist eine Technologie, die für fast jede mögliche Anwendung verwendbar ist, die Ganztextsuche erfordert, besonders Cross-Plattform.

Lucene ist allerdings keine fertige Suchmaschine sondern stellt Klassen und Funktionen zur Verfügung, um für beliebige Projekte eine eigene Suchmaschine zu bauen.

Die grundsätzliche Vorgehensweise von Lucene

Zunächst wird der gesamte zu durchsuchende Text (alle Dateien) indiziert.
Dann wird mit Hilfe des Indizes gesucht.

Eigenschaften von Lucene

Skalierbares, High-Performance Indexing
Mächtige, akurate und effiziente Suchalgorithmen
Einfache API erlaubt es Entwicklern,
Plattformübergreifende Lösung

Die Standard-Suchsyntax von Lucene

Übersicht: Die Standard-Suchsyntax von Lucene


Überblick

Lucene unterstützt standardmäßig reichhaltige Suchoptionen.
Allerdings besteht auch die Möglichkeit, eine eigene Suchsyntax zu entwickeln.

Ausdrücke

Eine Suchanfrage wird in Ausdrücke und Operatoren unterteilt.
Ein einzelner Ausdruck ist ein Wort wie z. B. "Hallo".
Eine Phrase ist eine Gruppe von Wörtern, von Anführungszeichen umschlossen, wie "Hallo Du".
Mehrere Ausdrücke können mit Hilfe boolescher Operatoren verbunden werden.

Felder

Lucene unterstützt Felddaten (z. B. Dokumenttitel und Inhalt). Bei einer Suchanfrage kann man entweder ein Feld spezifizieren oder man benutzt das Standardeld. Die Feldnamen sind implementationsspezifisch.

Um in einem bestimmten Feld zu suchen, kann man den Feldnamen, gefolgt von einem Doppelpunkt und dem Suchausdruck angeben. Ein Beispiel:
"title:index AND text:hallo".
Ist "text" das Standardfeld, kann man auch
"title:index AND hallo"
eingeben.

Beachte: Das Feld gilt nur für den direkt folgenden Ausdruck, bei
"title:Titel Nummer 1"
gilt das Feld " title" nur für das Wort " Titel".

Wildcards

Lucene erlaubt Wildcard-Suche für ein oder mehrere Zeichen.

Um einen Platzhalter für ein einzelnes Zeichen anzugeben, wird ein "?" benutzt.
te?t
würde nach allen Wörtern wie "test" oder "text" suchen.

Um ein Platzhalter für mehrere Zeichen anzugeben, wird ein "*" benutzt.
test*
würde nach allen Wörtern wie "test" oder "tester" suchen.

Beachte: Beide Wildcards können sowohl innerhalb, als auch am Ende der Ausdrucks stehen, nicht jedoch am Beginn.

Fuzzy-Suche (undeutliche Suche)

Lucene unterstützt undeutliche Suche (Levenshtein Distanz Algorithmus).

Um eine undeutliche Suche zu formulieren, benutzt man das Tilde-Symbol ("~") am Ende eines einzelnen Wortes.
lachen~
würde Wörter wie "lachen", "wachen" oder auch "Laschen" finden.

Beachte: Ausdrücke, die über die Fuzzy-Suche gefunden wurden, werden automatisch mit einem Verstärkungsfaktor von 0,2 gewichtet.

Distanzsuche

Lucene erlaubt das Suchen von Wörtern, die eine bestimmte maximale Distanz voneinander entfernt sind.

Um eine Distanzsuche zu formulieren, muss das Tilde-Symbol ("~"), gefolgt von der Anzahl der Wörter, die diese maximal voneinander entfernt sein dürfen, hinter dem Audruck angegeben werden.
"Guten wiedersehen"~10
würde nach den Wörtern "Guten" und "wiedersehen" suchen, die höchstens 10 Wörter voneinander entfernt liegen dürfen.

Verstärkungsfaktoren

Lucene erlaubt es, einen Ausdruck mit einem Verstärkungsfaktor zu versehen, was sich dann auf die Reihenfolge der Ergebnisse auswirkt.

Um einem Ausdruck ein Verstärkungsfaktor zuzuordnen, wird das "^"-Symbol, gefolgt von dem Verstärkungsfaktor hinter dem Ausdruck angegeben.
hallo^4 du
würde bedeuten, dass Ergebnisse, die das Wort "hallo" enthalten, viermal so stark gewichtet werden, wie solche, die das Wort "du" enthalten.

Auch Phrasen (Ausdrücke, die aus mehreren Wörtern bestehen) können mit einem Verstärkungsfaktor versehen werden.
Der Standardfaktor ist 1.
Verstärkungsfaktoren müssen immer positiv sein, sie können aber kleiner als 1 sein.

Boolesche Operatoren

Es können boolesche Operatoren, wie und, oder, usw. angegeben werden, um die Suche zu spezifizieren.

Sollen zwei Ausdrücke ODER-verknüpft werden, so ist dazwischen ein "OR" zu schreiben, oder aber gar nichts, da die ODER- die Standardverknüpfung ist.
hallo OR du
oder aber hallo du

Sollen zwei Ausdrücke UND-verknüpft werden, so ist dazwischen ein "AND", oder aber vor beide Ausdrücke ein "+"-Zeichen zu schreiben.
hallo AND du
oder aber
+hallo +du

Soll z. B. das Wort "hallo" unbedingt vorkommen, das Wort "du" aber nicht unbedingt, so ist
+hallo du
zu schreiben.

Es können auch negierende boolesche Operatoren angegeben werden.

Um anzugeben, dass ein Ausdruck nicht vorkommen soll, ist ein NOT, oder aber ein Minuszeichen ("-") vor dem Ausdruck zu schreiben.
hallo NOT du
bedeutet, dass "hallo", nicht aber "du" vorkommen soll.

Beachte: der NOT-Operator kann nicht nur mit einem Ausdruck benutzt werden, wie bei
NOT "hallo du"

Gruppierung von Elementen

Elemente können gruppiert werden.

Dazu werden Klammern mit den üblichen Regeln benutzt:
(hallo OR du) AND ich
sucht alle Dokumente, in denen "ich" vorkommt, sowie "hallo" oder "du".

Syntaxzeichen ausschließen

Auch Zeichen, die für die Suchsyntax reserviert werden, können in der Suche mit angegeben werden.

Ein Backslash ("\") vor einem solchen Zeichen bewirkt, dass es nicht fü die Syntax interpretiert, sondern in den Suchwausdruck einbezogen wird:
\(1\+1\)\:2
bedeutet, dass genau nach "(1+1):2" gesucht werden soll.

Index-Dateiformate von Lucene

Übersicht: Index-Dateiformate von Lucene


Überblick

Lucene ist komplett in Java geschrieben.
Es gibt Bemühungen, Versionen von Lucene auch in anderen Programmiersprachen zu schreiben.
Um das möglich zu machen, ist es notwendig, eine sprachunabhängige Definition des Lucene-Indexformats zu formulieren.

Definitioen

Das Grundkonzept in Lucene besteht aus Index, Dokument, Feld und Ausdruck.

Index-Diagramm

Ist der gleiche String in zwei veschiedenen Feldern, so sind das zwei Ausdrücke. Somit wird ein Ausdruck repräsentiert als ein String-Paar, Feldname und Feldinhalt.


Invertiertes Indizieren

Invertiertes Indizieren ("inverted index"):

Es gibt zwei Arten von Feldern:


Segmente

Indizes werden aus mehreren Sub-Indizes (Segmente) erstellt.

Indizes entstehen aus:

Bei einer Suche können mehrere Segmente und / oder mehrere Indizes verwendet werden, jeder Index kann aus mehreren Segmenten entstanden sein.


Dokumentnummern

Intern werden die zu indizierenden Dokumente durch eine Zahl, der Dokument-Nummer referenziert.

Dokumentnummern können sich ändern, daher ist es nicht ratsam, diese Nummern außerhalb von Lucene zu setzen:


Überblick über Segmente

Jedes Segment entält Folgendes:

Dateinamen

Alle Dateien (Dateien, in denen die Indexinformationen gespeichert werden) haben den gleichen Namen mit unterschiedlichen Erweiterungen.

Typischerweise werden alle diese Dateien im selben Verzeichnis gespeichert, obwohl das nicht zwingend erforderlich ist.

Primitive Datentypen

Folgende primitive Datentypen werden in diesen Dateien benutzt:

Die einzelnen Dateien

Segmentdatei

Diese Datei speichert die aktiven Segmente des Index, Dateiname: "segments", listet jedes Segment beim Namen und enthält dessen Größe.
Segmente --> SegAnzahl, <SegName, SegGröße>^SegAnzahl
SegAnzahl, SegGröße --> UInt32 (SegGröße ist die Anzahl der Dokumente des Segments)
SegName --> String (Name, wie der Datei-Prefix aller Dateien des Segment-Index)

Blockierdateien

Bestimmte Dateien werden benutzt um anzuzeigen, dass ein anderer Prozess auf den Index zugreift.

Löschdatei

Die Datei "deletable" gibt die Dateien an, die der Index nicht mehr benutzt, die aber nicht gelöscht werden konnten, da die Datei z. B. gerade geöffnet ist:
Löschdatei --> Anzahl, <Dateiname>^Anzahl.
Anzahl --> UInt32
Dateiname --> String

Dateien pro Segment

Von folgenden Dateien existiert eine pro Segment:


Beschränkungen

Es gibt eine Reihe von Stellen, in denen diese Dateiformate die maximale Anzahl der Ausdrücke und Dokumente auf eine 32-Bit große Zahl beschränken.

Es gibt nur zwei Stellen, an denen Werte eine feste Maximalgröße haben müssen:

An allen anderen Stellen können UInt32 ind VInt umgewandelt werden.


Lucene in der Praxis

Übersicht: Lucene in der Praxis


Download und Installation von Lucene

Um Lucene benutzen zu können, müssen zunächst die entsprechenden Dateien runtergeladen werden, entweder über diese Download-Seite, oder aber direkt über die offizielle Lucene-FTP-Seite. Nachdem die Dateien entpackt wurden, müssen zu der CLASSPATH-Variable die Dateien "lucene-1.2.jar" und, um die Demos ausprobieren zu können, "lucene-demos-1.2.jar" hinzugefügt werden.

Wir wollen uns nun eine kleine Demoanwendung angucken, um zu sehen, wie Lucene in der Praxis funktionieren kann. Die benötigten Java-Quellen können von der Download-Seite heruntergeladen werden.

Ein Beispielprogramm mit Lucene

Nachdem die Quellen (IndexFiles.java und SearchFiles.java) auf übliche Weise kompiliert wurden, wird nun der Index erstellt. Dazu wird IndexFiles ausgeführt und als Argument der Pfad übergeben, dessen Dateien indiziert werden sollen, z. B. "{Lucene-Pfad}/src", um die Lucene-Quelldateien durchsuchen zu können (java IndexFiles {Lucene-Pfad}/src).
Nun wird der Index erstellt. Danach wird SearchFiles ausgeführt (java SearchFiles) und man kann eine Suchanfrage eingeben.
Für diese Mini-Suchmaschine werden also lediglich die drei relativ kurzen Quellen "IndexFiles", "SearchFiles" und "FileDocument" benötigt.

Für das Programmieren eigener Anwendungen, die Lucene nutzen, empfiehlt sich immer wieder ein Blick in die javadoc-Dokumentationen der Lucene-Klassen.
Schauen wir uns zunächst einmal die Datei "IndexFiles.java" an, mit Hilfe derer der Index erstellt wird.
import org.apache.lucene.analysis.de.GermanAnalyzer;
import org.apache.lucene.index.IndexWriter;

import java.io.*;
import java.util.*;

public
class IndexFiles 
{
  public static
  void main(String[] args)
  {
    try
    {
      Date start = new Date();

      IndexWriter writer = new IndexWriter(Const.INDEX_DIR, new GermanAnalyzer(), true);
      indexDocs(writer, new File(args[0]));

      writer.optimize();
      writer.close();
    }
    catch (Exception e)
    {
      System.out.println(" caught a " + e.getClass() + "\n with message: " + e.getMessage());
    }
  }

  public static void indexDocs(IndexWriter writer, File file)
    throws Exception
  {
    if (file.isDirectory())
    {
      String[] files = file.list();
      for (int i = 0; i < files.length; i++)
      indexDocs(writer, new File(file, files[i]));
    } 
    else
    {
      System.out.println("hinzugefuegt: " + file);
      writer.addDocument(FileDocument.Document(file));
    }
  }
}
        

Zunächst wird eine Instanz von "IndexWriter" erstellt.

Dann wird "indexDocs(...)" aufgerufen:


Dann die Datei "FileDocument.java"
import java.io.*;
import org.apache.lucene.document.*;

public
class FileDocument
{
  public static
  Document Document(File f)
    throws java.io.FileNotFoundException
  {
    Document doc = new Document();
    doc.add(Field.Text(Const.PATH_FIELD, f.getPath()));
    doc.add(Field.Keyword(Const.MOD_FIELD,
              DateField.timeToString(f.lastModified())));

    FileInputStream is = new FileInputStream(f);
    Reader reader = new BufferedReader(new InputStreamReader(is));
    doc.add(Field.Text(Const.DEF_FIELD, reader));

    return doc;
  }
}
        

Schließlich die Datei "SearchFiles.java".
import java.io.*;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.de.GermanAnalyzer;
import org.apache.lucene.document.Document;
import org.apache.lucene.search.*;
import org.apache.lucene.queryParser.QueryParser;

public
class SearchFiles
{
  public static
  void main(String[] args)
  {
    try
    {
      Searcher searcher = new IndexSearcher(Const.INDEX_DIR);
      Analyzer analyzer = new GermanAnalyzer();

      BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
      // einlesen
      boolean lineEnd=false;
      while (!lineEnd)
      {
        System.out.print("Suchbegriff eingeben (<Enter>, um zu verlassen): ");
        String line = in.readLine();

        if (line.length() == -1)
          lineEnd=true;
        else
        {
          Query query = QueryParser.parse(line, Const.DEF_FIELD, analyzer);
          System.out.println("Suchbegriff: " + query.toString(Const.DEF_FIELD));

          Hits hits = searcher.search(query);
          System.out.println(hits.length() + " Dokumente gefunden");

          final int HITS_PER_PAGE = 10;
          int end = hits.length();
          for (int i=0; i<end; i++)
          {
            Document doc = hits.doc(i);
            String path = doc.get(Const.PATH_FIELD);
            System.out.println(i + ". " + path);
          }
        }
      }
      searcher.close();

    }
    catch (Exception e)
    {
      System.out.println(" caught a " + e.getClass() + "\n with message: " + e.getMessage());
    }
  }
}
        

Pläne für die Zukunft

In Zukunft soll das Lucene-Projekt in folgende Richtungen erweitert werden:


Links und Literaturverzeichnis

-*-