2017-02-21 3 views
1

J'utilise RandomAccessFile pour lire quelques informations à partir d'un gros fichier. RandomAccessFile a une méthode seek qui pointe le curseur sur une partie spécifique du fichier que je veux lire toute la ligne. Pour lire cette ligne, j'utilise la méthode readLine().Le moyen le plus rapide de lire une ligne dans le fichier

J'ai lu tout ce fichier avant de créer un index qui me permet d'accéder au début de n'importe quelle ligne avec la méthode seek. Cet index fonctionne bien. J'ai créé cet indice basé sur cette réponse: https://stackoverflow.com/a/42077860/763368

Depuis que je dois faire beaucoup d'accès dans ce dossier, la performance est un problème important de prendre soin, je suis à la recherche d'autres options pour lire le fichier va à un ligne spécifique et obtenir toute la ligne.

J'ai lu que FileChannel avec MappedByteBuffer est une bonne option pour lire rapidement des fichiers, mais je n'ai vu aucune solution qui fait ce que je veux.

P.S .: les lignes ont des longueurs différentes et je ne connais pas ces longueurs.

Est-ce que quelqu'un a une bonne solution?

Modifier:

Le fichier que je veux lire a suivre le format: clé\tvaleur

L'indice est un hashmap avec toutes les clés de cette clés du fichier été et les valeurs est la position d'octet (Long).

Supposons que je veux aller à la ligne avec la « foo » clé, je dois chercher à la position de valeur, comme celui-ci:

raf.seek(index.get("foo")) 

Si je raf.readLine() le retour sera l'ensemble ligne avec la touche "foo".

Mais je ne veux pas utiliser le RandomAccessFile pour ce travail car il est trop lent.

C'est la façon dont je fais maintenant Scala:

val raf = new RandomAccessFile(file,"r") 
raf.seek(position.get(key)) 
println(raf.readLine) 
raf.close 
+2

Êtes-vous accéder à des fichiers différents? Si non, pourquoi fermez-vous l'accès au fichier? Si vous laissez l'accès au fichier ouvert, vous n'avez pas à attendre que le système d'exploitation vous donne l'autorisation de lecture. – Tschallacka

+0

@Tschallacka Je ne fais que fermer à la fin de toutes les lectures, ce n'est qu'un exemple. Mais mon problème ici est la façon de lire le fichier. –

+0

Pouvez-vous fournir le code de lecture de votre index et comment le traduire vers une position de recherche. Parce que vous êtes déjà sur la bonne voie, votre recherche d'index pourrait bénéficier d'une certaine optimisation, mais sans le code complet et les données de l'échantillon, il est difficile d'aider. – Tschallacka

Répondre

1

Si vous avez déjà de lire le fichier une fois pour trouver les indices des clés, la solution absolument plus rapide serait de lire les lignes et gardez-les en mémoire. Si cela ne fonctionne pas pour une raison quelconque (par exemple des contraintes de mémoire), l'utilisation de tampons peut en effet être une bonne alternative. Ceci est un aperçu du code:

FileChannel channel = new RandomAccessFile("/some/file", "r").getChannel(); 

long pageSize = ...; // e.g. "3 GB or file size": max(channel.size(), THREE_GB); 
long position = 0; 
ByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, position, pageSize); 

ByteBuffer slice; 
int maxLineLength = 30; 
byte[] lineBuffer = new byte[maxLineLength]; 

// Read line at indices 20 - 25 
buffer.position(20); 
slice = buffer.slice(); 
slice.get(lineBuffer, 0, 6); 
System.out.println("Starting at 20:" + new String(lineBuffer, Charset.forName("UTF8"))); 

// Read line at indices 0 - 10 
buffer.position(0); 
slice = buffer.slice(); 
slice.get(lineBuffer, 0, 11); 
System.out.println("Starting at 0:" + new String(lineBuffer, Charset.forName("UTF8"))); 

Ce code peut également être utilisé pour des fichiers très volumineux. Il suffit d'appeler channel.map pour trouver la « page » où la clé se trouve: position = keyIndex/pageSize * pageSize puis appelez buffer.position de cet indice: keyIndex - position

Si vous n'avez vraiment un moyen d'accès du groupe à une « page » ensemble, vous n'a pas besoin de slice.Performance ne sera pas aussi bon, mais cela vous permet de simplifier le code plus:

byte[] lineBuffer = new byte[maxLineLength]; 
// ... 
ByteBuffer buffer = channel.map(FileChannel.MapMode.READ_ONLY, keyIndex, lineLength); 
buffer .get(lineBuffer, 0, lineLength); 
System.out.println(new String(lineBuffer, Charset.forName("UTF8"))); 

Notez que le ByteBuffer n'est pas créé sur le tas JVM, mais est en fait un fichier mappé en mémoire au niveau du système d'exploitation. (Depuis Java 8, vous pouvez le vérifier en regardant le code source et en recherchant sun.nio.ch.DirectBuffer dans l'implémentation).

Taille de la ligne: La meilleure façon d'obtenir la taille de la ligne est de stocker quand vous balayez le fichier, à savoir utiliser Map[String, (Long, Int)] au lieu de ce que vous utilisez pour index maintenant. Si cela ne fonctionne pas pour vous, vous devez exécuter des tests pour savoir ce qui est plus rapide:

  • magasin juste la taille maximale de la ligne puis recherchez un saut de ligne dans la chaîne de cette longueur maximale. Dans ce cas, faites attention à l'accès à la fin du fichier dans vos tests unitaires.
  • Numérisez en avant avec ByteBuffer.get jusqu'à ce que vous atteigniez \n. Si vous avez de vrais fichiers Unicode, ce n'est probablement pas une option, puisque le code Ascii pour le saut de ligne (0x0A) peut apparaître ailleurs, par exemple dans la syllabe coréenne codée en UTF-16 avec le code de caractère 0xAC0A.

Ce serait le code Scala pour la deuxième approche:

// this happens once 
val maxLineLength: Long = 2000 // find this in your initial sequential scan 
val lineBuffer = new Array[Byte](maxLineLength.asInstanceOf[Int]) 

// this is how you read a key 
val bufferLength = maxLineLength min (channel.size() - index("key")) 
val buffer = channel.map(FileChannel.MapMode.READ_ONLY, index("key"), bufferLength) 
var lineLength = 0 // or minLineLength 
while (buffer.get(lineLength) != '\n') { 
    lineLength += 1 
} 
buffer.get(lineBuffer, 0, lineLength - 1) 
println(new String(lineBuffer, Charset.forName("UTF8"))) 
+0

J'ai un index, donc je peux accéder au début d'une ligne. J'accède à cet index et puis cherche à là. Avec d'autres options différentes que RandomAccessFile je voudrais chercher à cette position aussi, l'index sera utilisé aussi. –

+0

J'ai lu tout le fichier avant de créer un index. Je mets cet index en mémoire pour que je puisse y accéder et aller au début d'une ligne avec seek mothod. Avec d'autres options que RandomAccessFile je voudrais chercher à cette position aussi, l'index sera aussi utilisé –

+0

Non, je ne peux pas mettre ce fichier en mémoire, c'est plus de 100Go. Ma solution fonctionne, mais c'est lent et c'est mon problème. –