The most primitive way is what the luxury coders can do and that is
reading
the whole file split it and then loop in reverse order
however if you once processed files of gigagbytes in size then you
understand that this isn`t going to work in all situations your app will
blow up ( out of memory )
or it will become verry slow .
The following piece of code solves this problem as it only reads a peace
of
the end of the file and then split the lines
so it doesn`t loop through a large file it reads a chunk at the end and
then
splits it in a few lines determines the last one and returns this
'---------------------------------------------------------------------------------------
'-- Michel Posseth [MCP]
'-- Created on : 15-03-2009
'-- http:\\
www.vbdotnetcoder.com
'-- (e-mail address removed)
'---------------------------------------------------------------------------------------
Option Compare Binary
Option Explicit On
Option Strict On
Option Infer On
Imports System.IO
Imports System.Text
Public Class ClsReadTextFileReversed
Implements IDisposable
Private _FileToReadReverse As String
Public Property FileToReadReverse() As String
Get
If Not My.Computer.FileSystem.FileExists(_FileToReadReverse)
Then
Throw New ArgumentException("Property doesn`t contain file
path")
End If
Return _FileToReadReverse
End Get
Private Set(ByVal value As String)
If Not My.Computer.FileSystem.FileExists(value) Then
Throw New ArgumentException("File does not exist")
End If
_FileToReadReverse = value
End Set
End Property
Public Sub New(ByVal FullFilePath As String)
Me.FileToReadReverse = FullFilePath
End Sub
Private _Sr As StreamReader
Public Property Sr() As StreamReader
Get
Return _Sr
End Get
Private Set(ByVal value As StreamReader)
_Sr = value
End Set
End Property
Private _Fs As FileStream
Public Property Fs() As FileStream
Get
Return _Fs
End Get
Private Set(ByVal value As FileStream)
_Fs = value
End Set
End Property
Private _filesize As Long
Public Property Filesize() As Long
Get
Return _filesize
End Get
Private Set(ByVal value As Long)
_filesize = value
End Set
End Property
Private Function Init() As Boolean
Dim ret As Boolean
If Not Initialized Then
Try
Fs = New FileStream(FileToReadReverse, FileMode.Open,
FileAccess.Read, FileShare.Read)
Sr = New StreamReader(Fs, True)
Filesize = Sr.BaseStream.Length
Initialized = True
ret = True
Catch ex As Exception
RaiseEvent eException(ex)
ret = False
End Try
Else
ret = True
End If
Return ret
End Function
Public Event eException(ByVal ex As Exception)
Private _Initialized As Boolean
Public Property Initialized() As Boolean
Get
Return _Initialized
End Get
Private Set(ByVal value As Boolean)
_Initialized = value
End Set
End Property
Private _newline As String = Environment.NewLine
Public Property Newline() As String
Get
Return _newline
End Get
Set(ByVal value As String)
_newline = value
End Set
End Property
Private _SplitOption As StringSplitOptions =
StringSplitOptions.RemoveEmptyEntries
Public Property SplitOption() As StringSplitOptions
Get
Return _SplitOption
End Get
Set(ByVal value As StringSplitOptions)
_SplitOption = value
End Set
End Property
Public Function ReadLastLine() As String
Dim ret As String = String.Empty
If Init() Then
Dim buffersize As Long = 1024
buffersize = Math.Min(Filesize, buffersize)
Sr.BaseStream.Seek(-buffersize, SeekOrigin.End)
Dim text As String = Sr.ReadToEnd
Dim lines As String() = text.Split(New String() {Newline},
SplitOption)
Dim n As Integer = 4
If lines.Length <= n Then
If lines.Length < n Then
n = lines.Length
End If
If Filesize = buffersize + 1 Then
n -= 1
ElseIf Filesize >= buffersize + 2 Then
If lines(0) = "" OrElse lines(0)(0) <> ControlChars.Lf
Then
Sr.BaseStream.Seek(-buffersize - 2,
SeekOrigin.[End])
If Not (Sr.Read() = 13 AndAlso Sr.Read() = 10) Then
n -= 1
End If
Else
Sr.BaseStream.Seek(-buffersize - 1,
SeekOrigin.[End])
If Not (Sr.Read() = 13) Then
n -= 1
Else
lines(0) = lines(0).Substring(1)
End If
End If
End If
End If
Sr.Close()
Dim lastLines As String()
If n < lines.Length Then
lastLines = New String(n - 1) {}
If n > 0 Then
Array.Copy(lines, lines.Length - n, lastLines, 0, n)
End If
Else
lastLines = lines
End If
ret = lastLines(lastLines.Length - 1)
End If
Return ret
End Function
Public Sub Close()
If Sr IsNot Nothing Then
Sr.Close()
Sr.Dispose()
Sr = Nothing
End If
If Fs IsNot Nothing Then
Fs.Close()
Fs.Dispose()
Fs = Nothing
End If
End Sub
#Region " IDisposable Support "
Private disposedValue As Boolean = False ' To detect redundant
calls
' IDisposable
Protected Overridable Sub Dispose(ByVal disposing As Boolean)
If Not Me.disposedValue Then
If disposing Then
If Sr IsNot Nothing Then
Sr.Close()
Sr.Dispose()
Sr = Nothing
End If
If Fs IsNot Nothing Then
Fs.Close()
Fs.Dispose()
Fs = Nothing
End If
' TODO: free other state (managed objects).
End If
' TODO: free your own state (unmanaged objects).
' TODO: set large fields to null.
End If
Me.disposedValue = True
End Sub
' This code added by Visual Basic to correctly implement the disposable
pattern.
Public Sub Dispose() Implements IDisposable.Dispose
' Do not change this code. Put cleanup code in Dispose(ByVal
disposing As Boolean) above.
Dispose(True)
GC.SuppressFinalize(Me)
End Sub
#End Region
End Class
'
Tony Bansten said:
How can I read the last non-blank (=non whitespace) line from a text
file?
Ok, the most primitive way would be to start reading from the beginning
and then loop
through all the lines until the end.
But that is rather unconvenient and cumbersome.
Is there a smarter way?
Tony