Inleiding tot Apache Lucene

1. Overzicht

Apache Lucene is een full-text zoekmachine die vanuit verschillende programmeertalen kan worden gebruikt.

In dit artikel proberen we de kernconcepten van de bibliotheek te begrijpen en een eenvoudige applicatie te maken.

2. Maven-instellingen

Laten we om te beginnen eerst de nodige afhankelijkheden toevoegen:

 org.apache.lucene lucene-core 7.1.0 

De laatste versie vind je hier.

Voor het ontleden van onze zoekopdrachten hebben we ook het volgende nodig:

 org.apache.lucene lucene-queryparser 7.1.0 

Kijk hier voor de laatste versie.

3. Kernconcepten

3.1. Indexeren

Simpel gezegd, Lucene gebruikt een 'omgekeerde indexering' van gegevens - in plaats van pagina's aan trefwoorden toe te wijzen, worden trefwoorden aan pagina's toegewezen net als een woordenlijst aan het einde van elk boek.

Dit zorgt voor snellere zoekreacties, omdat het door een index zoekt, in plaats van rechtstreeks door tekst te zoeken.

3.2. Documenten

Hier is een document een verzameling velden en aan elk veld is een waarde gekoppeld.

Indices bestaan ​​doorgaans uit een of meer documenten, en zoekresultaten zijn sets van best passende documenten.

Het is niet altijd een gewoon tekstdocument, het kan ook een databasetabel of een verzameling zijn.

3.3. Velden

Documenten kunnen veldgegevens bevatten, waarbij een veld doorgaans een sleutel is die een gegevenswaarde bevat:

titel: Goodness of Tea body: Discussing goodness of tea drink kruidenthee ...

Merk dat hier op titel en lichaam zijn velden en kunnen samen of afzonderlijk worden doorzocht.

3.4. Analyse

Een analyse is het omzetten van de gegeven tekst in kleinere en nauwkeurige eenheden om het zoeken gemakkelijk te maken.

De tekst doorloopt verschillende bewerkingen van het extraheren van trefwoorden, het verwijderen van veelgebruikte woorden en interpuncties, het veranderen van woorden in kleine letters, enz.

Hiervoor zijn er meerdere ingebouwde analysers:

  1. StandardAnalyzer - analyseert op basis van basisgrammatica, verwijdert stopwoorden zoals "a", "an" enz. Converteert ook in kleine letters
  2. SimpleAnalyzer - breekt de tekst op basis van geen lettertekens en converteert in kleine letters
  3. WhiteSpaceAnalyzer - breekt de tekst op basis van spaties

Er zijn meer analysers beschikbaar die we ook kunnen gebruiken en aanpassen.

3.5. Zoeken

Zodra een index is gemaakt, kunnen we die index doorzoeken met een Vraag en een IndexSearcher. Het zoekresultaat is doorgaans een resultatenset die de opgehaalde gegevens bevat.

Merk op dat een IndexWritter is verantwoordelijk voor het maken van de index en een IndexSearcher voor het doorzoeken van de index.

3.6. Query-syntaxis

Lucene biedt een zeer dynamische en gemakkelijk te schrijven querysyntaxis.

Om een ​​vrije tekst te zoeken, gebruiken we gewoon een tekst Draad als de vraag.

Om een ​​tekst in een bepaald veld te zoeken, gebruiken we:

fieldName: text bv: title: tea

Bereikzoekopdrachten:

tijdstempel: [1509909322,1572981321] 

We kunnen ook zoeken met jokertekens:

drinken

zou zoeken naar een enkel teken in plaats van het jokerteken "?"

d * k

zoekt naar woorden die beginnen met "d" en eindigen op "k", met meerdere tekens ertussen.

uni*

vindt woorden die beginnen met "uni".

We kunnen deze vragen ook combineren om meer complexe zoekopdrachten te maken. En neem een ​​logische operator op zoals AND, NOT, OR:

titel: "Thee bij het ontbijt" EN "koffie"

Meer over de syntaxis van zoekopdrachten hier.

4. Een eenvoudige applicatie

Laten we een eenvoudige applicatie maken en enkele documenten indexeren.

Eerst maken we een in-memory index en voegen we er enkele documenten aan toe:

... Directory memoryIndex = nieuwe RAMDirectory (); StandardAnalyzer analyzer = nieuwe StandardAnalyzer (); IndexWriterConfig indexWriterConfig = nieuwe IndexWriterConfig (analyzer); IndexWriter writter = nieuwe IndexWriter (memoryIndex, indexWriterConfig); Document document = nieuw document (); document.add (nieuw TextField ("titel", titel, Field.Store.YES)); document.add (new TextField ("body", body, Field.Store.YES)); writter.addDocument (document); writter.close (); 

Hier maken we een document mee TextField en voeg ze toe aan de index met de IndexWriter. Het derde argument in de TextField constructor geeft aan of de waarde van het veld ook moet worden opgeslagen of niet.

Analyzers worden gebruikt om de gegevens of tekst in stukjes te splitsen en vervolgens de stopwoorden eruit te filteren. Stopwoorden zijn woorden als 'a', 'ben', 'is' enz. Deze zijn volledig afhankelijk van de gegeven taal.

Laten we vervolgens een zoekopdracht maken en in de index naar het toegevoegde document zoeken:

openbare lijst searchIndex (String inField, String queryString) {Queryquery = nieuwe QueryParser (inField, analyzer) .parse (queryString); IndexReader indexReader = DirectoryReader.open (memoryIndex); IndexSearcher-zoeker = nieuwe IndexSearcher (indexReader); TopDocs topDocs = searcher.search (zoekopdracht, 10); Lijstdocumenten = nieuwe ArrayList (); voor (ScoreDoc scoreDoc: topDocs.scoreDocs) {documents.add (searcher.doc (scoreDoc.doc)); } retourdocumenten; }

In de zoeken() methode het tweede integer-argument geeft aan hoeveel topzoekresultaten het moet retourneren.

Laten we het nu testen:

@Test openbare leegte gegevenSearchQueryWhenFetchedDocumentThenCorrect () {InMemoryLuceneIndex inMemoryLuceneIndex = nieuwe InMemoryLuceneIndex (nieuwe RAMDirectory (), nieuwe StandardAnalyzer ()); inMemoryLuceneIndex.indexDocument ("Hallo wereld", "Een beetje hallo wereld"); List documents = inMemoryLuceneIndex.searchIndex ("body", "world"); assertEquals ("Hallo wereld", documents.get (0) .get ("title")); }

Hier voegen we een eenvoudig document toe aan de index, met twee velden ‘titel 'en‘ body', en proberen we hetzelfde te zoeken met een zoekopdracht.

6. Lucene-zoekopdrachten

Omdat we nu vertrouwd zijn met de basisprincipes van indexeren en zoeken, gaan we wat dieper graven.

In eerdere secties hebben we de basisquery-syntaxis gezien en hoe u deze kunt converteren naar een Vraag instantie met behulp van de QueryParser.

Lucene biedt ook verschillende concrete implementaties:

6.1. TermQuery

EEN Termijn is een basiseenheid voor zoeken, die de veldnaam en de te zoeken tekst bevat.

TermQuery is de eenvoudigste van alle zoekopdrachten die uit een enkele term bestaan:

@Test openbare ongeldig gegevenTermQueryWhenFetchedDocumentThenCorrect () {InMemoryLuceneIndex inMemoryLuceneIndex = nieuwe InMemoryLuceneIndex (nieuwe RAMDirectory (), nieuwe StandardAnalyzer ()); inMemoryLuceneIndex.indexDocument ("activiteit", "loopt in track"); inMemoryLuceneIndex.indexDocument ("activiteit", "Auto's rijden op de weg"); Term term = new Term ("body", "running"); Queryquery = nieuwe TermQuery (term); Lijst documenten = inMemoryLuceneIndex.searchIndex (query); assertEquals (2, documents.size ()); }

6.2. PrefixQuery

Om een ​​document te zoeken met een woord "begint met":

@Test openbare ongeldig gegevenPrefixQueryWhenFetchedDocumentThenCorrect () {InMemoryLuceneIndex inMemoryLuceneIndex = nieuwe InMemoryLuceneIndex (nieuwe RAMDirectory (), nieuwe StandardAnalyzer ()); inMemoryLuceneIndex.indexDocument ("artikel", "Lucene introductie"); inMemoryLuceneIndex.indexDocument ("artikel", "Inleiding tot Lucene"); Term term = new Term ("body", "intro"); Queryquery = nieuwe PrefixQuery (term); Lijst documenten = inMemoryLuceneIndex.searchIndex (query); assertEquals (2, documents.size ()); }

6.3. WildcardQuery

Zoals de naam doet vermoeden, kunnen we jokertekens "*" of "?" Gebruiken voor zoeken:

// ... Term term = new Term ("body", "intro *"); Queryquery = nieuwe WildcardQuery (term); // ...

6.4. PhraseQuery

Het wordt gebruikt om een ​​reeks teksten in een document te doorzoeken:

// ... inMemoryLuceneIndex.indexDocument ("quotes", "Een roos met een andere naam zou zo zoet ruiken."); Queryquery = nieuwe PhraseQuery (1, "body", nieuwe BytesRef ("geur"), nieuwe BytesRef ("sweet")); Lijst documenten = inMemoryLuceneIndex.searchIndex (query); // ...

Merk op dat het eerste argument van de PhraseQuery constructor wordt genoemd slop, dat is de afstand in het aantal woorden tussen de termen die moeten worden vergeleken.

6.5. FuzzyQuery

We kunnen dit gebruiken bij het zoeken naar iets soortgelijks, maar niet noodzakelijk identiek:

// ... inMemoryLuceneIndex.indexDocument ("artikel", "Halloween Festival"); inMemoryLuceneIndex.indexDocument ("decoratie", "Decoraties voor Halloween"); Term term = new Term ("body", "hallowen"); Queryquery = nieuwe FuzzyQuery (term); Lijst documenten = inMemoryLuceneIndex.searchIndex (query); // ...

We hebben geprobeerd te zoeken naar de tekst "Halloween", maar met verkeerd gespelde "hallowen".

6.6. BooleanQuery

Soms moeten we misschien complexe zoekopdrachten uitvoeren, waarbij we twee of meer verschillende soorten zoekopdrachten combineren:

// ... inMemoryLuceneIndex.indexDocument ("Destination", "Las Vegas singapore car"); inMemoryLuceneIndex.indexDocument ("Commutes in singapore", "Bus Car Bikes"); Term term1 = new Term ("body", "singapore"); Term term2 = nieuwe term ("body", "auto"); TermQuery query1 = nieuwe TermQuery (term1); TermQuery query2 = nieuwe TermQuery (term2); BooleanQuery booleanQuery = nieuwe BooleanQuery.Builder () .add (query1, BooleanClause.Occur.MUST) .add (query2, BooleanClause.Occur.MUST) .build (); // ...

7. Zoekresultaten sorteren

We kunnen de documenten met zoekresultaten ook sorteren op basis van bepaalde velden:

@Test openbare leegte gegevenSortFieldWhenSortedThenCorrect () {InMemoryLuceneIndex inMemoryLuceneIndex = nieuwe InMemoryLuceneIndex (nieuwe RAMDirectory (), nieuwe StandardAnalyzer ()); inMemoryLuceneIndex.indexDocument ("Ganges", "Rivier in India"); inMemoryLuceneIndex.indexDocument ("Mekong", "Deze rivier stroomt in Zuid-Azië"); inMemoryLuceneIndex.indexDocument ("Amazon", "Rain forest river"); inMemoryLuceneIndex.indexDocument ("Rijn", "Behoort tot Europa"); inMemoryLuceneIndex.indexDocument ("Nile", "Longest River"); Term term = new Term ("body", "river"); Queryquery = nieuwe WildcardQuery (term); SortField sortField = nieuw SortField ("title", SortField.Type.STRING_VAL, false); Sorteer sortByTitle = nieuwe sortering (sortField); Lijst documenten = inMemoryLuceneIndex.searchIndex (query, sortByTitle); assertEquals (4, documents.size ()); assertEquals ("Amazon", documents.get (0) .getField ("title"). stringValue ()); }

We hebben geprobeerd de opgehaalde documenten te sorteren op titelvelden, dit zijn de namen van de rivieren. Het booleaanse argument voor de SortField constructor is voor het omkeren van de sorteervolgorde.

8. Verwijder documenten uit de index

Laten we proberen om enkele documenten uit de index te verwijderen op basis van een gegeven Termijn:

// ... IndexWriterConfig indexWriterConfig = nieuwe IndexWriterConfig (analyzer); IndexWriter-schrijver = nieuwe IndexWriter (memoryIndex, indexWriterConfig); writer.deleteDocuments (term); // ...

We zullen dit testen:

@Test openbare leegte whenDocumentDeletedThenCorrect () {InMemoryLuceneIndex inMemoryLuceneIndex = nieuwe InMemoryLuceneIndex (nieuwe RAMDirectory (), nieuwe StandardAnalyzer ()); inMemoryLuceneIndex.indexDocument ("Ganges", "Rivier in India"); inMemoryLuceneIndex.indexDocument ("Mekong", "Deze rivier stroomt in Zuid-Azië"); Term term = nieuwe term ("title", "ganges"); inMemoryLuceneIndex.deleteDocument (term); Queryquery = nieuwe TermQuery (term); Lijst documenten = inMemoryLuceneIndex.searchIndex (query); assertEquals (0, documents.size ()); }

9. Conclusie

Dit artikel was een korte inleiding om aan de slag te gaan met Apache Lucene. Ook hebben we verschillende zoekopdrachten uitgevoerd en de opgehaalde documenten gesorteerd.

Zoals altijd is de code voor de voorbeelden te vinden op Github.