Introduksjon til Apache Lucene
1. Oversikt
Apache Lucene er en søkemotor i fulltekst som kan brukes fra forskjellige programmeringsspråk.
I denne artikkelen vil vi prøve å forstå kjernekonseptene i biblioteket og lage en enkel applikasjon.
2. Maven-oppsett
For å komme i gang, la oss først legge til nødvendige avhengigheter:
org.apache.lucene lucene-core 7.1.0
Den siste versjonen finner du her.
For å analysere søkene våre trenger vi også:
org.apache.lucene lucene-queryparser 7.1.0
Se etter den nyeste versjonen her.
3. Kjernekonsepter
3.1. Indeksering
Enkelt sagt bruker Lucene en “invertert indeksering” av data - i stedet for å kartlegge sider til nøkkelord, tilordner det nøkkelord til sider akkurat som en ordliste på slutten av en hvilken som helst bok.
Dette gir raskere svar på søket, ettersom det søker gjennom en indeks, i stedet for å søke gjennom tekst direkte.
3.2. Dokumenter
Her er et dokument en samling felt, og hvert felt har en verdi knyttet til seg.
Indekser består vanligvis av ett eller flere dokumenter, og søkeresultatene er sett med best matchende dokumenter.
Det er ikke alltid et vanlig tekstdokument, det kan også være en databasetabell eller en samling.
3.3. Enger
Dokumenter kan ha feltdata, der et felt vanligvis er en nøkkel som inneholder en dataverdi:
tittel: Goodness of Tea body: Diskuterer godhet ved å drikke urtete ...
Legg merke til det her tittel og kropp er felt og kan søkes etter sammen eller hver for seg.
3.4. Analyse
En analyse konverterer den gitte teksten til mindre og presise enheter for å gjøre det enkelt å søke.
Teksten går gjennom ulike operasjoner for å trekke ut søkeord, fjerne vanlige ord og skilletegn, endre ord til små bokstaver, etc.
For dette formålet er det flere innebygde analysatorer:
- Standardanalysator - analyser basert på grunnleggende grammatikk, fjerner stoppord som “a”, “an” osv. Konverterer også med små bokstaver
- SimpleAnalyzer - bryter teksten basert på tegn uten bokstav og konverterer med små bokstaver
- WhiteSpaceAnalyzer - bryter teksten basert på hvite mellomrom
Det er flere analysatorer tilgjengelig for oss å bruke og tilpasse også.
3.5. Søker
Når en indeks er bygget, kan vi søke i den indeksen ved hjelp av a Spørsmål og en IndexSearcher. Søkeresultatet er vanligvis et resultatsett som inneholder de hentede dataene.
Merk at en IndexWritter er ansvarlig for å lage indeksen og en IndexSearcher for å søke i indeksen.
3.6. Spørringssyntaks
Lucene gir en veldig dynamisk og enkel å skrive syntaks.
For å søke i en fri tekst, bruker vi bare en tekst String som spørringen.
For å søke i en tekst i et bestemt felt, bruker vi:
feltnavn: tekst f.eks. tittel: te
Omfangssøk:
tidsstempel: [1509909322,1572981321]
Vi kan også søke med jokertegn:
drikke
vil søke etter et enkelt tegn i stedet for jokertegnet “?”
d * k
søker etter ord som begynner med “d” og slutter med “k”, med flere tegn i mellom.
uni *
vil finne ord som begynner med “uni”.
Vi kan også kombinere disse spørsmålene og opprette mer komplekse spørsmål. Og inkluder logiske operatører som AND, NOT, OR:
tittel: "Te i frokosten" OG "kaffe"
Mer om spørringssyntaks her.
4. En enkel applikasjon
La oss lage et enkelt program og indeksere noen dokumenter.
Først oppretter vi en indeks i minnet og legger til noen dokumenter i den:
... KatalogminneIndex = ny RAMDirectory (); StandardAnalyzer analysator = ny StandardAnalyzer (); IndexWriterConfig indexWriterConfig = ny IndexWriterConfig (analysator); IndexWriter writter = ny IndexWriter (memoryIndex, indexWriterConfig); Dokumentdokument = nytt dokument (); document.add (ny TextField ("tittel", tittel, Field.Store.YES)); document.add (ny TextField ("body", body, Field.Store.YES)); writter.addDocument (dokument); writter.close ();
Her lager vi et dokument med Tekstfelt og legg dem til indeksen ved hjelp av IndexWriter. Det tredje argumentet i Tekstfelt konstruktør indikerer om verdien av feltet også skal lagres eller ikke.
Analysatorer brukes til å dele dataene eller teksten i biter, og deretter filtrere ut stoppordene fra dem. Stoppord er ord som 'a', 'am', 'is' osv. Disse er helt avhengige av det gitte språket.
La oss deretter opprette et søk og søke i indeksen etter dokumentet som er lagt til:
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 (spørring, 10); Liste dokumenter = ny ArrayList (); for (ScoreDoc scoreDoc: topDocs.scoreDocs) {document.add (searcher.doc (scoreDoc.doc)); } returnere dokumenter; }
I Søk() metode det andre heltallargumentet indikerer hvor mange topp søkeresultater det skal returnere.
La oss nå teste det:
@Test offentlig ugyldig gittSearchQueryWhenFetchedDocumentThenCorrect () {InMemoryLuceneIndex inMemoryLuceneIndex = new InMemoryLuceneIndex (new RAMDirectory (), new StandardAnalyzer ()); inMemoryLuceneIndex.indexDocument ("Hello world", "Some hallo world"); Liste dokumenter = inMemoryLuceneIndex.searchIndex ("body", "world"); assertEquals ("Hello world", documents.get (0) .get ("title")); }
Her legger vi til et enkelt dokument i indeksen, med to felt 'title' og 'body', og prøver deretter å søke det samme ved hjelp av et søk.
6. Lucene-spørsmål
Siden vi nå er komfortable med det grunnleggende om indeksering og søk, la oss grave litt dypere.
I tidligere seksjoner har vi sett den grunnleggende spørringssyntaksen, og hvordan du konverterer den til en Spørsmål eksempel bruker QueryParser.
Lucene tilbyr også forskjellige konkrete implementeringer:
6.1. TermQuery
EN Begrep er en grunnleggende enhet for søk, som inneholder feltnavnet sammen med teksten det skal søkes etter.
TermQuery er den enkleste av alle spørsmål som består av et enkelt begrep:
@Test public void givenTermQueryWhenFetchedDocumentThenCorrect () {InMemoryLuceneIndex inMemoryLuceneIndex = new InMemoryLuceneIndex (new RAMDirectory (), new StandardAnalyzer ()); inMemoryLuceneIndex.indexDocument ("aktivitet", "kjører i sporet"); inMemoryLuceneIndex.indexDocument ("aktivitet", "Biler kjører på veien"); Begrepsterm = nytt begrep ("kropp", "løpende"); Query query = new TermQuery (term); Liste dokumenter = inMemoryLuceneIndex.searchIndex (spørring); assertEquals (2, documents.size ()); }
6.2. PrefixQuery
Slik søker du i et dokument med et "starter med" ord:
@Test public void givenPrefixQueryWhenFetchedDocumentThenCorrect () {InMemoryLuceneIndex inMemoryLuceneIndex = new InMemoryLuceneIndex (new RAMDirectory (), new StandardAnalyzer ()); inMemoryLuceneIndex.indexDocument ("artikkel", "Lucene introduksjon"); inMemoryLuceneIndex.indexDocument ("artikkel", "Introduksjon til Lucene"); Begrepsterm = nytt begrep ("kropp", "intro"); Query query = new PrefixQuery (term); Liste dokumenter = inMemoryLuceneIndex.searchIndex (spørring); assertEquals (2, documents.size ()); }
6.3. WildcardQuery
Som navnet antyder, kan vi bruke jokertegn “*” eller “?” for å søke:
// ... Term term = new Term ("body", "intro *"); Query query = new WildcardQuery (term); // ...
6.4. PhraseQuery
Den brukes til å søke i en sekvens av tekster i et dokument:
// ... inMemoryLuceneIndex.indexDocument ("sitater", "En rose med noe annet navn vil lukte så søt."); Query query = new PhraseQuery (1, "body", new BytesRef ("smell"), new BytesRef ("sweet")); Liste dokumenter = inMemoryLuceneIndex.searchIndex (spørring); // ...
Legg merke til at det første argumentet til PhraseQuery konstruktør kalles slop, som er avstanden i antall ord, mellom ordene som skal matches.
6.5. FuzzyQuery
Vi kan bruke dette når vi søker etter noe lignende, men ikke nødvendigvis identisk:
// ... inMemoryLuceneIndex.indexDocument ("artikkel", "Halloween-festival"); inMemoryLuceneIndex.indexDocument ("dekorasjon", "Dekorasjoner til Halloween"); Begrepsterm = nytt begrep ("kropp", "hallowen"); Query query = new FuzzyQuery (term); Liste dokumenter = inMemoryLuceneIndex.searchIndex (spørring); // ...
Vi prøvde å søke etter teksten “Halloween”, men med feilstavet “hallowen”.
6.6. BooleanQuery
Noen ganger kan det hende vi trenger å utføre komplekse søk ved å kombinere to eller flere forskjellige spørsmål:
// ... inMemoryLuceneIndex.indexDocument ("Destination", "Las Vegas singapore car"); inMemoryLuceneIndex.indexDocument ("Pendler i singapore", "Bus Car Bikes"); Begrep term1 = nytt begrep ("kropp", "singapore"); Term term2 = ny Term ("kropp", "bil"); TermQuery query1 = ny TermQuery (term1); TermQuery query2 = ny TermQuery (term2); BooleanQuery booleanQuery = ny BooleanQuery.Builder () .add (query1, BooleanClause.Occur.MUST) .add (query2, BooleanClause.Occur.MUST) .build (); // ...
7. Sortering av søkeresultater
Vi kan også sortere søkeresultatdokumentene basert på visse felt:
@Test offentlig ugyldighet givenSortFieldWhenSortedThenCorrect () {InMemoryLuceneIndex inMemoryLuceneIndex = new InMemoryLuceneIndex (new RAMDirectory (), new StandardAnalyzer ()); inMemoryLuceneIndex.indexDocument ("Ganges", "River in India"); inMemoryLuceneIndex.indexDocument ("Mekong", "Denne elven renner i Sør-Asia"); inMemoryLuceneIndex.indexDocument ("Amazon", "Rain forest river"); inMemoryLuceneIndex.indexDocument ("Rhinen", "Tilhører Europa"); inMemoryLuceneIndex.indexDocument ("Nilen", "Lengste elv"); Begrepsterm = nytt begrep ("kropp", "elv"); Query query = new WildcardQuery (term); SortField sortField = new SortField ("title", SortField.Type.STRING_VAL, false); Sort sortByTitle = new Sort (sortField); Liste dokumenter = inMemoryLuceneIndex.searchIndex (spørring, sortByTitle); assertEquals (4, documents.size ()); assertEquals ("Amazon", documents.get (0) .getField ("title"). stringValue ()); }
Vi prøvde å sortere de hentede dokumentene etter tittelfelt, som er navnene på elvene. Det boolske argumentet til SortField konstruktør er for å reversere sorteringsrekkefølgen.
8. Fjern dokumenter fra indeksen
La oss prøve å fjerne noen dokumenter fra indeksen basert på en gitt Begrep:
// ... IndexWriterConfig indexWriterConfig = ny IndexWriterConfig (analysator); IndexWriter-forfatter = ny IndexWriter (memoryIndex, indexWriterConfig); writer.deleteDocuments (begrep); // ...
Vi tester dette:
@Test offentlig ugyldig nårDocumentDeletedThenCorrect () {InMemoryLuceneIndex inMemoryLuceneIndex = new InMemoryLuceneIndex (new RAMDirectory (), new StandardAnalyzer ()); inMemoryLuceneIndex.indexDocument ("Ganges", "River in India"); inMemoryLuceneIndex.indexDocument ("Mekong", "Denne elven renner i Sør-Asia"); Begrepsterm = nytt begrep ("tittel", "ganges"); inMemoryLuceneIndex.deleteDocument (begrep); Query query = new TermQuery (term); Liste dokumenter = inMemoryLuceneIndex.searchIndex (spørring); assertEquals (0, documents.size ()); }
9. Konklusjon
Denne artikkelen var en rask introduksjon til å komme i gang med Apache Lucene. Vi utførte også ulike spørsmål og sorterte de hentede dokumentene.
Som alltid kan koden for eksemplene finnes på Github.