Каков самый быстрый и эффективный способ чтения последней строки текста из [очень, очень большого] файла на Java?Быстро читает последнюю строку текстового файла?
ответ
Посмотрите мой ответ на вопрос similar question for C#. Код был бы очень похож, хотя поддержка кодирования несколько отличается в Java.
В принципе, это не очень простая вещь в целом. Как указывает MSalter, UTF-8 позволяет легко определить \r
или \n
, поскольку представление этих символов UTF-8 аналогично ASCII, и эти байты не будут встречаться в многобайтовом символе.
Итак, возьмите буфер (скажем) 2K и прогрессивно прочитайте назад (пропустите до 2K до того, как вы были до этого, прочитайте следующий 2K), проверив о завершении линии. Затем перейдите в нужное место в потоке, создайте на верхнем конце InputStreamReader
и сверху на нем BufferedReader
. Затем просто позвоните BufferedReader.readLine()
.
В C#, вы должны быть в состоянии установить положение ручья:
От: 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
}
}
В Java FileInputStream (на котором основан FileReader) вы не можете установить позицию; вы можете только пропустить вперед, что, вероятно, не читает те части, которые вы пропускаете, но все равно односторонняя операция и, следовательно, не подходит для поиска строки с неизвестным смещением с конца. –
Ну ... Я попытался: P – rball
Вы можете использовать mark(), чтобы обойти эту проблему, в зависимости от того, что такое streamLimit(). –
Использование FileReader или FileInputStream не будет работать - вам придется использовать либо FileChannel или RandomAccessFile для прокрутки файла назад с конца. Кодировки будут проблемой, хотя, как сказал Джон.
Примечание. Производительность RandomAccessFile отстойна для отдельных операций - поэтому разумный размер считывается в буфер. –
Ниже перечислены две функции: одна, которая возвращает последнюю непустую строку файла без загрузки или прохода по всему файлу, а другая, которая возвращает последние N строк файла без прохождения по всему файлу:
Какой хвост имеет зум прямо к последнему символу файла, затем шаг назад, символ по символу, записывая то, что он видит, пока не найдет разрыв строки. Как только он находит разрыв строки, он выходит из цикла. Обращает внимание на то, что было записано, и вставляет его в строку и возвращается. 0xA - новая строка, а 0xD - возврат каретки.
Если ваши строки заканчиваются \r\n
или crlf
или какой-либо другой «новой чертой стиля новой строки», вам нужно будет указать n * 2 строки, чтобы получить последние n строк, потому что они подсчитывают 2 строки для каждой строки.
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 */
}
}
}
Но вы, вероятно, не хотите последнюю строку, вы хотите, чтобы последние N строк, поэтому используйте вместо этого:
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) {
if (filePointer < fileLength) {
line = line + 1;
}
} else if(readByte == 0xD) {
if (filePointer < fileLength-1) {
line = line + 1;
}
}
if (line >= lines) {
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) {
}
}
}
Invoke вышеуказанные методы, как это:
File file = new File("D:\\stuff\\huge.log");
System.out.println(tail(file));
System.out.println(tail2(file, 10));
Предупреждение В дикой природе к западу от юникода, этот код может привести к ошибке выхода этой функции. Например, «Мэри» вместо «Мэри». Символы с hats, accents, Chinese characters и т. Д. Могут привести к неправильному результату, поскольку акценты добавляются в качестве модификаторов после символа. Обратные составные символы изменяют характер идентичности персонажа при развороте. Вам нужно будет выполнить полную проверку тестов на всех языках, на которых вы планируете использовать это.
Для получения дополнительной информации об этой проблеме реверсирования юникода прочитать: http://msmvps.com/blogs/jon_skeet/archive/2009/11/02/omg-ponies-aka-humanity-epic-fail.aspx
Вышеупомянутое не учитывает линии, завершающиеся как CR, так и LF. – Jags
ваша многострочная реализация не работает в ваших особых случаях filePointer == fileLength, строка останется неизменной, поэтому строка строки состояния == не будет запускаться после этого, и код будет читать весь файл. – ZPiDER
Вы можете легко изменить код, приведенный ниже, чтобы напечатать последнюю строку.
для печати отображение файла в память последние 5 строк:
private static void printByMemoryMappedFile(File file) throws FileNotFoundException, IOException{
FileInputStream fileInputStream=new FileInputStream(file);
FileChannel channel=fileInputStream.getChannel();
ByteBuffer buffer=channel.map(FileChannel.MapMode.READ_ONLY, 0, channel.size());
buffer.position((int)channel.size());
int count=0;
StringBuilder builder=new StringBuilder();
for(long i=channel.size()-1;i>=0;i--){
char c=(char)buffer.get((int)i);
builder.append(c);
if(c=='\n'){
if(count==5)break;
count++;
builder.reverse();
System.out.println(builder.toString());
builder=null;
builder=new StringBuilder();
}
}
channel.close();
}
RandomAccessFile печатать последние 5 строк:
private static void printByRandomAcessFile(File file) throws FileNotFoundException, IOException{
RandomAccessFile randomAccessFile = new RandomAccessFile(file, "r");
int lines = 0;
StringBuilder builder = new StringBuilder();
long length = file.length();
length--;
randomAccessFile.seek(length);
for(long seek = length; seek >= 0; --seek){
randomAccessFile.seek(seek);
char c = (char)randomAccessFile.read();
builder.append(c);
if(c == '\n'){
builder = builder.reverse();
System.out.println(builder.toString());
lines++;
builder = null;
builder = new StringBuilder();
if (lines == 5){
break;
}
}
}
}
Apache Commons имеет реализацию с помощью RandomAccessFile.
Это называется ReversedLinesFileReader.
Я думаю, что это самый быстрый способ прочитать файл в обратном порядке –
кажется, что он не читает n строк, как принятый ответ. – JuanToroMarty
@JuanToroMarty Можно использовать цикл 'readLine()'. – Stephan
Это способ я сделал это :) Надеюсь, что помогает
try(BufferedReader reader = new BufferedReader(new FileReader(reqFile))){
String line = null;
System.out.println("======================================");
line = reader.readLine(); //Read Line ONE
line = reader.readLine(); //Read Line TWO
System.out.println("first line : " + line);
//Length of one line if lines are of even length
int len = line.length();
//skip to the end - 3 lines
reader.skip((reqFile.length() - (len*3)));
//Searched to the last line for the date I was looking for.
while((line = reader.readLine()) != null){
System.out.println("FROM LINE : " + line);
String date = line.substring(0,line.indexOf(","));
System.out.println("DATE : " + date); //BAM!!!!!!!!!!!!!!
}
System.out.println(reqFile.getName() + " Read(" + reqFile.length()/(1000) + "KB)");
System.out.println("======================================");
} catch (IOException x){
x.printStackTrace();
}
UTF-8 не имеет значения - вам нужен последний CR или LF символ, который представляет собой один байт в обоих ASCII и UTF -8. – MSalters
@MSalters: Хорошая точка. Будет обновлено ... –