在VB.NET中设置FileStream.Seek的位置/索引以检索“块”数据

4
我正在研究一种方法,该方法接收一个文本文件,并将该文件缩减至约10 MB。此方法用于截断日志文件并将其保持在10 MB限制内。
代码的逻辑基本上是这样的...如果文件大小为250 MB或更大,则读取字节直到数组达到250 MB。将其存储到StringBuilder中,设置下一次读取的位置并重复操作,直到StringBuilder包含约10 MB的数据。然后写入文件并擦除所有数据,仅留下最近写入的10 MB数据。
为了防止切割行,它会检查最后一个CrLf的位置,然后从该点向前写出所有数据。
我的问题是,我无法使seek在第一次读取后正确定位。它首先正确读取数据,然后当我使用上一次读取的位置进行下一次迭代时,它会“忽略”该位置并再次从文件开头读取。
If logFile.Length > (1024 * 1024 * 250) Then
    Dim DataToDelete As Integer = logFile.Length - (1024 * 1024 * 250)
    Dim ArrayIndex As Integer = 0
    While DataToDelete > 0
        Using fs As FileStream = New FileStream(logFile.FullName, FileMode.Open, FileAccess.ReadWrite)
            fs.Seek(ArrayIndex, SeekOrigin.Begin)
            If strBuilder.Length < (1024 * 1024 * 250) Then
                Dim bytes() As Byte = New Byte((1024 * 1024 * 250)) {}
                Dim n As Integer = fs.Read(bytes, 0, (1024 * 1024 * 250))
                ArrayIndex = bytes.Length
                Dim enc As Encoding = Encoding.UTF8
                strBuilder.Append(enc.GetString(bytes))
            Else
                If DataToDelete - strBuilder.Length < 0 And strBuilder.Length > (1024 * 1024 * My.Settings.Threshold) Then
                    Dim DataToCut As Integer = strBuilder.Length - (1024 * 1024 * My.Settings.Threshold)
                    While Not (strBuilder.Chars(DataToCut).ToString.Equals(vbCr)) And DataToCut <> 0
                        DataToCut -= 1
                    End While
                    strBuilder.Remove(0, DataToCut)
                    File.WriteAllText(logFile.FullName, strBuilder.ToString)
                Else
                    DataToDelete -= strBuilder.Length
                    strBuilder.Clear()
                End If
            End If
        End Using
    End While
End If

我每次运行程序都会出现内存不足的错误。导致错误的文件大小为550MB。字符串构建器的最大容量为2GB,所以我完全不知道为什么它无法容纳这个文件?虽然这不是我的高效想法,但你“可以”将所有行读入字符串构建器中,最多达到2GB。如果有关于内存不足的原因的想法,也请提供帮助! - user1732364
您的 File.WriteAllText() 调用将替换文件的内容。在 32 位 Windows 上无法获得如此大的数组,请搜索“地址空间碎片化”以了解原因。 - Hans Passant
2个回答

1

针对您正在进行的操作,将整个文件加载到内存中是不必要的,而且并不是一个好主意。最好的方法是只读取您想要保留的日志文件部分(例如最后10MB)。例如,执行以下操作会更简单和更有效:

Private Sub ShrinkLog(ByVal filePath As String, ByVal maxSize As Integer)
    Dim buffer As String
    If New FileInfo(filePath).Length > maxSize Then
        Using reader As New StreamReader(filePath)
            reader.BaseStream.Seek(-maxSize, SeekOrigin.End)
            buffer = reader.ReadToEnd()
        End Using
        File.WriteAllText(filePath, buffer)
    End If
End Sub

还有其他方法可以做到这一点。如果您要保留文件的较大部分,那么更有效的方法是不将所有内容加载到内存中,而是直接从一个流传输到另一个流。此外,这个简单的示例并没有展示如何避免在文件的某个位置截断一行,但我相信您可以一次寻找一个字节,直到找到第一个换行符。


哇,谢谢你的快速和简单的回复!!!实际上我一开始就是用这种方法,但在处理大文件时遇到了内存错误...我相当确定只是对象没有正确清除/释放。我知道我把它做得太难了,哈哈,我稍微改了一下代码,让它逐字节搜索直到找到第一个crlf(向文件末尾移动),然后从字符串中删除那些无用的字节,再将其写出。大约10mb的大小非常完美!向你致敬,我的朋友! - user1732364

0

这是我的最终结果,运行得非常好!

        Dim Maxsize As Integer = (1024 * 1024 * My.Settings.Threshold)
    For Each logfile In filesToTrim
        Dim sb As New StringBuilder
        Dim buffer As String = String.Empty
        If logfile.Length > Maxsize Then
            Using reader As New StreamReader(logfile.FullName)
                reader.BaseStream.Seek(-Maxsize, SeekOrigin.End)
                buffer = reader.ReadToEnd()
                sb.Append(buffer)
            End Using
            Dim Midpoint As Integer = 0
            While Not (sb.Chars(Midpoint).ToString.Equals(vbCr)) And Midpoint <> sb.Length - 1
                Midpoint += 1
            End While
            sb.Remove(0, Midpoint)
            File.WriteAllText(logfile.FullName, sb.ToString)
        End If
    Next

希望这个线程能够帮助其他人在尝试截断日志文件以保持特定大小时,我知道有软件可以做到这一点,但为什么要去折腾那些东西呢?当你可以编写一个简短的方法来完成它 :) - user1732364

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接