Introduktion til Apache Lucene

1. Oversigt

Apache Lucene er en søgemaskine i fuldtekst, der kan bruges fra forskellige programmeringssprog.

I denne artikel vil vi forsøge at forstå de centrale begreber i biblioteket og oprette en enkel applikation.

2. Maven-opsætning

For at komme i gang, lad os først tilføje nødvendige afhængigheder:

 org.apache.lucene lucene-core 7.1.0 

Den seneste version kan findes her.

For at analysere vores søgeforespørgsler har vi også brug for:

 org.apache.lucene lucene-queryparser 7.1.0 

Se efter den nyeste version her.

3. Kernebegreber

3.1. Indeksering

Enkelt sagt bruger Lucene en “omvendt indeksering” af data - i stedet for at kortlægge sider til nøgleord, kortlægger det nøgleord til sider ligesom en ordliste i slutningen af ​​enhver bog.

Dette giver mulighed for hurtigere søgesvar, da det søger gennem et indeks i stedet for at søge gennem tekst direkte.

3.2. Dokumenter

Her er et dokument en samling felter, og hvert felt har en værdi tilknyttet.

Indekser består typisk af et eller flere dokumenter, og søgeresultaterne er sæt best matchende dokumenter.

Det er ikke altid et almindeligt tekstdokument, det kan også være en databasetabel eller en samling.

3.3. Felter

Dokumenter kan have feltdata, hvor et felt typisk er en nøgle, der indeholder en dataværdi:

titel: Godhed af te krop: Diskuterer godhed ved at drikke urtete ...

Bemærk det her titel og legeme er felter og kunne søges efter sammen eller individuelt.

3.4. Analyse

En analyse konverterer den givne tekst til mindre og præcise enheder for let at søge.

Teksten gennemgår forskellige operationer med udpakning af nøgleord, fjernelse af almindelige ord og tegnsætning, ændring af ord til små bogstaver osv

Til dette formål er der flere indbyggede analysatorer:

  1. Standardanalysator - analyser baseret på grundlæggende grammatik, fjerner stopord som “a”, “an” osv. Konverteres også med små bogstaver
  2. SimpleAnalyzer - bryder teksten baseret på bogstaver uden bogstaver og konverterer med små bogstaver
  3. WhiteSpaceAnalyzer - bryder teksten baseret på hvide mellemrum

Der er flere analysatorer til rådighed for os at bruge og tilpasse også.

3.5. Søger

Når et indeks er bygget, kan vi søge i det indeks ved hjælp af en Forespørgsel og en IndexSearcher. Søgeresultatet er typisk et resultatsæt, der indeholder de hentede data.

Bemærk, at en IndexWritter er ansvarlig for at oprette indekset og et IndexSearcher til søgning i indekset.

3.6. Forespørgselssyntaks

Lucene giver en meget dynamisk og let at skrive forespørgselssyntaks.

For at søge i en fri tekst bruger vi bare en tekst Snor som forespørgslen.

For at søge i en tekst i et bestemt felt bruger vi:

feltnavn: tekst f.eks. titel: te

Rækkevidde søgninger:

tidsstempel: [1509909322,1572981321] 

Vi kan også søge ved hjælp af jokertegn:

drikke

ville søge efter et enkelt tegn i stedet for jokertegnet “?”

d * k

søger efter ord, der starter med “d” og slutter med “k”, med flere tegn imellem.

enhed *

finder ord, der starter med "uni".

Vi kan også kombinere disse forespørgsler og oprette mere komplekse forespørgsler. Og inkluder logiske operatører som AND, NOT, OR:

titel: "Te i morgenmaden" OG "kaffe"

Mere om forespørgselssyntaks her.

4. En simpel applikation

Lad os oprette en simpel applikation og indeksere nogle dokumenter.

Først opretter vi et indeks for hukommelse og tilføjer nogle dokumenter til det:

... Directory memoryIndex = ny RAMDirectory (); StandardAnalyzer analysator = ny StandardAnalyzer (); IndexWriterConfig indexWriterConfig = ny IndexWriterConfig (analysator); IndexWriter writter = ny IndexWriter (memoryIndex, indexWriterConfig); Dokumentdokument = nyt dokument (); document.add (nyt TextField ("titel", titel, Field.Store.YES)); document.add (nyt TextField ("body", body, Field.Store.YES)); writter.addDocument (dokument); writter.close (); 

Her opretter vi et dokument med Tekstfelt og tilføj dem til indekset ved hjælp af IndexWriter. Det tredje argument i Tekstfelt konstruktør angiver, om værdien af ​​feltet også skal lagres eller ej.

Analysatorer bruges til at opdele data eller tekst i bidder og derefter filtrere stopord fra dem. Stopord er ord som 'a', 'er', 'er' osv. Disse afhænger helt af det givne sprog.

Lad os derefter oprette en søgeforespørgsel og søge i indekset efter det tilføjede dokument:

public List searchIndex (String inField, String queryString) {Query query = new QueryParser (inField, analyzer) .parse (queryString); IndexReader indexReader = DirectoryReader.open (memoryIndex); IndexSearcher searcher = ny IndexSearcher (indexReader); TopDocs topDocs = searcher.search (forespørgsel, 10); Liste dokumenter = ny ArrayList (); for (ScoreDoc scoreDoc: topDocs.scoreDocs) {document.add (searcher.doc (scoreDoc.doc)); } returnere dokumenter }

I Søg() metode det andet heltalargument angiver, hvor mange topsøgeresultater det skal returnere.

Lad os nu teste det:

@Test offentlig ugyldighed givetSearchQueryWhenFetchedDocumentThenCorrect () {InMemoryLuceneIndex inMemoryLuceneIndex = ny InMemoryLuceneIndex (ny RAMDirectory (), ny StandardAnalyzer ()); inMemoryLuceneIndex.indexDocument ("Hej verden", "En goddag verden"); Liste dokumenter = inMemoryLuceneIndex.searchIndex ("body", "world"); assertEquals ("Hej verden", document.get (0) .get ("titel")); }

Her tilføjer vi et simpelt dokument til indekset med to felter 'titel' og 'body', og forsøger derefter at søge på det samme ved hjælp af en søgeforespørgsel.

6. Lucene Forespørgsler

Da vi nu er fortrolige med det grundlæggende i indeksering og søgning, lad os grave lidt dybere.

I tidligere sektioner har vi set den grundlæggende forespørgselssyntaks, og hvordan man konverterer det til en Forespørgsel eksempel ved hjælp af QueryParser.

Lucene leverer også forskellige konkrete implementeringer:

6.1. TermQuery

EN Semester er en grundlæggende enhed til søgning, der indeholder feltnavnet sammen med den tekst, der skal søges efter.

TermQuery er den enkleste af alle forespørgsler, der består af et enkelt udtryk:

@Test offentlig ugyldighed givenTermQueryWhenFetchedDocumentThenCorrect () {InMemoryLuceneIndex inMemoryLuceneIndex = ny InMemoryLuceneIndex (ny RAMDirectory (), ny StandardAnalyzer ()); inMemoryLuceneIndex.indexDocument ("aktivitet", "kører i sporet"); inMemoryLuceneIndex.indexDocument ("aktivitet", "Biler kører på vej"); Begrebeterm = nyt udtryk ("body", "running"); Forespørgsel = ny TermQuery (term); Liste dokumenter = inMemoryLuceneIndex.searchIndex (forespørgsel); assertEquals (2, document.size ()); }

6.2. PrefixQuery

Sådan søger du i et dokument med et "starter med" ord:

@Test offentlig ugyldighed givenPrefixQueryWhenFetchedDocumentThenCorrect () {InMemoryLuceneIndex inMemoryLuceneIndex = ny InMemoryLuceneIndex (ny RAMDirectory (), ny StandardAnalyzer ()); inMemoryLuceneIndex.indexDocument ("artikel", "Lucene introduktion"); inMemoryLuceneIndex.indexDocument ("artikel", "Introduktion til Lucene"); Begrebeterm = nyt udtryk ("body", "intro"); Query query = new PrefixQuery (term); Liste dokumenter = inMemoryLuceneIndex.searchIndex (forespørgsel); assertEquals (2, document.size ()); }

6.3. WildcardQuery

Som navnet antyder, kan vi bruge jokertegn "*" eller "?" til søgning:

// ... Term term = ny Term ("body", "intro *"); Forespørgsel = ny WildcardQuery (udtryk); // ...

6.4. PhraseQuery

Det bruges til at søge i en sekvens af tekster i et dokument:

// ... inMemoryLuceneIndex.indexDocument ("citater", "En rose med ethvert andet navn lugter så sød."); Query query = new PhraseQuery (1, "body", new BytesRef ("smell"), new BytesRef ("sweet")); Liste dokumenter = inMemoryLuceneIndex.searchIndex (forespørgsel); // ...

Bemærk, at det første argument fra PhraseQuery konstruktør kaldes slop, som er afstanden i antallet af ord mellem de termer, der skal matches.

6.5. FuzzyQuery

Vi kan bruge dette, når vi søger efter noget lignende, men ikke nødvendigvis identisk:

// ... inMemoryLuceneIndex.indexDocument ("artikel", "Halloween Festival"); inMemoryLuceneIndex.indexDocument ("dekoration", "Dekorationer til Halloween"); Begrebeterm = nyt udtryk ("krop", "hallowen"); Forespørgsel = ny FuzzyQuery (udtryk); Liste dokumenter = inMemoryLuceneIndex.searchIndex (forespørgsel); // ...

Vi forsøgte at søge efter teksten "Halloween", men med stavet forkert "hallowen".

6.6. Boolsk forespørgsel

Nogle gange er vi muligvis nødt til at udføre komplekse søgninger ved at kombinere to eller flere forskellige typer forespørgsler:

// ... inMemoryLuceneIndex.indexDocument ("Destination", "Las Vegas singapore car"); inMemoryLuceneIndex.indexDocument ("Pendler i singapore", "Buscykler"); Begreb term1 = nyt begreb ("body", "singapore"); Begreb term2 = ny betegnelse ("krop", "bil"); TermQuery-forespørgsel1 = ny TermQuery (term1); TermQuery-forespørgsel2 = ny TermQuery (term2); BooleanQuery booleanQuery = new BooleanQuery.Builder () .add (query1, BooleanClause.Occur.MUST) .add (query2, BooleanClause.Occur.MUST) .build (); // ...

7. Sortering af søgeresultater

Vi kan også sortere søgeresultatdokumenterne baseret på bestemte felter:

@Test offentlig ugyldighed givenSortFieldWhenSortedThenCorrect () {InMemoryLuceneIndex inMemoryLuceneIndex = ny InMemoryLuceneIndex (ny RAMDirectory (), ny StandardAnalyzer ()); inMemoryLuceneIndex.indexDocument ("Ganges", "River in India"); inMemoryLuceneIndex.indexDocument ("Mekong", "Denne flod strømmer i det sydlige Asien"); inMemoryLuceneIndex.indexDocument ("Amazon", "Rain forest river"); inMemoryLuceneIndex.indexDocument ("Rhinen", "Tilhører Europa"); inMemoryLuceneIndex.indexDocument ("Nilen", "længste flod"); Begrebeterm = nyt udtryk ("krop", "flod"); Forespørgsel = ny WildcardQuery (udtryk); SortField sortField = new SortField ("title", SortField.Type.STRING_VAL, false); Sort sortByTitle = ny Sort (sortField); Liste dokumenter = inMemoryLuceneIndex.searchIndex (forespørgsel, sortByTitle); assertEquals (4, document.size ()); assertEquals ("Amazon", documents.get (0) .getField ("title"). stringValue ()); }

Vi forsøgte at sortere de hentede dokumenter efter titelfelter, som er navnene på floderne. Det boolske argument til SortField konstruktør er til at vende sorteringsrækkefølgen.

8. Fjern dokumenter fra indekset

Lad os prøve at fjerne nogle dokumenter fra indekset baseret på en given Semester:

// ... IndexWriterConfig indexWriterConfig = ny IndexWriterConfig (analysator); IndexWriter-forfatter = ny IndexWriter (memoryIndex, indexWriterConfig); writer.deleteDocuments (begreb); // ...

Vi tester dette:

@Test offentlig ugyldig nårDocumentDeletedThenCorrect () {InMemoryLuceneIndex inMemoryLuceneIndex = ny InMemoryLuceneIndex (ny RAMDirectory (), ny StandardAnalyzer ()); inMemoryLuceneIndex.indexDocument ("Ganges", "River in India"); inMemoryLuceneIndex.indexDocument ("Mekong", "Denne flod strømmer i det sydlige Asien"); Begrebeterm = nyt udtryk ("titel", "ganges"); inMemoryLuceneIndex.deleteDocument (term); Forespørgsel = ny TermQuery (term); Liste dokumenter = inMemoryLuceneIndex.searchIndex (forespørgsel); assertEquals (0, document.size ()); }

9. Konklusion

Denne artikel var en hurtig introduktion til at komme i gang med Apache Lucene. Vi udførte også forskellige forespørgsler og sorterede de hentede dokumenter.

Som altid kan koden for eksemplerne findes på Github.