Quel est le moyen le plus rapide et le plus efficace de lire la dernière ligne de texte d'un fichier [très, très grand] en Java?
Réponses
Trop de publicités?Ci-dessous sont deux fonctions, l'une qui renvoie la dernière ligne non vide d'un fichier sans chargement ou de marcher à travers l'ensemble du fichier, et l'autre qui renvoie les N dernières lignes du fichier sans avoir à naviguer dans le fichier en entier:
Ce que la queue n'est zoom directement au dernier caractère du fichier, puis pas en arrière, caractère par caractère, l'enregistrement de ce qu'il voit jusqu'à ce qu'il trouve un saut de ligne. Une fois qu'il trouve un saut de ligne, elle tombe en dehors de la boucle. Inverse ce qui a été enregistré et le lance dans une chaîne de caractères et renvoie. 0xA est la nouvelle ligne et 0xD est le retour chariot.
Remarque: Si votre ligne terminaisons sont les deux caractères comme "CR LF", ce code permettra de délimiter sur le transport de retour et de laisser le saut de Ligne dans le contenu.
public String tail( File file ) {
RandomAccessFile fileHandler = null;
try {
fileHandler = new RandomAccessFile( file, "r" );
long fileLength = fileHandler.length() - 1;
StringBuilder sb = new StringBuilder();
for(long filePointer = fileLength; filePointer != -1; filePointer--){
fileHandler.seek( filePointer );
int readByte = fileHandler.readByte();
if( readByte == 0xA ) {
if( filePointer == fileLength ) {
continue;
}
break;
} else if( readByte == 0xD ) {
if( filePointer == fileLength - 1 ) {
continue;
}
break;
}
sb.append( ( char ) readByte );
}
String lastLine = sb.reverse().toString();
return lastLine;
} catch( java.io.FileNotFoundException e ) {
e.printStackTrace();
return null;
} catch( java.io.IOException e ) {
e.printStackTrace();
return null;
} finally {
if (fileHandler != null )
try {
fileHandler.close();
} catch (IOException e) {
/* ignore */
}
}
}
Mais vous ne voulez probablement pas la dernière ligne, vous voulez les N dernières lignes, donc l'utiliser à la place:
public String tail2( File file, int lines) {
java.io.RandomAccessFile fileHandler = null;
try {
fileHandler =
new java.io.RandomAccessFile( file, "r" );
long fileLength = fileHandler.length() - 1;
StringBuilder sb = new StringBuilder();
int line = 0;
for(long filePointer = fileLength; filePointer != -1; filePointer--){
fileHandler.seek( filePointer );
int readByte = fileHandler.readByte();
if( readByte == 0xA ) {
line = line + 1;
if (line == lines) {
if (filePointer == fileLength) {
continue;
}
break;
}
} else if( readByte == 0xD ) {
line = line + 1;
if (line == lines) {
if (filePointer == fileLength - 1) {
continue;
}
break;
}
}
sb.append( ( char ) readByte );
}
String lastLine = sb.reverse().toString();
return lastLine;
} catch( java.io.FileNotFoundException e ) {
e.printStackTrace();
return null;
} catch( java.io.IOException e ) {
e.printStackTrace();
return null;
}
finally {
if (fileHandler != null )
try {
fileHandler.close();
} catch (IOException e) {
}
}
}
Invoquer les méthodes ci-dessus comme ceci:
File file = new File("D:\\stuff\\huge.log");
System.out.println(tail(file));
System.out.println(tail2(file, 10));
Avertissement Dans l'ouest sauvage de l'unicode ce code peut provoquer la sortie de cette fonction à sortir de mal. Par exemple "Marie?s" au lieu de "Marie". Les personnages avec les chapeaux, les accents, les caractères Chinois , etc, peuvent causer la sortie à l'erreur car les accents sont ajoutés en tant que modificateurs d'après le personnage. L'inversion de caractère composé des changements à la nature de l'identité du personnage sur l'inversion. Vous aurez à faire le plein de la batterie de tests sur toutes les langues que vous prévoyez d'utiliser cette avec.
Pour plus d'informations sur cette unicode renversement problème de lire ceci: http://msmvps.com/blogs/jon_skeet/archive/2009/11/02/omg-ponies-aka-humanity-epic-fail.aspx
Apache Commons a une implémentation utilisant RandomAccessFile .
C'est ce qu'on appelle ReversedLinesFileReader .
Jetez un oeil à ma réponse à une question similaire pour C#. Le code devrait être assez similaire, bien que le support d'encodage est quelque peu différente en Java.
En gros, c'est pas vraiment chose facile à faire en général. Comme MSalter points, UTF-8, il est facile de repérer \r
ou \n
comme l'UTF-8 est une représentation de ces personnages est la même chose que de l'ASCII, et ces octets ne se produira pas dans les caractères multi-octets.
Donc, fondamentalement, tenir un tampon de (dis) 2K, et progressivement une lecture à l'envers (passez à 2K avant vous étiez avant, lisez la prochaine 2K) vérification pour une terminaison de ligne. Passez ensuite à la bonne place dans le ruisseau, créer un InputStreamReader
sur le haut, et un BufferedReader
sur le dessus de cela. Puis il suffit d'appeler BufferedReader.readLine()
.
L'utilisation de FileReader ou FileInputStream ne fonctionnera pas - vous devrez utiliser FileChannel ou RandomAccessFile pour parcourir le fichier en arrière à partir de la fin. Les encodages seront un problème cependant, comme l'a dit Jon.
En C # , vous devriez pouvoir définir la position du flux:
De: http://bytes.com/groups/net-c/269090-streamreader-read-last-line-text-file
using(FileStream fs = File.OpenRead("c:\\file.dat"))
{
using(StreamReader sr = new StreamReader(fs))
{
sr.BaseStream.Position = fs.Length - 4;
if(sr.ReadToEnd() == "DONE")
// match
}
}