Arquitetura da Solução
O sistema é composto por três elementos principais:
- FileScannerApp: classe principal que orquestra a execução
- FileCrawler: responsável por varrer diretórios e alimentar a fila
- ContentMatcher: consome arquivos da fila e verifica padrões
Classe Principal
package br.exemplo.filabloqueante;
import java.io.File;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class FileScannerApp {
public static void main(String[] args) {
String caminhoDiretorio = "e://testfile//";
String termoBusca = "aaa";
final int CAPACIDADE_FILA = 5;
final int QUANTIDADE_CONSUMIDORES = 10;
BlockingQueue<File> filaArquivos = new ArrayBlockingQueue<>(CAPACIDADE_FILA);
Thread produtor = new Thread(new FileCrawler(filaArquivos, new File(caminhoDiretorio)));
produtor.start();
for (int i = 0; i < QUANTIDADE_CONSUMIDORES; i++) {
System.out.println("Iniciando consumidor " + (i + 1));
new Thread(new ContentMatcher(filaArquivos, termoBusca)).start();
}
}
}
Produtor: Varredura de Diretórios
package br.exemplo.filabloqueante;
import java.io.File;
import java.util.concurrent.BlockingQueue;
class FileCrawler implements Runnable {
public static final File SENTINELA = new File("");
private final BlockingQueue<File> fila;
private final File diretorioRaiz;
public FileCrawler(BlockingQueue<File> fila, File diretorioRaiz) {
this.fila = fila;
this.diretorioRaiz = diretorioRaiz;
}
@Override
public void run() {
try {
explorar(diretorioRaiz);
fila.put(SENTINELA);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
private void explorar(File diretorio) throws InterruptedException {
File[] conteudo = diretorio.listFiles();
if (conteudo == null) return;
for (File item : conteudo) {
if (item.isDirectory()) {
explorar(item);
} else {
fila.put(item);
}
}
}
}
Consumidor: Análise de Conteúdo
package br.exemplo.filabloqueante;
import java.io.File;
import java.io.IOException;
import java.util.Scanner;
import java.util.concurrent.BlockingQueue;
public class ContentMatcher implements Runnable {
private final BlockingQueue<File> fila;
private final String padrao;
public ContentMatcher(BlockingQueue<File> fila, String padrao) {
this.fila = fila;
this.padrao = padrao;
}
@Override
public void run() {
try {
boolean finalizado = false;
while (!finalizado) {
File arquivo = fila.take();
if (arquivo == FileCrawler.SENTINELA) {
fila.put(arquivo);
finalizado = true;
} else {
analisar(arquivo);
}
}
} catch (IOException | InterruptedException ex) {
ex.printStackTrace();
}
}
private void analisar(File arquivo) throws IOException {
try (Scanner leitor = new Scanner(arquivo)) {
int numeroLinha = 0;
System.out.println("Analisando: " + arquivo.getName());
while (leitor.hasNextLine()) {
numeroLinha++;
String linha = leitor.nextLine();
if (linha.contains(padrao)) {
System.out.printf("%s [%d]: %s%n",
arquivo.getAbsolutePath(),
numeroLinha,
linha
);
}
}
}
}
}
Observações Importantes
A estratégia de sentinelas permite que todos os consumidores identifiquem o término do processamento. Ao encontrar o marcador, cada consumidor o recoloca na fila para que os demais também possam encerrar.
A capacidade limitada da ArrayBlockingQueue impede que o produtor acumule muitos arquivos em memória, controlando o uso de recursos durante a varredura de hierarquias extensas.