Por que o Lucene é necessário?
A pesquisa é uma das operações mais comuns que realizamos várias vezes ao dia. Esta pesquisa pode ser em várias páginas da web que existem na Web ou em um aplicativo de música ou um repositório de código ou uma combinação de todos. Alguém pode pensar que um banco de dados relacional simples também pode suportar a pesquisa. Isto está certo. Bancos de dados como MySQL suportam pesquisa de texto completo. Mas e quanto à Web ou um aplicativo de música ou um repositório de código ou uma combinação de todos esses? O banco de dados não pode armazenar esses dados em suas colunas. Mesmo que isso acontecesse, levaria um tempo inaceitável para executar uma pesquisa tão grande.
Um mecanismo de pesquisa de texto completo é capaz de executar uma consulta de pesquisa em milhões de arquivos de uma vez. A velocidade com que os dados estão sendo armazenados em um aplicativo hoje é enorme. Executar a pesquisa de texto completo neste tipo de volume de dados é uma tarefa difícil. Isso ocorre porque as informações de que precisamos podem existir em um único arquivo entre bilhões de arquivos mantidos na web.
Como funciona o Lucene?
A pergunta óbvia que deve vir à sua mente é: como o Lucene é tão rápido em executar consultas de pesquisa de texto completo? A resposta para isso, é claro, é com a ajuda de índices que ele cria. Mas, em vez de criar um índice clássico, o Lucene usa Índices Invertidos.
Em um índice clássico, para cada documento, coletamos a lista completa de palavras ou termos que o documento contém. Em um índice invertido, para cada palavra em todos os documentos, armazenamos em qual documento e posição esta palavra / termo pode ser encontrado em. Este é um algoritmo de alto padrão que torna a pesquisa muito fácil. Considere o seguinte exemplo de criação de um índice clássico:
Doc1 -> "This", "is", "simple", "Lucene", "sample", "classic", "inverted", "index"Doc2 -> "Running", "Elasticsearch", "Ubuntu", "Update"
Doc3 -> "RabbitMQ", "Lucene", "Kafka", "", "Spring", "Boot"
Se usarmos o índice invertido, teremos índices como:
Este -> (2, 71)Lucene -> (1, 9), (12,87)
Apache -> (12, 91)
Estrutura -> (32, 11)
Índices invertidos são muito mais fáceis de manter. Suponha que se quisermos encontrar o Apache em meus termos, terei respostas imediatas com índices invertidos, enquanto que a pesquisa clássica será executada em documentos completos que podem não ser possíveis em cenários em tempo real.
Fluxo de trabalho Lucene
Antes que Lucene possa realmente pesquisar os dados, ele precisa executar etapas. Vamos visualizar essas etapas para um melhor entendimento:
Lucene Workflow
Conforme mostrado no diagrama, isso é o que acontece no Lucene:
- Lucene é alimentado com os documentos e outras fontes de dados
- Para cada documento, Lucene primeiro converte esses dados em texto simples e, em seguida, os Analisadores convertem esta fonte em texto simples
- Para cada termo no texto simples, os índices invertidos são criados
- Os índices estão prontos para serem pesquisados
Com este fluxo de trabalho, Lucene é um motor de busca de texto completo muito forte. Mas esta é a única parte que Lucene cumpre. Precisamos realizar o trabalho nós mesmos. Vejamos os componentes de indexação necessários.
Componentes Lucene
Nesta seção, descreveremos os componentes básicos e as classes básicas do Lucene usadas para criar índices:
- Diretórios: Um índice Lucene armazena dados em diretórios de sistema de arquivos normais ou na memória se você precisar de mais desempenho. É completamente a escolha do aplicativo armazenar dados onde quiser, um banco de dados, a RAM ou o disco.
- Documentos: Os dados que alimentamos para o mecanismo Lucene precisam ser convertidos em texto simples. Para fazer isso, criamos um objeto Document que representa a fonte de dados. Mais tarde, quando executamos uma consulta de pesquisa, como resultado, obteremos uma lista de objetos Document que satisfazem a consulta que passamos.
- Campos: Os documentos são preenchidos com uma coleção de campos. Um campo é simplesmente um par de (nome, valor) Itens. Então, ao criar um novo objeto Document, precisamos preenchê-lo com esse tipo de dados emparelhados. Quando um campo é indexado inversamente, o valor do campo é tokenizado e fica disponível para pesquisa. Agora, enquanto usamos Fields, não é importante armazenar o par real, mas apenas o indexado invertido. Dessa forma, podemos decidir quais dados são apenas pesquisáveis e quais não são importantes para serem salvos. Vejamos um exemplo aqui:
Indexação de Campo
Na tabela acima, decidimos armazenar alguns campos e outros não são armazenados. O campo do corpo não é armazenado, mas indexado. Isso significa que o e-mail será retornado como resultado quando a consulta de um dos Termos para o conteúdo do corpo for executada.
- Termos: Os termos representam uma palavra do texto. Os termos são extraídos da análise e tokenização dos valores dos Campos, portanto O termo é a menor unidade na qual a pesquisa é executada.
- Analisadores: Um analisador é a parte mais importante do processo de indexação e pesquisa. É o Analyzer que converte o texto simples em Tokens e Termos para que possam ser pesquisados. Bem, essa não é a única responsabilidade de um analisador. Um analisador usa um tokenizer para fazer tokens. Um analisador também executa as seguintes tarefas:
- Stemming: Um Analyzer converte a palavra em um Stem. Isso significa que 'flores' é convertido na palavra-tronco 'flor'. Então, quando uma pesquisa por 'flor' é executada, o documento será retornado.
- Filtragem: um analisador também filtra as palavras de parada como 'O', 'é' etc. pois essas palavras não atraem nenhuma consulta para serem executadas e não são produtivas.
- Normalização: este processo remove acentos e outras marcações de caracteres.
Esta é apenas a responsabilidade normal do StandardAnalyzer.
Aplicação de exemplo
Estaremos usando um dos muitos arquétipos Maven para criar um projeto de amostra para o nosso exemplo. Para criar o projeto, execute o seguinte comando em um diretório que você usará como espaço de trabalho:
arquétipo mvn: gerar -DgroupId = com.linuxhint.exemplo -DartifactId = LH-LuceneExample -DarchetypeArtifactId = maven-archetype-quickstart -DinteractiveMode = falseSe você estiver executando o maven pela primeira vez, levará alguns segundos para realizar o comando de geração porque o maven precisa baixar todos os plug-ins e artefatos necessários para fazer a tarefa de geração. Esta é a aparência do resultado do projeto:
Configuração do Projeto
Depois de criar o projeto, sinta-se à vontade para abri-lo em seu IDE favorito. A próxima etapa é adicionar dependências Maven apropriadas ao projeto. Aqui está o pom.arquivo xml com as dependências apropriadas:
Finalmente, para entender todos os JARs que são adicionados ao projeto quando adicionamos esta dependência, podemos executar um comando Maven simples que nos permite ver uma Árvore de Dependência completa para um projeto quando adicionamos algumas dependências a ele. Aqui está um comando que podemos usar:
dependência mvn: árvoreQuando executamos este comando, ele nos mostra a seguinte Árvore de Dependência:
Finalmente, criamos uma classe SimpleIndexer que executa
importar java.io.Arquivo;
importar java.io.FileReader;
importar java.io.IOException;
import org.apache.lucene.análise.Analisador;
import org.apache.lucene.análise.padrão.StandardAnalyzer;
import org.apache.lucene.documento.Documento;
import org.apache.lucene.documento.StoredField;
import org.apache.lucene.documento.Campo de texto;
import org.apache.lucene.índice.IndexWriter;
import org.apache.lucene.índice.IndexWriterConfig;
import org.apache.lucene.loja.FSDirectory;
import org.apache.lucene.util.Versão;
public class SimpleIndexer
private static final String indexDirectory = "/ Usuários / shubham / algum lugar / LH-LuceneExample / Index";
private static final String dirToBeIndexed = "/ Usuários / shubham / algum lugar / LH-LuceneExample / src / main / java / com / linuxhint / example";
public static void main (String [] args) lança Exception
Arquivo indexDir = novo arquivo (indexDirectory);
Arquivo dataDir = novo arquivo (dirToBeIndexed);
Indexador SimpleIndexer = novo SimpleIndexer ();
int numIndexed = indexer.index (indexDir, dataDir);
Sistema.Fora.println ("Total de arquivos indexados" + numIndexado);
índice int privado (File indexDir, File dataDir) throws IOException
Analisador de analisador = novo StandardAnalyzer (versão.LUCENE_46);
IndexWriterConfig config = new IndexWriterConfig (Versão.LUCENE_46,
analisador);
IndexWriter indexWriter = new IndexWriter (FSDirectory.aberto (indexDir),
config);
Arquivo [] arquivos = dataDir.listFiles ();
para (Arquivo f: arquivos)
Sistema.Fora.println ("Arquivo de indexação" + f.getCanonicalPath ());
Documento doc = novo Documento ();
doc.adicionar (novo TextField ("conteúdo", novo FileReader (f)));
doc.add (new StoredField ("fileName", f.getCanonicalPath ()));
indexWriter.addDocument (doc);
int numIndexed = indexWriter.maxDoc ();
indexWriter.perto();
return numIndexed;
Neste código, acabamos de criar uma instância de Documento e adicionamos um novo Campo que representa o conteúdo do Arquivo. Aqui está a saída que obtemos quando executamos este arquivo:
Arquivo de indexação / Usuários / shubham / algum lugar / LH-LuceneExample / src / main / java / com / linuxhint / example / SimpleIndexer.JavaTotal de arquivos indexados 1
Além disso, um novo diretório é criado dentro do projeto com o seguinte conteúdo:
Dados de Índice
Vamos analisar o que todos os arquivos são criados neste índice em mais lições que virão no Lucene.
Conclusão
Nesta lição, vimos como o Apache Lucene funciona e também criamos um aplicativo de exemplo simples baseado em Maven e java.