Ler uma linha específica de um arquivo, tipo pelo número

Pessoal, estou com um problema tenho um log que digamos, deve gerar aproximadamente 10 mil linhas por dia, este log deve ser organizado e salvo em um banco de dados, para ser consultado a qualquer momento no sistema, então, digamos que eu tenha uma thread em java que le este arquivo de 10 em 10 minutos, este arquivo é constantemente atualizado e fica disponivel no servidor sem que seja apagado, ele somente é modificado, este arquivo também é diario, é gerado um arquivo por dia.

Bem, o que eu preciso? Preciso ler a informação do log, depois gravar no banco, por exemplo, a thread le as primeiras 1000 linhas, e grava no banco, depois de 10 minutos, vai ler o arquivo novamente, na última leitura eu salvei no banco uma informação: número da ultima linha lida: 1000 por exemplo.

Agora, na próxima leitura, eu nao quero percorrer o arquivo todo contando as linhas até chegar na linha 1000, eu quero começar direto na linha 1000, tipo, digamos que seja algo como gotoLinha(int numLinha) no arquivo.

Tentei a api LineNumberReader, mas ela serve para enumerar as linhas, tentei usar o método setLine(int) e ele apenas modificar o atributo número da linha.

O arquivo é .txt, se alguem souber como me ajudar agradeço! Obrigado

Talvez a RandomAccessFile lhe sirva.

Já tentei, não serve. Eu tentei utilizar o método seek(long) e passar a linha, mas o método seek atribui o “ponteiro” da classe para uma posição no arquivo, em bytes.

Testei com o valor 500, e ele setou na posição do caracter 500.

Alguma outra sugestão, talvez uma lógica para determinar a linha usando o método seek?

Ao terminar uma leitura você não pode salvar a posição atual, usando o getFilePointer? E na próxima leitura usar o seek com esse dado salvo?

Boa ideia, vou tentar esta abordagem e reporto se funcionou! Valeu!

Você pode ler o arquivo todo e colocar todas as linhas em um HashMap. Depois, você pode acessar uma linha qualquer do arquivo a partir desse HashMap.

[code]BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(“arquivo.txt”)));
String line = null;
int lineNumber = 0;
HashMap<Integer, String> lines = new HashMap<Integer, String>();

while ((line = bufferedReader.readLine()) != null) {
lines.put(lineNumber, line);
lineNumber++;
}
bufferedReader.close();[/code]

O código anterior lê todo o arquivo e armazena todas as linhas em um HashMap de Linha. Se você quiser acessar uma linha espeecífica, use:

lines.get(LineNumber);

Onde LineNumber é o número da linha que você quer acessar.
Abs.

[quote=matheuslmota]Você pode ler o arquivo todo e colocar todas as linhas em um HashMap. Depois, você pode acessar uma linha qualquer do arquivo a partir desse HashMap.

[code]BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(“arquivo.txt”)));
String line = null;
int lineNumber = 0;
HashMap<Integer, String> lines = new HashMap<Integer, String>();

while ((line = bufferedReader.readLine()) != null) {
lines.put(lineNumber, line);
lineNumber++;
}
bufferedReader.close();[/code]

O código anterior lê todo o arquivo e armazena todas as linhas em um HashMap de Linha. Se você quiser acessar uma linha espeecífica, use:

lines.get(LineNumber);

Onde LineNumber é o número da linha que você quer acessar.
Abs.[/quote]

  1. Só poderá ler linhas que foram lidas durante a leitura inicial. (se houver novas linhas não estarão no Map).
  2. Os registros estarão todas na memória principal

[quote=RafaelViana][quote=matheuslmota]Você pode ler o arquivo todo e colocar todas as linhas em um HashMap. Depois, você pode acessar uma linha qualquer do arquivo a partir desse HashMap.

[code]BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(new FileInputStream(“arquivo.txt”)));
String line = null;
int lineNumber = 0;
HashMap<Integer, String> lines = new HashMap<Integer, String>();

while ((line = bufferedReader.readLine()) != null) {
lines.put(lineNumber, line);
lineNumber++;
}
bufferedReader.close();[/code]

O código anterior lê todo o arquivo e armazena todas as linhas em um HashMap de Linha. Se você quiser acessar uma linha espeecífica, use:

lines.get(LineNumber);

Onde LineNumber é o número da linha que você quer acessar.
Abs.[/quote]

  1. Só poderá ler linhas que foram lidas durante a leitura inicial. (se houver novas linhas não estarão no Map).
  2. Os registros estarão todas na memória principal[/quote]

Pois é, eu tinha noção desses problemas. Ele teria de ler todo o arquivo para depois acessar as linhas. Não disponho de um arquivo suficientemente grande para efetuar um teste de performance. Quanto ao fato de todos os registros estare na memmória principal, acho que isso não representa algo tão problemático. Um arquivo com 10 mil linhas deve ter em torno de uns 10 megas de tamanho, acho que isso não seria danoso à memória. (A não ser que ele faça a leitura de arquivos igualmente grande várias vezes, ai teremos um problema.)
Abs.

Dessa forma não dá, porque o mesmo documento é constantemente atualizado, então teria que reabrir ele a todo momento, preciso de uma forma segura, e com alto desempenho, armazenar em um hashmap não é seguro, uma vez que pode ocorrer um erro na thread, ou esta ser encerrada por algum motivo, perderia as referencias. Temos também o problema da atualização do arquivo, teriamos que ler novamente e atualizar o hashmap. Ou seja, não é bem o que eu procuro.

Precisava de algo em que voce apontasse o inicio do arquivo através do número da linha, uma espécie de “ctrl + L” pra localizar a linha, tipo do netbeans. mas vejo que de qualquer forma precisamos percorrer o arquivo do inicio ao fim. Farei a leitura deste arquivo de 10 em 10 minutos, durante os 7 dias da semana.

Se alguem conhecer alguma forma, algum algoritmo, estou aberto a sugestões.

Existe uma API da apache chamada Commons-IO.
Baixe ela e adicione o jar commons-io-2.0.1.jar (se estiver no Eclipse basta copiar esse Jar para a pasta do projeto e pelo Eclipse clique no jar com o botão direito Build Path --> Add to Build Path).
Essa API possui uma classe chamada FileUtils, que possui um método ReadLines, que recebe o arquivo como parâmetro. Chame a seguinte linha:

String line = (String)FileUtils.readLine(new File("path")).get(line)

Onde line é o número de linha menos 1, pois a numeraçã das linha começa em 0.

Espero que isso lhe ajude.
Abs.

Dica: armazenar um arquivo completo de log na memória é absurdo, para se dizer o mínimo. Você pode criar um índice para ele, para que, a partir do número da linha, você consiga determinar sua posição para usar com RandomAccessFile. Para criar o índice, você precisa ler o arquivo pelo menos uma vez (com RandomAccessFile para determinar a posição, é claro) e criar um segundo arquivo, contendo as posições relativas a cada número de linha.

É, sabia que minha ideia não era das melhores. Agora entanglement, tira uma dúvida. O arquivo dele vai sofrer alterações constamente, o que significa que ele vai ter que ficar relendo o arquivo a toda hora, para reindexar as linhas. Teria como melhorar o desempenho disso? Porque dessa forma, o acesso as linhas do arquivo vai ser um pouco lento.

Mas o arquivo é texto, não? Ele não é só alterado no final dele (append)? Se esse for o caso, então é relativamente fácil indexá-lo. Isso porque você supõe que tudo que já foi indexado (exceto, talvez, pela última linha) continua com o mesmo valor, e você só precisa indexar o resto.

[quote=entanglement]Mas o arquivo é texto, não? Ele não é só alterado no final dele (append)? Se esse for o caso, então é relativamente fácil indexá-lo. Isso porque você supõe que tudo que já foi indexado (exceto, talvez, pela última linha) continua com o mesmo valor, e você só precisa indexar o resto.

[/quote]

Hum, é mesmo. Então o trabalho maior vai ser na primeira indexação do arquivo.

entanglement, ele não poderia então criar um HashMap que armazenasse o índice da linha e sua posição no arquivo, obtida com o RandomAccessFile? Assim, o tamnho do Map armazenado na memória seria pequeno e o tempo para o acesso seria bem mais rápido caso um arquivo fosse usado para armazenar os índices. O que acha?

EDIT:

O seguinte método faz o que eu falei, gera um HashMap de Posições das linhas:

public HashMap<Integer, Long> generateIndex(String file) throws IOException {
	RandomAccessFile raFile = new RandomAccessFile(file, "r");
	int lineNumber = 1;
	HashMap<Integer, Long> lines = new HashMap<Integer, Long>();
		
	do {
		lines.put(lineNumber, raFile.getFilePointer());
		lineNumber++;
	} while (raFile.readLine() != null);
        raFile.close();	
	return lines;

}

Uma classe completa, que gera um HashMap de linhas e que possui um método para retornar uma linha de uma arquivo dado um número:
EDIT: Havia faltado um método para atualizar o HashMap de Índices:

[code]import java.io.IOException;
import java.io.RandomAccessFile;
import java.util.HashMap;

public class LineRandomAccessFile {

public HashMap<Integer, Long> generateIndexes(String file) throws IOException {
	RandomAccessFile raFile = new RandomAccessFile(file, "r");
	int lineNumber = 1;
	HashMap<Integer, Long> lines = new HashMap<Integer, Long>();
	
	do {
		lines.put(lineNumber, raFile.getFilePointer());
		lineNumber++;
	} while (raFile.readLine() != null);
	
	raFile.close();
	return lines;

}

public String getLine(HashMap<Integer, Long> lines, String file, int index) throws IOException
{
	RandomAccessFile raFile = new RandomAccessFile(file, "r");
	raFile.seek(lines.get(index));
	String line = raFile.readLine();
	raFile.close();
	return line != null ? line : "";
}
    
public HashMap<Integer, Long> updateIndexes(HashMap<Integer, Long> lines, String file) throws IOException {
	int index = lines.size(), i = 1;
	RandomAccessFile raFile = new RandomAccessFile(file, "r");
	raFile.seek(lines.get(index));
	raFile.readLine();
	do {
		lines.put(index + i, raFile.getFilePointer());
		i++;
	} while (raFile.readLine() != null);
	return lines;
}

}
[/code]