Lucene - LexML

Propaganda
Lucene
(http://lucene.apache.org/)
João Kramer ([email protected])
Tribunal de Contas da União
Características
• Lucene é uma API de indexação e pesquisa.
• Foi inicialmente codificado por Doug Cutting em
1997.
• Em 2000 estava no SourceForge.
• Em 2004 foi integrado ao projeto Apache.
• Esta codificado em Java, Delphi, Ruby, PHP,C++, C#,
Perl e Python.
O que é um Search Engine?
• Resposta: Um software que cria um índice
para um conjunto de documentos e responde
a consultas usando esse índice.
• Mas um banco de dados faz isso!
O que é um Search Engine?
• Um Search Engine oferece:
– Escalabilidade
– Pesquisa ranqueada (Relevance Ranking)
– Integra diferentes fontes de dados (email, páginas web, arquivos,
banco de dados, ...)
– Trabalha em palavras, não em substrings
– Processo de indexação
• Converte documentos
• Extrai texto e metadados
• Normaliza texto
• Cria o índice invertido
Lucene Java
•
•
•
•
•
•
•
•
Biblioteca para indexação e pesquisa
Sem dependências
Java 1.4 ou superior
Entrada para indexação são instâncias de Document
contendo Fields (field name; field content)
Pesquisa usa objetos do tipo Query
Armazena os índice como arquivos no disco
Não tem conversor de documentos (Nutch)
Não tem web crawler (Nutch)
Lucene Java
• Sintaxe de query poderosa
• Pode-se criar query de forma programática ou
diretamente dos dados informados pelo
usuário.
• Indexação rápida
• Pesquisa rápida
• Sort por relevância ou outros campos
• Grande comunidade (bastante ativa)
Vai começar ...
Criando um índice (Fácil!!!)
• Diretório do índice
File indexDir = new File(“c:\\lucene\\index”);
• Criando o objeto indexador
IndexWriter writer = new IndexWriter(indexDir, new StandardAnalyzer(), true);
• Indexando
Document doc = new Document();
String texto = “The quick brown fox jumps over the lazy dog”;
doc.add(Field.Text(“conteudo”,texto);
doc.add(Field.Text(“data”, (new Date()).toString());
writer.addDocument(doc);
writer.close();
Pesquisando
• Diretório do índice
Directory indexDir = FSDirectory.getDirectory(“c:\\lucene\\index”,false);
• Criando o objeto de pesquisa
IndexSearcher is = new IndexSearcher (indexDir);
• Pesquisando
String pesquisa = “fox”;
Query query = new QueryParser.parse(pesquisa,”conteudo”,new StandardAnalyzer());
Hits hits = is.search(query);
for (int i=0; i < hits.length(); i++)
{
Document doc = hits.doc(i);
System.out.println(doc.get(“conteudo”));
System.out.println(“indexado em  “ + doc.get(“data”));
}
Classes de indexação e pesquisa
• Classes fundamentais do processo de
indexação e pesquisa
Classes de indexação - IndexWriter
•
•
•
•
Cria um novo índice.
Adiciona documentos a um índice.
Não permite ler o índice ou pesquisá-lo.
Usada para modificar o índice.
File indexDir = new File(“c:\\lucene\\index”);
IndexWriter writer = new IndexWriter(indexDir, new StandardAnalyzer(),
true);
Classes de indexação e pesquisa Directory
• Representa o local do índice.
• É uma classe abstrata.
• FSDirectory implementação que acessa os
arquivos do índice no sistema operacional.
• RAMDirectory implementação que mantém os
dados do índice em memória.
Directory indexDir = FSDirectory.getDirectory(“c:\\lucene\\index”,false);
Classes de indexação e pesquisa Analyzer
• Antes do texto ser indexado, ele é processado por um
analisador.
• O analisador pode retirar ‘palavras’ (tokens), eliminar tokens,
transformar para minúscula, retirar ‘stop words’. Em geral
permite realizar qualquer funcionalidade programável.
• Implementações BrazilianAnalyser; WhitespaceAnalyzer;
SimpleAnalyzer; StopAnalyzer; StandardAnalyzer;
File indexDir = new File(“c:\\lucene\\index”);
IndexWriter writer = new IndexWriter(indexDir, new StandardAnalyzer(), true);
Classes de indexação e pesquisa Document
• Representa um coleção de campos (fields).
• O campos (fields) representam o documento
ou seus metadados.
• O Lucene apenas indexa texto (String ou
Reader)
Document doc = new Document();
String texto = “The quick brown fox jumps over the lazy dog”;
doc.add(Field.Text(“conteudo”,texto);
doc.add(Field.Text(“data”, (new Date()).toString());
Classes de indexação e pesquisa - Field
• Um documento contêm um ou mais campos
(fields) encapsulados na classe Field.
• Existem quatro tipos de Field:
– Keyword  não é analisado, mas é indexado e armazenado no índice
(ex: URL, datas, CPF, nomes pessoais). Aceita campos String e Date.
– UnIndexed não é analisado nem indexado, mas é armazenado no
índice (ex:PK).
– UnStored  é analisado e indexado, mas não é armazenado no índice
(mais usado).
– Text  é analisado e indexado; se do tipo String é armazenado, mas
se do tipo Reader não.
Classes de indexação e pesquisa -
IndexSearcher
• Abre o índice no modo read-only e permite
pesquisá-lo.
Directory indexDir = FSDirectory.getDirectory(“c:\\lucene\\index”,false);
IndexSearcher is = new IndexSearcher (indexDir);
String pesquisa = “fox”;
Query query = new QueryParser.parse(pesquisa,”conteudo”,new
StandardAnalyzer());
Hits hits = is.search(query);
Classes de indexação e pesquisa - Term
• É a unidade básica de pesquisa.
• Similar ao Field, consiste num par de strings: o
nome do campo a pesquisar e o valor a ser
pesquisado.
• Também é usada no processo de indexação.
Directory indexDir = FSDirectory.getDirectory(“c:\\lucene\\index”,false);
IndexSearcher is = new IndexSearcher (indexDir);
String pesquisa = “fox”;
Query query = new TermQuery(new Term(”conteudo”,pesquisa));
Hits hits = is.search(query);
Classes de indexação e pesquisa - Query
• Lucene vem com algumas subclasses
concretas de Query.
• Ex: BooleanQuery, PhraseQuery, PrefixQuery,
PhraseQuery, PrefixQuery, PhrasePrefixQuery,
RangeQuery, FilteredQuery e SpanQuery
Classes de indexação e pesquisa TermQuery
• É o tipo mais básico de Query suportado pelo
Lucene.
• É usada para encontrar documentos que
contém campos com um determinado valor.
Classes de indexação e pesquisa - Hits
• É um container que contém ponteiros para
objetos do tipo Document resultantes de uma
pesquisa ranqueada (os documentos são
ranqueados).
• Por questões de performance as instâncias de
Hits não carregam do índice todas as
instâncias de Document. Funciona por
demanda, em blocos de 100 documentos.
Classes de indexação e pesquisa Exemplo
• Exemplo1
Processo de indexação
Processo de indexação
• Três passos:
– Conversão para texto.
– Análise
– Gravar no índice
• Índice invertido
• Ao invés de perguntar “que palavra tem no
documento”, pergunta-se “que documentos contém
determinada palavra”.
• Os tokens são as chaves que apontam para o
documento.
Processo de indexação
• Documentos heterogêneos
– É possível que documentos com diferentes
conjuntos de campos possam coexistir no mesmo
índice.
– Ex: documentos de acórdão com número do
processo, inteiro teor, e ministro relator;
currículos com CPF, nome, data nascimento e
experiência profissional.
Processo de indexação
• Adicionando campos
String sinonimos[] = new String {“menino”,”garoto”,”guri”};
Document doc = new Document();
for (int i=0; i < sinonimos.length ; i++)
{
doc.add(Field.Text(“palavra”,sinonimos[i]));
}
-------------------------------------------------------------------------------------------String sinonimos = “menino garoto guri” ;
Document doc = new Document();
doc.add(Field.Text(“palavra”,sinonimos));
Processo de indexação
• Removendo e recuperando documentos
IndexReader reader = new IndexReader.open(dir);
reader.delete (1);
reader.delete(new Term(“id”,”1”));
reader.delete(new Term(“CPF”,”32000227104”));
// deve-se usar a mesma instância do IndexReader
reader.undeleteAll();
Processo de indexação
• Boost factor
Document doc = new Document();
doc.add(Field.Keyword(“senderEmail”,getSenderEmail()));
doc.add(Field.Keyword(“senderName”,getSenderName()));
doc.add(Field.Keyword(“subject”,getSubject()));
doc.add(Field.Text(“body”,getBody()));
if (getSenderDomain().endsWithIgnoreCase(COMPANY_DOMAIN)) {
doc.setBoost(1.5);
} else {
doc.setBoost(0.1);
}
writer.addDocument(doc).
Processo de indexação
• Boost factor
– O “boost fator” é considerado no momento da busca. Na
pesquisa, os documentos recebem uma pontuação que
leva em conta quão compatíveis estão com a query, além
dos valores dos “boost fator”.
– O “boost factor” do documento se aplica a todos os
campos desse.
– O “boost fator” do campo (Field.setBoost) é considerado
em conjunto com o do documento ao qual pertence.
Processo de indexação
• Indexando datas
– Antes de ser indexado, o campo é convertido para
uma String utilizando a classe DateField.
– Recomenda-se converter para String no formato
YYYYMMDD e indexar como String.
Document doc = new Document();
doc.add(Field.Keyword(“data”,new Date()));
Processo de indexação
• Indexando números
– Devem ser convertidos para String e preparados
(ex: 0001, 0010).
Índice - Performance
• Ajustando a performance
Variável IndexWriter
Valor Padrão
Descrição
mergeFactor
10
Controla a freqüência do merge
e tamanho dos segmentos
maxMergeFactor
Integer.MAX_VALUE Limita o número de
documentos por segmento
minMergeDocs
10
Controla a quantidade de RAM
usada ao indexar
Índice - Performance
• Ajustando a performance
– Se o mergeFactor for 10, um novo segmento é
criado para cada 10 documentos adicionados ao
índice.
– Quando o 10º segmento é adicionado, todos os 10
segmentos são concatenados num único
segmento de com 100 documentos.
– Quando 10º segmento de 100 documentos for
criado, novamente os segmentos serão
concatenados em 1 segmento de 1000.
Índice - Performance
• Ajustando a performance
– Nunca existirão mais de 9 segmentos no índice e o
tamanho de cada segmento é uma potência de
10.
– Exceção: se o maxMergeDocs for 1.000, ao invés
de criar um segmento de 10.000, será criado o 11º
segmento de 1000.
– minMergeDocs controla quantos documentos
devem ser mantidos em memória antes de serem
gravados no segmento.
Índice - Exemplo
• Exemplo 2
Índice - RAMDirectory
• Indexação na memória (RAMDirectory)
IndexWriter writer = new IndexWriter(new RamDirectory(), new
SimpleAnalyzer(),true);
writer.mergeFactor = x;
writer.maxMergeDocs = y;
writer.minMergeDocs = z;
Índice - Batch
• Indexação em batch
FSDirectory fsDir = FSDirectory.getDirectory("/tmp/index“,true);
RAMDirectory ramDir = new RAMDirectory();
IndexWriter fsWriter = IndexWriter(fsDir,new SimpleAnalyzer(), true);
IndexWriter ramWriter = new IndexWriter(ramDir,new SimpleAnalyzer(), true);
while (existe documento para indexar) {
... criar Document ...
ramWriter.addDocument(doc);
if (condition for flushing memory to disk has been met) {
fsWriter.addIndexes(Directory[] {ramDir});
ramWriter.close();
ramWriter = new IndexWriter(ramDir, new SimpleAnalyzer(),true);
}
}
Manutenção do índice
• Otimização do índice
– Quanto menos arquivos de índice menor o tempo
de pesquisa. Não minimiza o tempo de indexação!
– Segundo o livro, incluir documentos num índice
não otimizado é mais rápido que incluí-los num
índice otimizado (why!?)
– O tempo de pesquisa é reduzido em função da
menor quantidade de arquivos a ler.
Índice - Otimização
• Otimização do índice
IndexWriter writer = new IndexWriter("/path/to/index“,analyzer, false);
writer.optimize();
writer.close();
Índice – Concorrência
• Regras de concorrência
– Qualquer número de operações read-only podem ser
executadas de forma concorrente. Várias threads e
processos podem pesquisar no índice ao mesmo
tempo.
– Qualquer número de operações read-only podem ser
executadas durante a atualização do índice
(otimização, adição de novos documentos, atualização
ou deleção de documentos).
– Somente um operação de alteração de índice por vez.
Índice - Concorrência
• Regras de concorrência
Operação
Se permitida
Executar pesquisas simultâneas no mesmo índice.
Sim
Executar pesquisas simultâneas num índice que está sendo
criado, otimizado, concatenado com outro índice, ou cujos
documentos estão sendo apagados ou atualizados.
Sim
Adicionar ou atualizar documentos no mesmo índice usando
múltiplas instâncias de IndexWriter.
Não
Esquecer de fechar o IndexReader usado para apagar
documentos a um índice, antes de abrir um IndexWriter para
adicionar mais documentos ao mesmo índice.
Não
Esquecer de fechar o IndexWriter usado para adicionar
documentos a um índice, antes de abrir um IndexReader para
apagar documentos desse mesmo índice.
Índice – Concorrência
• Regras de concorrência
– Operações com uma instância de IndexWriter ou
IndexReader compartilhadas entre várias threads.
Query
Read doc
Add
Delete
Optimize
Merge
Query (R)
Read doc (R)
Add (W)
Delete (R)
x
x
x
x
Optimize (W)
x
Merge (W)
x
Índice – Concorrência
• Regras de concorrência
– Uma operação de modificação do índice através
do IndexReader não pode ser feita se uma
operação de modificação de índice através do
IndexWriter estiver em progresso, e vice-versa.
Índice – Concorrência
Lock File Classe
Obtido em
Apagado em
Descrição
write.lock
IndexWriter
Construtor
close()
Lock apagado quando o IndexWriter é fechado.
write.lock
IndexReader
delete(int)
close()
Lock apagado quando o IndexReader é fechado.
write.lock
IndexReader
undeleteAll(int)
close()
Lock apagado quando o IndexReader é fechado.
write.lock
IndexReader
setNorms(int, String, byte)
close()
Lock apagado quando o IndexReader é fechado.
commit.lock
IndexWriter
Constructor
Construtor
Lock apagado assim que a informação do segmento é
lida ou gravada.
commit.lock
IndexWriter
addIndexes
(IndexReader[])
addIndexes
(IndexReader[])
Lock obtido quando um novo segmento é gravado.
commit.lock
IndexWriter
addIndexes (Directory[])
addIndexes
(Directory[])
Lock obtido quando um novo segmento é gravado.
commit.lock
IndexWriter
mergeSegments (int)
mergeSegments (int)
Lock obtido quando um novo segmento é gravado.
commit.lock
IndexReader
open(Directory)
Open(Directory)
Lock obtido até que todos os segmentos sejam lidos.
commit.lock
SegmentReader
doClose()
doClose()
Lock obtido enquanto o segmento é gravado ou
regravado.
commit.lock
SegmentReader
undeleteAll()
undeleteAll()
Lock obtido enquanto o arquivo de segmento .del é
apagado.
Pesquisa
Pesquisa – Principais classes
Classe
Objetivo
IndexSearcher
Todas as pesquisas são feitas através de uma instância de
IndexSearch usando um dos métodos sobrecarregados
search.
Query e subclasses
Classes concretas de Query encapsulam a lógica para um
determinado tipo de consulta. Essas instâncias são passadas
para o método search de IndexSearch.
QueryParser
Processa expressões de pesquisa e gera uma classe concreta
filha de Query.
Hits
Provê acesso aos resultados das pesquisas. Um instância de
Hits é retornada pelo método search de IndexSearch.
Pesquisa - Exemplo
public class SearchingTest extends LiaTestCase {
public void testTerm() throws Exception {
IndexSearcher searcher = new IndexSearcher(directory);
Term t = new Term("subject", "ant");
Query query = new TermQuery(t);
Hits hits = searcher.search(query);
assertEquals("JDwA", 1, hits.length());
t = new Term("subject", "junit");
hits = searcher.search(new TermQuery(t));
assertEquals(2, hits.length());
searcher.close();
}
}
Pesquisa – Exemplo
• É importante que os termos passados para o
IndexSearcher sejão consistentes
(compatíveis) com os termos produzidos pelo
analisador ao indexar o documento.
• Hints  os documentos são recuperados sob
demanda.
Pesquisa - QueryParser
• Fazer o ‘parse’ de uma consulta de usuário (ex:
bola OR goleiro) corresponde a transformá-la
numa instância apropriada de Query.
• O objeto QueryParser requer uma analizador
(analyzer) para quebrar a consulta em termos
(terms).
• Os outros tipos de Query não requerem um
analizador, entretanto os termos pesquisados
devem corresponder ao que foi indexado.
Pesquisa – QueryParser (exemplo)
public void testQueryParser() throws Exception {
IndexSearcher searcher = new IndexSearcher(directory);
Query query = QueryParser.parse("+JUNIT +ANT MOCK“,"contents“,new SimpleAnalyzer());
Hits hits = searcher.search(query);
assertEquals(1, hits.length());
Document d = hits.doc(0);
assertEquals("Java Development with Ant", d.get("title"));
query = QueryParser.parse("mock OR junit“,"contents“,new
SimpleAnalyzer());
hits = searcher.search(query);
assertEquals("JDwA and JIA", 2, hits.length());
}
Pesquisa – QueryParser (expressões)
Expressão da Query
Encontra documentos que ...
java
Contêm java no campo default.
java junit
Java or junit
Contêm o termo java ou junit, ou ambos, no campo default.
+java +junit
java AND junit
Contêm java e junit no campo default.
title:ant
Contêm o termo ant no campo title.
title:extreme –subject:sports
title:extreme AND NOT
subject:sports
Têm extreme no campo title e não tem sports no campo subject
(agile OR extreme) AND
methodology
Contêm methodology e deve conter agile e/ou extreme, todos no campo default.
title:”junit in action”
Contêm a frase (exata) ‘junit in action’ no campo title
title:”junit action”~5
Contêm os termos junit e action a menos de 5 posições de distância um do outro
java*
Contêm termos que começam com java no campo default.
java~
Contêm termos que são semelhantes (próximos) a java, como ‘lava’
lastmodified:[1/1/04 TO
12/31/04]
Têm no campo lastmodified valores de data entre 01/01/2004 e 31/12/04.
Pesquisa - IndexSearcher
• O último argumento do método FSDirectory.getDirectory()
sinaliza a abertura do índice, e não a construção de um
novo.
public abstract class LiaTestCase extends TestCase {
private String indexDir = System.getProperty("index.dir");
protected Directory directory;
protected void setUp() throws Exception {
directory = FSDirectory.getDirectory(indexDir,false);
}
// ...
}
Pesquisa – IndexSearcher
• Uma instância de IndexSearcher pesquisa o
índice somente com os valores existentes no
momento da abertura. Se está ocorrendo uma
indexação concorrentemente com a pesquisa,
os novos documentos não serão visíveis. Para
visualizar os novos documentos uma nova
instância de IndexSearcher deve ser criada.
Pesquisa - Hits
Métodos do Hits Retorno
length()
Número de documentos na coleção Hits.
doc(n)
Instância do documento (Document) na posição “n”. Os documento
são ordenados pela pontuação que recebem.
id(n)
ID do documento na posição “n”.
score(n)
Pontuação normalizada (baseada na pontuação do maior
documento). Os pontos ficam entre 0 e 1.
Pesquisa - Hits
• O objeto Hits mantém um número limitado de
documentos na memória e além de uma lista
dos documentos mais acessados. Os 100
primeiros documentos são automaticamente
recuperados e colocados no cache.
• Os métodos doc(n), id(n) e score(n) forçam a
leitura do documento no índice se esses ainda
não estão no cache.
Pesquisa – Hits- Paginação
• Mantenha as instâncias originais de Hits e
IndexSearcher ao navegar pelos resultados.
• Refaça a consulta sempre que o usuário
navegar para uma nova página (pode parecer
lento, mas não é).
Pesquisa - Scoring
Factor
Description
tf(t in d)
Term frequency factor for the term (t) in the
document (d).
idf(t)
Inverse document frequency of the term.
boost(t.field in d)
Field boost, as set during indexing.
lengthNorm(t.field in d)
Normalization value of a field, given the number of
terms within the
field. This value is computed during indexing and
stored in the index.
coord(q, d)
Coordination factor, based on the number of query
terms the document contains.
queryNorm(q)
queryNorm(q) Normalization value for a query,
given the sum of the squared weights
of each of the query terms.
Pesquisa - TermQuery
• Um termo (Term) é a menor parte do índice, e
consiste de um nome de campo e um valor.
public void testKeyword() throws Exception {
IndexSearcher searcher = new IndexSearcher(directory);
Term t = new Term("isbn", "1930110995");
Query query = new TermQuery(t);
Hits hits = searcher.search(query);
assertEquals("JUnit in Action", 1, hits.length());
}
Pesquisa - RangeQuery
• Facilita a pesquisa de um termo inicial a um termo final.
• O terceiro argumento do construtor RangeQuery indica se o
intervalo especificado é inclusivo ou não ([] ; {})
public class RangeQueryTest extends LiaTestCase {
private Term begin, end;
protected void setUp() throws Exception {
begin = new Term("pubmonth","198805");
// pub date of TTC was October 1988
end = new Term("pubmonth","198810");
super.setUp();
}
public void testInclusive() throws Exception {
RangeQuery query = new RangeQuery(begin, end, true);
IndexSearcher searcher = new IndexSearcher(directory);
Hits hits = searcher.search(query);
assertEquals("tao", 1, hits.length());
}
}
Pesquisa - PrefixQuery
• Encontra documentos que contenham termos
começando com uma determinada String.
public class PrefixQueryTest extends LiaTestCase {
public void testPrefix() throws Exception {
IndexSearcher searcher = new IndexSearcher(directory);
Term term = new Term("category“,
"/technology/computers/programming");
PrefixQuery query = new PrefixQuery(term);
Hits hits = searcher.search(query);
int programmingAndBelow = hits.length();
hits = searcher.search(new TermQuery(term));
int justProgramming = hits.length();
assertTrue(programmingAndBelow > justProgramming);
}
}
Pesquisa - BooleanQuery
• Uma cláusula é que pode ser opcional,
requerida ou proibida.
Pesquisa - BooleanQuery
public void testAnd() throws Exception {
TermQuery searchingBooks =new TermQuery(new Term("subject","search"));
RangeQuery currentBooks =new RangeQuery(new Term("pubmonth","200401"), new Term("pubmonth","200412"),true);
BooleanQuery currentSearchingBooks = new BooleanQuery();
currentSearchingBooks.add(searchingBook s, true, false);
currentSearchingBooks.add(currentBooks, true, false);
IndexSearcher searcher = new IndexSearcher(directory);
Hits hits = searcher.search(currentSearchingBooks);
assertHitsIncludeTitle(hits, "Lucene in Action");
}
// following method from base LiaTestCase class
protected final void assertHitsIncludeTitle(Hits hits, String title) throws IOException {
for (int i=0; i < hits.length(); i++) {
Document doc = hits.doc(i);
if (title.equals(doc.get("title"))) {
assertTrue(true);
return;
}
}
fail("title '" + title + "' not found");
}
Pesquisa - BooleanQuery
required
prohibited
false
true
false
clause is optional
clause must match
true
clause must not
match
Invalid
Pesquisa - PhraseQuery
• O índice contém informação sobre a posição
dos termos e a classe PhraseQuery usa essa
informação para determinar a distância entre
os termos.
The quick brown fox jumped over the lazy dog
(quick fox)  1
(fox quick)  3
(quick,jumped,lazy)  4
(lazy,jumped,quick)  8
Pesquisa - PhraseQuery
public class PhraseQueryTest extends TestCase {
private IndexSearcher searcher;
protected void setUp() throws IOException {
// set up sample document
RAMDirectory directory = new RAMDirectory();
IndexWriter writer = new IndexWriter(directory,
new WhitespaceAnalyzer(), true);
Document doc = new Document();
doc.add(Field.Text("field“,"the quick brown fox jumped over the lazy dog"));
writer.addDocument(doc);
writer.close();
searcher = new IndexSearcher(directory);
}
private boolean matched(String[] phrase, int slop) throws IOException {
PhraseQuery query = new PhraseQuery();
query.setSlop(slop);
for (int i=0; i < phrase.length; i++) {
query.add(new Term("field", phrase[i]));
}
Hits hits = searcher.search(query);
return hits.length() > 0;
}
}
Pesquisa - PhraseQuery
public void testSlopComparison() throws Exception {
String[] phrase = new String[] {"quick", "fox"};
assertFalse("exact phrase not found", matched(phrase, 0));
assertTrue("close enough", matched(phrase, 1));
}
public void testMultiple() throws Exception {
assertFalse("not close enough“,matched(new String[] {"quick", "jumped", "lazy"},
3));
assertTrue("just enough“,matched(new String[] {"quick", "jumped", "lazy"}, 4));
assertFalse("almost but not quite“,matched(new String[] {"lazy", "jumped",
"quick"}, 7));
assertTrue("bingo“,matched(new String[] {"lazy", "jumped", "quick"}, 8));
}
Pesquisa - WildcardQuery
• Caracter ‘*’ para representar zero ou mais
caracteres
• Caracter ‘?’ para representar zero ou um
caracter.
Pesquisa - WildcardQuery
private void indexSingleFieldDocs(Field[] fields) throws Exception {
IndexWriter writer = new IndexWriter(directory,new WhitespaceAnalyzer(), true);
for (int i = 0; i < fields.length; i++) {
Document doc = new Document();
doc.add(fields[i]);
writer.addDocument(doc);
}
writer.optimize();
writer.close();
}
public void testWildcard() throws Exception {
indexSingleFieldDocs(new Field[] { Field.Text("contents", "wild"),Field.Text("contents", "child"),
Field.Text("contents", "mild"),Field.Text("contents", "mildew") });
IndexSearcher searcher = new IndexSearcher(directory);
Query query = new WildcardQuery(new Term("contents", "?ild*"));
Hits hits = searcher.search(query);
assertEquals("child no match", 3, hits.length());
assertEquals("score the same", hits.score(0),
hits.score(1), 0.0);
assertEquals("score the same", hits.score(1),
hits.score(2), 0.0);
}
Pesquisa - FuzzyQuery
• Procura por termos semelhantes.
public void testFuzzy() throws Exception {
indexSingleFieldDocs(new Field[] {Field.Text("contents", "fuzzy"),
Field.Text("contents", "wuzzy“)});
IndexSearcher searcher = new IndexSearcher(directory);
Query query = new FuzzyQuery(new Term("contents", "wuzza"));
Hits hits = searcher.search(query);
assertEquals("both close enough", 2, hits.length());
assertTrue("wuzzy closer than fuzzy",
hits.score(0) != hits.score(1));
assertEquals("wuzza bear“,"wuzzy", hits.doc(0).get("contents"));
}
Pesquisa - Exemplo
• Exemplo 3
Analyzer
• Durante o processo de análise pode-se
remover palavras, acentos e palavras comuns
(stop words), descartar pontuação ou reduzir
palavras a forma ‘primitiva’ (stemming). Esse
processo é denominado ‘tokenization’ e os
texto resultantes dele são chamados de
‘tokens’.
• A combinação dos ‘tokens’ ao nome do campo
é denominado ‘terms’.
Analyzer – Exemplo
Analyzing "The quick brown fox jumped over the lazy dogs"
WhitespaceAnalyzer:
[The] [quick] [brown] [fox] [jumped] [over] [the] [lazy] [dogs]
SimpleAnalyzer:
[the] [quick] [brown] [fox] [jumped] [over] [the] [lazy] [dogs]
StopAnalyzer:
[quick] [brown] [fox] [jumped] [over] [lazy] [dogs]
StandardAnalyzer:
[quick] [brown] [fox] [jumped] [over] [lazy] [dogs]
Analyzing "XY&Z Corporation - [email protected]"
WhitespaceAnalyzer:
[XY&Z] [Corporation] [-] [[email protected]]
SimpleAnalyzer:
[xy] [z] [corporation] [xyz] [example] [com]
StopAnalyzer:
[xy] [z] [corporation] [xyz] [example] [com]
StandardAnalyzer:
[xy&z] [corporation] [[email protected]]
Analyzer -Exemplo
• Exemplo 4
Analyzer -Escopo
• Índice
Analyzer analyzer = new StandardAnalyzer();
IndexWriter writer = new IndexWriter(directory, analyzer,
true);
• Documento
writer.addDocument(doc, analyzer);
• Durante a análise tem-se acesso ao nome do campo,
portanto pode-se utilizar essa informação para analisar o
texto associado ao campo de forma específica.
Analyzer - Parsing
• A análise não é parser.
• Um documento HTML deve ter as tags
removidas antes de ser submetido ao
analisador.
Analyzer -Classe
• A classe abstrata Analyzer é a base.
• Transforma o texto numa stream de tokens (não é terms)
chamada de TokenStream.
public final class SimpleAnalyzer extends Analyzer {
public TokenStream tokenStream(String fieldName, Reader reader) {
return new LowerCaseTokenizer(reader);
}
}
• O TokenStream se parece com a classe Enumerator, ou seja
retornar sucessivos Tokens a cada chamada do método next()
e retorna null ao final.
Analyzer - Token
• É representado pela classe Token e tem como
atributos: ‘start offset’ , ‘end offset’, ‘position
increment’, texto e tipo.
the quick brown fox
• 1: [the:0->3:word]
• 2: [quick:4->9:word]
• 3: [brown:10->15:word]
• 4: [fox:16->19:word]
Analyzer - Estrutura
Analyzer - Estrutura
Analyzer -Tokenizer
• A fonte de dados é um Reader.
• Sobrescreve o método next(Token t) de
TokenStream.
Analyzer -TokenStream
public abstract class TokenStream {
// método chamado pelo indexador
public Token next() throws IOException {
Token result = next(new Token());
if (result != null) {
Payload p = result.getPayload();
if (p != null) {
result.setPayload((Payload) p.clone());
}
}
return result;
}
// métodos a serem sobrescritos
public Token next(Token result) throws IOException {
return next();
}
public void reset() throws IOException {}
public void close() throws IOException {}
}
Analyzer - Tokenizer
public abstract class Tokenizer extends TokenStream {
/** The text source for this Tokenizer. */
protected Reader input;
/** Construct a tokenizer with null input. */
protected Tokenizer() {}
/** Construct a token stream processing the given input. */
protected Tokenizer(Reader input) {
this.input = input;
}
/** By default, closes the input Reader. */
public void close() throws IOException {
input.close();
}
/** Expert: Reset the tokenizer to a new reader. Typically, an
* analyzer (in its reusableTokenStream method) will use
* this to re-use a previously created tokenizer. */
public void reset(Reader input) throws IOException {
this.input = input;
}
}
Analyzer - CharTokenizer
public abstract class CharTokenizer extends Tokenizer {
public CharTokenizer(Reader input) {
super(input);
}
protected abstract boolean isTokenChar(char c);
protected char normalize(char c) {
return c;
}
public final Token next(Token token) throws IOException {
token.clear();
int length = 0;
int start = bufferIndex;
char[] buffer = token.termBuffer();
while (true) {
if (bufferIndex >= dataLen) {
offset += dataLen;
dataLen = input.read(ioBuffer);
if (dataLen == -1) {
....
}
}
Analyzer - WhitespaceTokenizer
public class WhitespaceTokenizer extends CharTokenizer {
/** Construct a new WhitespaceTokenizer. */
public WhitespaceTokenizer(Reader in) {
super(in);
}
/** Collects only characters which do not satisfy
* {@link Character#isWhitespace(char)}.*/
protected boolean isTokenChar(char c) {
return !Character.isWhitespace(c);
}
}
Analyzer - Classe
public abstract class Analyzer {
// o indexador irá chamar o método next() de TokenStream
public abstract TokenStream tokenStream(String fieldName, Reader reader);
public TokenStream reusableTokenStream(String fieldName, Reader reader) throws IOException {
return tokenStream(fieldName, reader);
}
private ThreadLocal tokenStreams = new ThreadLocal();
protected Object getPreviousTokenStream() {
return tokenStreams.get();
}
protected void setPreviousTokenStream(Object obj) {
tokenStreams.set(obj);
}
public int getPositionIncrementGap(String fieldName)
{
return 0;
}
}
Analyzer - WhitespaceAnalyzer
public final class WhitespaceAnalyzer extends Analyzer {
public TokenStream tokenStream(String fieldName, Reader reader) {
return new WhitespaceTokenizer(reader);
}
public TokenStream reusableTokenStream(String fieldName, Reader reader) throws IOException {
Tokenizer tokenizer = (Tokenizer) getPreviousTokenStream();
if (tokenizer == null) {
tokenizer = new WhitespaceTokenizer(reader);
setPreviousTokenStream(tokenizer);
} else
tokenizer.reset(reader);
return tokenizer;
}
}
Analyzer
public final class BrazilianAnalyzer extends Analyzer {
public final TokenStream tokenStream(String fieldName, Reader reader) {
TokenStream result = new StandardTokenizer(reader);
// remove "'s" and "."
result = new StandardFilter(result);
// remove stop words
result = new StopFilter(result, stoptable);
// do brazilian steamming
result = new BrazilianStemFilter(result, excltable);
return result;
}
}
Analyzer
• Ver BrazilianAnalyzer
Documentos “Indexáveis”
• HTML (CyberNeko HTML Parser, JTidy,
TagSoup,Jericho HTML Parser, TextExtractor)
• XML (deve-se fazer o parse)
• Arquivos Openoffice  são arquivos ZIP que
contêm um arquivo ‘meta.xml’ e outro
‘content.xml’.
• Word, Excel, PowerPoint, Vision  usar o POI
Documentos “Indexáveis
• Email 
http://www.tropo.com/techno/java/lucene/i
map.html e http://guests.evectors.it/zoe/
• RTF  Usar classe SwingRTFEditorKit
• PDF  PDFBox, XPDF e JPedal.
Solução TCU
• GED (900 mil documentos ; incluindo as
versões)
• Documentos incluídos no GED são
automaticamente indexados pelo Lucene
• Aplicativo em Swing (Sisdoc) usa dados
relacionais e do Lucene nas consultas
• Aplicativos corporativos alimentam o GED.
Solução TCU
Quem usa
• Wikipedia
• TCU
• Apple Mac OS Finder (correspondente ao
Microsoft Explorer)
• Enciclopédia Britânica (CD / DVD)
• FedEx
• Eclipse
Bibliografia
• “Lucene in Action” de Otis Gospodnetic & Erik
Hatcher
• http://wiki.apache.org/lucenejava/LuceneFAQ
• http://lucene.apache.org/
Download