A faster way to read lines in text files quickly - vb.net

My application is looking at huge text files (upwards to half a million lines) from a proxy server log. The problem is that a normal StreamRead iteration of the logs can take an excessive amount of time to process, so I'm looking for something faster.
On the form, the user picks the file they need to parse and enters up to three site filters to check for. The application then opens the file and begins to parse the date stamp and website URL from each line in the file. The average speed is about two lines per second, so for a file with 200,000 lines in it, this process will take about 28 hours to process a file.
I've been reading on the Task class, and I'm thinking this would probably be the route to take, but Microsoft doesn't give a very good example, so how can I can accomplish it?

I think you could use File.ReadLines() when reading large files.
According to MSDN :
The ReadLines and ReadAllLines methods differ as follows: When you use ReadLines, you can start enumerating the collection of strings before the whole collection is returned; when you use ReadAllLines, you must wait for the whole array of strings be returned before you can access the array. Therefore, when you are working with very large files, ReadLines can be more efficient.
For more detail, see MSDN File.ReadLines()

Instead of guessing about why it is slow, is it reading the file, processing the lines, etc. start by measuring how long it takes to read the file line-by-line.
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim stpw As New Stopwatch
Dim path As String = "path to your file here"
Dim sr As New IO.StreamReader(path)
Dim linect As Integer = 0
stpw.Restart()
Do While Not sr.EndOfStream
Dim s As String = sr.ReadLine
linect += 1
Loop
stpw.Stop()
sr.Close()
Debug.WriteLine(stpw.Elapsed.ToString)
Debug.WriteLine(linect)
End Sub
I ran this against a test file I have that is 20MB. It is close to 3,000,000 lines long(the lines are very short). It took about .3 of a second to run.
After you run this you will know whether the problem is the read or the processing, or both.

Thanks, dbasnett... the results were:
00:00:00.6991336
172900
Believe it or not, I found the problem. I had the textbox inside a GroupBox and was using the GroupBox.Text property to update statistics back to the user, using GroupBox.Refresh() to update the line x of y and matches found, etc. so the user had some idea of what was being found.
By leaving that information out and putting in a progress bar, the speed of the scans went up exponentially. Using 3 filters, I was able to parse 172900 lines in a matter of 3:19 minutes:
Scan complete!
Process complete!
Scanned 172900 lines out of 172900 lines.
Percentage (icc): 0.0052% (900 matches)
Percentage (facebook): 0.0057% (988 matches)
Percentage (illinois): 0.0005% (95 matches)
Total Matches: 1983
Elapsed Time: 00:03:19.1088851

Related

Read of text file never being released from memory

I'm using .NET Framework 4.6.2 (VB) for a Windows Service. I'm using NLog to write a log file without issue. I'm now adding a log viewer utility which will show the last 100 lines of the log file. I've used various methods to read the file but can't seem to escape the reality that I eventually need to iterate through the entire file to get to the lines that I need. That's not a problem.
Where I'm having an issue is that after I've finished reading the file it NEVER seems to get released from memory. When I start my application, it's using approximately 16MB of memory. After the read (of an at most 10MB file) it's using around 38.5MB. Even doing things like clearing the List(Of String) or a forced Garbage Collection is never fully releasing the memory.
I'm using probably the simplest version of a read:
Dim LogEntries As List(Of String) = System.IO.File.ReadLines(LogFile).ToList()
LogEntries.Clear()
I am performing other tasks between the ReadLines and LogEntries.Clear() steps, but the issue is present even if I use only the lines shown above.
I would expect that on clearing the LogEntries list would return the memory usage to approximately 16MB, but the lowest I've been able to get it (after a GC.Collect()) is about 22MB. Can anyone explain this to me?
The whole point of calling ReadLines is that it doesn't read every line at the same time. If you then call ToList on the result then you force it to wait until all lines are read. That's silly. If you want the last 100 lines then you have no choice but to read the whole lot but there's no point keeping it all.
Dim lines = File.ReadAllLines(filePath)
lines = lines.Skip(lines.Length - 100).ToArray()
The first line reads the entire file into a String array and then the second line creates a second array containing just the last 100 elements and discards the first array.
Another option that would reduce memory consumption at the expense of performance would be this:
Dim lines As New List(Of String)
Using reader As New StreamReader(filePath)
Do Until reader.EndOfStream
lines.Add(reader.ReadLine())
If lines.Count > 100 Then
lines.RemoveAt(0)
End If
Loop
End Using

Why do I get stack overflow BEFORE all the possible combinations have been reached?

So.
I am making a bruteforcer in visual basic.
It has a charset as shown below:
Dim charset as string
charset = "abcdefghijklmnopqrstuvwxyz1234567890."
There is 37 different chars right? The program is made to search for all the different combinations made with this charset, with a maximum of 3 different letters. For example
This is a combination that can be made: ac6
So since there is 37 letters and 3 slots the possible combinations are 37^3
But I wanted my program not to try the same combination twice.
So it saves every single combination tried in this location (Desktop)
Dim filex As System.IO.StreamWriter
filex = My.Computer.FileSystem.OpenTextFileWriter("c:\Users\" + Environment.UserName + "\desktop\alreadytested.txt", True)
filex.WriteLine(combination)
filex.Close()
And, at the start of the Sub that checks for new combinations, I have this
text = File.ReadAllText("c:\Users\" + Environment.UserName + "\desktop\alreadytested.txt")
index = text.IndexOf(combination) 'checks if it has been generated already
If index >= 0 Then
keyword() 'The sub
End If
But after some combinations (in this case the max 37^3 ~= 50.000 and I the program tried around 5200 times) I get this error
An unhandled exception of type 'System.StackOverflowException' occurred in mscorlib.dll
And this error points in this line of code
filex = My.Computer.FileSystem.OpenTextFileWriter("c:\Users\" + Environment.UserName + "\desktop\alreadytested.txt", True)
But why? at 5200 there is still 44800 possible random combinations, why do I get an overflow?
It would make sense if I got it when I had 50000 combinations out of 50000 possible tested, but now I have 10% only, so why do I get an overflow?
You keep on recursively calling the keyword() method. Every time you call a method its return address and possibly its arguments is/are added to the call stack. The callstack can only take a certain amount of calls before it overflows, and for your computer and that specific method of yours, that amount seems to be about 5200.
You should switch to using for example a While-loop instead, and whenever you want to block the rest of the execution and go back to the beginning of the loop you'd just call Continue While.
A little side note is also that you shouldn't open and close the file every time you read/write to it. Store the contents of the file in a long string (or even better, in a HashSet(Of T)) instead and check that every time you need to, then at the end of the loop you may write all the contents to a file.
If you still wish to write to the file during the process then do so. But instead open a stream before your loop which you keep writing to until the loop is finished, then close the stream.

VB.NET (2013) - Check string against huge file

I have a text file that is 125Mb in size, it contains 2.2 million records. I have another text file which doesn't match the original but I need to find out where it differs. Normally, with a smaller file I would read each line and process it in some way, or read the whole file into a string and do likewise, however the two files are too big for that and so I would like to create something to achieve my goal. Here's what I currently have.. excuse the mess of it.
Private Sub refUpdateBtn_Click(sender As Object, e As EventArgs) Handles refUpdateBtn.Click
Dim refOrig As String = refOriginalText.Text 'Original Reference File
Dim refLatest As String = refLatestText.Text 'Latest Reference
Dim srOriginal As StreamReader = New StreamReader(refOrig) 'start stream of original file
Dim srLatest As StreamReader = New StreamReader(refLatest) 'start stream of latest file
Dim recOrig, recLatest, baseDIR, parentDIR, recOutFile As String
baseDIR = vb.Left(refOrig, InStrRev(refOrig, ".ref") - 1) 'find parent folder
parentDIR = Path.GetDirectoryName(baseDIR) & "\"
recOutFile = parentDIR & "Updated.ref"
Me.Text = "Processing Reference File..." 'update the application
Update()
If Not File.Exists(recOutFile) Then
FileOpen(55, recOutFile, OpenMode.Append)
FileClose(55)
End If
Dim x As Integer = 0
Do While srLatest.Peek() > -1
Application.DoEvents()
recLatest = srLatest.ReadLine
recOrig = srOriginal.ReadLine ' check the original reference file
Do
If Not recLatest.Equals(recOrig) Then
recOrig = srOriginal.ReadLine
Else
FileOpen(55, recOutFile, OpenMode.Append)
Print(55, recLatest & Environment.NewLine)
FileClose(55)
x += 1
count.Text = "Record No: " & x
count.Refresh()
srOriginal.BaseStream.Seek(0, SeekOrigin.Begin)
GoTo 1
End If
Loop
1:
Loop
srLatest.Close()
srOriginal.Close()
FileClose(55)
End Sub
It's got poor programming and scary loops, but that's because I'm not a professional coder, just a guy trying to make his life easier.
Currently, this uses a form to insert the original file and the latest file and outputs each line that matches into a new file. This is less than perfect, but I don't know how to cope with the large file sizes as streamreader.readtoend crashes the program. I also don't need the output to be a copy of the latest input, but I don't know how to only output the records it doesn't find. Here's a sample of the records each file has:
doc:ARCHIVE.346CCBD3B06711E0B40E00163505A2EF
doc:ARCHIVE.346CE683B29811E0A06200163505A2EF
doc:ARCHIVE.346CEB15A91711E09E8900163505A2EF
doc:ARCHIVE.346CEC6AAA6411E0BEBB00163505A2EF
The program I have currently works... to a fashion, however I know there are better ways of doing it and I'm sure much better ways of using the CPU and memory, but I don't know this level of programming. All I would like is for you to take a look and offer your best answers to all or some of the code. Tell me what you think will make it better, what will help with one line, or all of it. I have no time limit on this because the code works, albeit slowly, I would just like someone to tell me where my code could be better and what I could do to get round the huge file sizes.
Your code is slow because it is doing a lot of file IO. You're on the right track by reading one line at a time, but this can be improved.
Firstly, I've created some test files based off the data that you provided. Those files contain three million lines and are about 130 MB in size (2.2 million records was less than 100 MB so I've increased the number of lines to get to the file size that you state).
Reading the entire file into a single string uses up about 600 MB of memory. Do this with two files (which I assume you were doing) and you have over 1GB of memory used, which may have been causing the crash (you don't say what error was shown, if any, when the crash occurred, so I can only assume that it was an OutOfMemoryException).
Here's a few tips before I go through your code:
Use Using Blocks
This won't help with performance, but it does make your code cleaner and easier to read.
Whenever you're dealing with a file (or anything that implements the IDisposable interface), it's always a good idea to use a Using statement. This will automatically dispose of the file (which closes the file), even if an error happens.
Don't use FileOpen
The FileOpen method is outdated (and even stated as being slow in its documentation). There are better alternatives that you are already (almost) using: StreamWriter (the cousin of StreamReader).
Opening and closing a file two million times (like you are doing inside your loop) won't be fast. This can be improved by opening the file once outside the loop.
DoEvents() is evil!
DoEvents is a legacy method from back in the VB6 days, and it's something that you really want to avoid, especially when you're calling it two million times in a loop!
The alternative is to perform all of your file processing on a separate thread so that your UI is still responsive.
Using a separate thread here is probably overkill, and there are a number of intricacies that you need to be aware of, so I have not used a separate thread in the code below.
So let's look at each part of your code and see what we can improve.
Creating the output file
You're almost right here, but you're doing some things that you don't need to do. GetDirectoryName works with file names, so there's no need to remove the extension from the original file name first. You can also use the Path.Combine method to combine a directory and file name.
recOutFile = Path.Combine(Path.GetDirectoryName(refOrig), "Updated.ref")
Reading the files
Since you're looping through each line in the "latest" file and finding a match in the "original" file, you can continue to read one line at a time from the "latest" file.
But instead of reading a line at a time from the "original" file, then seeking back to the start when you find a match, you will be better off reading all of those lines into memory.
Now, instead of reading the entire file into memory (which took up 600 MB as I mentioned earlier), you can read each line of the file into an array. This will use up less memory, and is quite easy to do thanks to the File class.
originalLines = File.ReadAllLines(refOrig)
This reads all of the lines from the file and returns a String array. Searching through this array for matches will be slow, so instead of reading into an array, we can read into a HashSet(Of String). This will use up a bit more memory, but it will be much faster to seach through.
originalLines = New HashSet(Of String)(File.ReadAllLines(refOrig))
Searching for matches
Since we now have all of the lines from the "original" line in an array or HashSet, searching for a line is very easy.
originalLines.Contains(recLatest)
Putting it all together
So let's put all of this together:
Private Sub refUpdateBtn_Click(sender As Object, e As EventArgs)
Dim refOrig As String
Dim refLatest As String
Dim recOutFile As String
Dim originalLines As HashSet(Of String)
refOrig = refOriginalText.Text 'Original Reference File
refLatest = refLatestText.Text 'Latest Reference
recOutFile = Path.Combine(Path.GetDirectoryName(refOrig), "Updated.ref")
Me.Text = "Processing Reference File..." 'update the application
Update()
originalLines = New HashSet(Of String)(File.ReadAllLines(refOrig))
Using latest As New StreamReader(refLatest),
updated As New StreamWriter(recOutFile, True)
Do
Dim line As String
line = latest.ReadLine()
' ReadLine returns Nothing when it reaches the end of the file.
If line Is Nothing Then
Exit Do
End If
If originalLines.Contains(line) Then
updated.WriteLine(line)
End If
Loop
End Using
End Sub
This uses around 400 MB of memory and takes about 4 seconds to run.

vb.net storing large amounts of data

I need to store a large list of id's and load them into my .NET project the id's are like -
1:1:1:1
9:2:5:3
0:2:6:4
the list is roughly 15k lines.
ive tried storing the list online and reading it line by line but it takes 3-4 minutes just to get all the information, also tried downloading the text file and then reading it line by line but that's also incredibly slow.
can anyone suggest a better way ?
edit - code for reading from text file
Private Sub frmIkovItemID_Load(sender As Object, e As EventArgs) Handles MyBase.Load
Try
Dim fStream As New System.IO.FileStream("c:\proxy.txt", IO.FileMode.Open)
Dim sReader As New System.IO.StreamReader(fStream)
Do While sReader.Peek >= 0
itemIDs.Add(sReader.ReadLine)
Loop
Catch o As Exception
MsgBox(o.Message)
End Try
I need to store a large list of [...]
What you really mean is that you want to persist data.
Reading and writing in a file is a kind of persistence, yes, but a better solution would be to use a database. Especially if you are using a large amount of data. (Why use a database instead of just saving your data to disk?)
If you don't know how to use a database in vb.net, have a look at the Microsoft support page.

Loop takes forever with large count

This loop takes forever to run as the amount of items in the loop approach anything close to and over 1,000, close to like 10 minutes. This needs to run fast for amounts all the way up to like 30-40 thousand.
'Add all Loan Record Lines
Dim loans As List(Of String) = lar.CreateLoanLines()
Dim last As Integer = loans.Count - 1
For i = 0 To last
If i = last Then
s.Append(loans(i))
Else
s.AppendLine(loans(i))
End If
Next
s is a StringBuilder. The first line there
Dim loans As List(Of String) = lar.CreateLoanLines()
Runs in only a few seconds even with thousands of records. It's the actual loop that's taking a while.
How can this be optimized???
Set the initial capacity of your StringBuilder to a large value. (Ideally, large enough to contain the entire final string.) Like so:
s = new StringBuilder(loans.Count * averageExpectedStringSize)
If you don't specify a capacity, the builder will likely end up doing a large amount of internal reallocations, and this will kill performance.
You could take the special case out of the loop, so you wouldn't need to be checking it inside the loop. I would expect this to have almost no impact on performance, however.
For i = 0 To last - 1
s.AppendLine(loans(i))
Next
s.Append(loans(last))
Though, internally, the code is very similar, if you're using .NET 4, I'd consider replacing your method with a single call to String.Join:
Dim result as String = String.Join(Envionment.NewLine, lar.CreateLoanLines())
I can't see how the code you have pointed out could be slow unless:
The strings you are dealing with are huggggge (e.g. if the resulting string is 1 gigabyte).
You have another process running on your machine consuming all your clock cycles.
You haven't got enough memory in your machine.
Try stepping through the code line by line and check that the strings contain the data that you expect, and check Task Manager to see how much memory your application is using and how much free memory you have.
My guess would be that every time you're using append it's creating a new string. You seem to know how much memory you'll need, if you allocate all of the memory first and then just copy it into memory it should run much faster. Although I may be confused as to how vb.net works.
You could look at doing this another way.
Dim str As String = String.Join(Environment.NewLine, loans.ToArray)