注册 登录
编程论坛 VB6论坛

求教如何高效有效的处理巨大TXT文件?

ictest 发布于 2017-05-09 08:54, 4700 次点击
程序我已经写好,可以使用,能达到预期效果。该程序对TXT的文件处理方式是逐行读取和判断,然后删除,写入。
目前我遇到的问题是,需要处理的TXT文件太大,几十兆至上百兆,内容十几万行乃至几十或上百万行,这样处理的时间太长,至少需要7~8分钟乃至几十分钟,甚至报错死机,
求教各位前辈如何改写我的程序,或者说使用别的方法能够秒处理。听说有读入内存处理或者说数组处理的方法,但这些方法我都不会,只能死板的使用逐行读取、判断、删除、写入、循环的笨方法,这就导致程序效率极其低下。
求各位前辈帮助改写程序!这里深深拜谢了!
三个附件分别为程序源码、用于验证的大的数据文件、用于验证的小的数据文件。
只有本站会员才能查看附件,请 登录
只有本站会员才能查看附件,请 登录
只有本站会员才能查看附件,请 登录
17 回复
#2
ictest2017-05-09 17:00
还请路过的前辈和各位大神帮忙解决。
#3
风吹过b2017-05-09 18:33
看了一下你的程序,怎么说呢,
首先,VB 处理这文件就是慢。
其次,你代码未经任何优化,只是堆在一起。
例如,
1、每一个文件,需要打开4次,并且每次都需要完全读完。
   你第一次使用二进制读取,并且分好了行的数据,为啥不留给后面几次使用呢,而当作一次性数据就丢弃了。  
   在我电脑上,第一次打开大文件 ,需要 10 秒钟。你算一下,4次打开这就浪费了多少时间?

2、查找    Device#: 18 时,会遍类每一个字符,找到后也不退出,而是继续查找。
   你完全可以 查找该行是否存在 Device#: 这个关键字就可以了,使用 instr 函数。

3、很多地方直接使用控件值
   val(text1.text) 这使用,比
   T1txt=val(text1.text)
然后使用 T1txt 要慢非常多。建议你所有的控件关键属性,都保存到变量里,全部使用变量来完成操作,控件需要显示时,临时显示变量的内容。

4、代码重复太多
If Dir(Dir1.Path & "\" & Label71.Caption & ".bak") = "" Then
这个为条件判断,判断后文件不存在,就复制备份。然后后面的操作都是一样的,就不需要再分为二个段。而到备份完成后就结束判断。

5、split 函数 比 instr 函数要慢。
判断时,先使用 instr 函数,判断是否存在指定的数据,存在再调用 split 函数,而不应该一股垴的都调用 split 函数。

----------------
对文件操作最快的方式就是 内存映射文件,这个你在另一个贴子里贴了,我对这一块也不熟,就不多说了。

好吧,才看半个函数,没时间了。
自己多研究一下。

#4
ictest2017-05-09 20:07
一针见血啊!一个“堆”字点出了我写程序的真实水准。拜服。
您提出的意见我真心接受,我叫针对您提出的问题对我的程序进行优化。谢谢您!
由于数据文件文件实在是真心的大,处理时间估计最多能省掉1/3,还能快点么?因为处理那个大的数据文件需要45分钟左右,优化后还是需要半个小时。或者还有更快的方法?
内存映射文件方式重来没有见识和使用过,另一个帖子还请前辈回复一下,教教我使用。
#5
ictest2017-05-09 21:10
一针见血啊!一个“堆”字点出了我写程序的真实水准。拜服。
您提出的意见我真心接受,我叫针对您提出的问题对我的程序进行优化。谢谢您!
由于数据文件文件实在是真心的大,处理时间估计最多能省掉1/3,还能快点么?因为处理那个大的数据文件需要45分钟左右,优化后还是需要半个小时。或者还有更快的方法?
内存映射文件方式重来没有见识和使用过,另一个帖子还请前辈回复一下,教教我使用。
#6
风吹过b2017-05-09 23:20
没看懂你的后面 的统计函数。
前面一部分:
程序代码:
Option Explicit

Public Const DeviceStr = "Device#:"
Public Const DeviceStrTitle = " Number     Site  Result   Test Name                 Pin   Channel   Low            Measured       High           Force          Loc"

Public Const SiteBin = " Site    Sort     Bin"
Public Const Sitetests = " Site Failed tests/Executed tests"

Public Const Device_F = "========================================================================="
Public Const site_F = "------------------------------------"


Public Type DeviceType
    Device          As String       'Device
    DeviceS         As Long         '所处行数
    SiteBinS        As Long
    SitetestsS      As Long
End Type



Public Type FileTYpe
   
    '文件属性
    Name        As String
    Path        As String
    PathName    As String
    Size        As Long
    DateTime    As String

    data()          As String   '文件数据
    Datacount       As Long     '总行数
   
    '文件头,具体名字,参考 FileT 数组定义
    FileT(9)        As String

    '数据组数
    Count           As Long
    '数据
    Device()          As DeviceType
End Type


Public PF As FileTYpe
Public FileT(9) As String



Public Sub 文件解析()           '不传参数,填充全局变量 PF
'
不负责备份文件

Dim i As Long, j As Long
Dim k As Long, tmp1 As String

Dim fr As Long

fr = FreeFile

With PF
    .PathName = .Path & "\" & .Name               '文件名及全路径
    .DateTime = FileDateTime(.PathName)         '文件日期
    .Size = FileLen(.PathName)      '文件大小
   
    Open .PathName For Input As #fr                   '打开文件
        tmp1 = Input(LOF(fr), #fr)
        '.data = Split((Input(LOF(2), #2)), vbCrLf)       '一次性读入内存
        .data = Split(tmp1, vbCrLf)
    Close #fr
    .Datacount = UBound(.data)
    tmp1 = ""                               '清掉内存
   
.Count = 0

For j = 0 To UBound(.data)

    For i = 0 To 8
        k = InStr(1, .data(j), FileT(i))
        If k > 0 Then
            tmp1 = Trim(Mid(.data(j), k + Len(FileT(i)) + 1))
            .FileT(i) = tmp1
            Exit For        '本次循环处理完毕
        End If
    Next i
   
    If InStr(1, .data(j), FileT(9)) > 0 Then  'Site Number 数据在下一行
        tmp1 = Trim(.data(j + 1))
        .FileT(9) = tmp1
    End If
   
    If InStr(1, .data(j), DeviceStr) > 0 Then        'Device#:
        .Count = .Count + 1
    End If
   
Next j

i = 0
ReDim .Device(.Count)
For j = 0 To UBound(.data)
    k = InStr(1, .data(j), DeviceStr)
    If k > 0 Then        'Device#:
        i = i + 1
        .Device(i).DeviceS = j
        .Device(i).Device = Trim(Mid(.data(j), k + Len(DeviceStr)))
    End If
   
    k = InStr(1, .data(j), SiteBin)
    If k > 0 Then
        .Device(i).SiteBinS = j + 2
    End If
   
    k = InStr(1, .data(j), Sitetests)
    If k > 0 Then
        .Device(i).SitetestsS = j + 2
    End If
Next j
End With

End Sub

#7
风吹过b2017-05-09 23:22
程序代码:
Label11.Caption = "正在处理,请等候........."

PF.Name = File1.Filename
PF.Path = Dir1.Path
PF.PathName = PF.Path & "\" & PF.Name                  '文件名及全路径

If Dir(PF.PathName & ".bak") = "" Then
    BakYesNo = True         '备份文件不存在
    FileCopy PF.PathName, PF.PathName & ".bak"
Else
    BakYesNo = False        '备份文件存在
   
End If
   

Call 文件解析

    Label71.Caption = PF.Name          '显示文件名

    Label2.Caption = PF.PathName                '文件名及全路径
    Label8.Caption = PF.DateTime          '文件日期
    Label10.Caption = Format(PF.Size / 1024, "0.00") & " K 字节"       '文件大小
    Label6.Caption = "共有 " & PF.Datacount + 1 & " 行数据。"             '显示行数

For i = 0 To 9
    Label53(i).Caption = PF.FileT(i)
Next i


Label4.Caption = "共有 " & PF.Count & " 个数据"


从这个向后,不知道你的统计算法。无法继续。
备份文件的信息还没处理

整个文件都被拆了,然后分门别类的进行起始位置统计了。放在 PF 这个结构体内。

#8
风吹过b2017-05-09 23:24
程序代码:
'文件头,对应的是 Label53 个各字段的顺序
FileT(0) = "Prog Name:"
FileT(1) = "Job Name:"
FileT(2) = "Lot:"
FileT(3) = "Operator:"
FileT(4) = "Test Mode:"
FileT(5) = "Node Name:"
FileT(6) = "Part Type:"
FileT(7) = "Channel map:"
FileT(8) = "Environment:"
FileT(9) = "Site Number:"


label53-62,改成控件数组。
#9
ictest2017-05-10 12:57
关于我程序后面的统计,如下的一张图我想能够说明了:
只有本站会员才能查看附件,请 登录
只有本站会员才能查看附件,请 登录


[此贴子已经被作者于2017-5-10 12:59编辑过]

#10
风吹过b2017-05-10 20:42
程序代码:
Option Explicit

Public Const DeviceStr = "Device#:"
Public Const DeviceStrTitle = " Number     Site  Result   Test Name                 Pin   Channel   Low            Measured       High           Force          Loc"

Public Const SiteBin = " Site    Sort     Bin"
Public Const Sitetests = " Site Failed tests/Executed tests"

Public Const Device_F = "========================================================================="
Public Const site_F = "------------------------------------"

Public Enum PassFaiL
    PASS = 1
    FAIL = 2
End Enum

Public Enum 清显示范围
    全部 = 0
    分析 = 1
    删除 = 2
End Enum



Public Type DeviceType
    Device          As String       'Device
    DeviceS         As Long         '所处行数
    Sitetests       As Long
    SitetestsS      As Long
    SiteBin         As Long
    SiteBinS        As Long
    PF              As PassFaiL     '这段是 pass 还是 Fail
End Type


Public Type FileTYpe
   
    '文件属性
    Name        As String
    Path        As String
    PathName    As String
    Size        As Long
    DateTime    As String

    data()          As String   '文件数据
    Datacount       As Long     '总行数
   
    '文件头,具体名字,参考 FileT 数组定义
    FileT(9)        As String

    '数据组数
    Count           As Long
    '数据
    Device()        As DeviceType
    PASS            As Long      '统计
    FAIL            As Long
End Type


Public PF As FileTYpe
Public FileT(9) As String



Public Sub 文件解析()           '不传参数,填充全局变量 PF
'
不负责备份文件

Dim i As Long, j As Long
Dim k As Long, tmp1 As String

Dim fr As Long

fr = FreeFile

With PF

    .Datacount = 0
    .Count = 0
    .FAIL = 0
    .PASS = 0

    .PathName = .Path & "\" & .Name               '文件名及全路径
    .DateTime = FileDateTime(.PathName)         '文件日期
    .Size = FileLen(.PathName)      '文件大小
   
    '预处理完成
   
    Call Form1.进度条(0.05, "准备打开文件..")
   
    Open .PathName For Input As #fr                   '打开文件
        tmp1 = Input(LOF(fr), #fr)                       '一次性读入内存
        
        Call Form1.进度条(0.1, "打开文件完成,准备分行")
        
        .data = Split(tmp1, vbCrLf)
        
        Call Form1.进度条(0.15, "分行完成,开始分析")
    Close #fr
    .Datacount = UBound(.data)
    tmp1 = ""                               '清掉内存
   
.Count = 0

    Call Form1.显示日志("正在分析步骤一..")
For j = 0 To .Datacount
   
    If j Mod 10 = 0 Then        '每处理10个变化一次进度条
        Call Form1.进度条(0.15 + j / .Datacount * 0.43)
    End If

    For i = 0 To 8
        k = InStr(1, .data(j), FileT(i))
        If k > 0 Then
            tmp1 = Trim(Mid(.data(j), k + Len(FileT(i)) + 1))
            .FileT(i) = tmp1
            Exit For        '本次循环处理完毕
        End If
    Next i
   
    If InStr(1, .data(j), FileT(9)) > 0 Then  'Site Number 数据在下一行
        tmp1 = Trim(.data(j + 1))
        .FileT(9) = tmp1
    End If
   
    If InStr(1, .data(j), DeviceStr) > 0 Then        'Device#:
        .Count = .Count + 1
    End If
   
Next j


    Call Form1.显示日志("正在分析步骤二..")

i = 0
ReDim .Device(.Count)
For j = 0 To .Datacount

    If j Mod 10 = 0 Then        '每处理10个变化一次进度条
        Call Form1.进度条(0.58 + j / .Datacount * 0.4)
    End If

    k = InStr(1, .data(j), DeviceStr)
    If k > 0 Then        'Device#:
        i = i + 1
        .Device(i).DeviceS = j
        .Device(i).Device = Trim(Mid(.data(j), k + Len(DeviceStr)))
    End If
   
    k = InStr(1, .data(j), SiteBin)
    If k > 0 Then
        .Device(i).SiteBinS = j + 2
        '解析SiteBin
        .Device(i).SiteBin = Mid(.data(j + 2), 24, 8)
    End If
   
    k = InStr(1, .data(j), Sitetests)
    If k > 0 Then
        .Device(i).SitetestsS = j + 2
        '解析Sitetest
        .Device(i).SiteBin = Val(Mid(.data(j + 2), 14, 8))
    End If
Next j

    Call Form1.显示日志("正在分析步骤三..")

'解析完需要统计
For j = 1 To .Count

    If j Mod 10 = 0 Then        '每处理10个变化一次进度条
        Call Form1.进度条(0.98 + j / .Count * 0.02)
    End If


    If .Device(j).SiteBin = 1 And .Device(j).Sitetests = 0 Then
        .Device(j).PF = PASS
        .PASS = .PASS + 1
    Else
        .Device(j).PF = FAIL
        .FAIL = .FAIL + 1
    End If
Next j


End With
   
    Call Form1.显示日志("文件解析完成")

End Sub


Public Sub 保存Pass()

'Dim fn As String
Dim fr As Long
fr = FreeFile

Dim i As Long
Dim j As Long

Call Form1.进度条(0.01, "开始保存Pass数据")
Open (PF.Path & "\" & "Tmp-Pass.txt") For Output As fr

Call Form1.进度条(0.01, "写入文件头")
'先写入头
For j = 0 To PF.Device(1).DeviceS - 1
    Print #fr, PF.data(j)
Next j

Call Form1.进度条(0.05, "开始写入数据")
'再写入每节
For i = 1 To PF.Count
   
Call Form1.进度条(0.05 + i / PF.Count * 0.9)

    If PF.Device(i).PF = PASS Then
        For j = PF.Device(i).DeviceS To PF.Device(i).SiteBinS
            Print #fr, PF.data(j)
        Next j
        '写完后,写入分隔符
        Print #fr, Device_F
    End If
Next i
Close #fr
            
If Dir(PF.PathName & "-Pass.txt") <> "" Then
    Kill (PF.PathName & "-Pass.txt")
End If

Name (PF.Path & "\" & "Tmp-Pass.txt") As (PF.PathName & "-Pass.txt")

Call Form1.进度条(1, "保存数据完成!")

End Sub
#11
风吹过b2017-05-10 20:43
只有本站会员才能查看附件,请 登录
#12
风吹过b2017-05-10 20:44
只有本站会员才能查看附件,请 登录


删除失效,那些的框里应该填些什么值,真是不知道。看你的代码是填最后一个数据的值。
#13
风吹过b2017-05-10 20:48
最后 分析步骤三 的提示,建议去掉。

    Call Form1.显示日志("正在分析步骤三..")
这句干掉,怎么样,你自己想一想。 这里的进度条比例真不太好分布。
打开文件那二条命令,进度条是走不动,一定会跳的。那里执行是 非VB代码。


#14
ictest2017-05-11 17:21
大神!拜服!您的VB水平惊为天人!
学习代码中。。。。。。
头晕中。。。。。。。。
N多不懂中。。。。。。
还有两个问题求教:
1、如何删除指定的DEVICE#:号的数据段?
2、对于DEVICE#:号,还有需要修改号,改号方式如下,原文件名不变:
第一种方法:
一键修改:按一下按钮,原来乱序的NO:,被重新命名从NO:1——NO: n,(只修改NO行,其他行不变。)

第二种方法:
顺序修改:按“开始”键,出现第一个NO,例如NO: 19,在后面的一个文本框内输入一个数字,如5,该行就变成NO: 5;按“下一个”键,出现第一个NO,例如NO: 18,在后面的文本框内输入一个数字,如7,该行就变成NO: 7。。。。。。以此类推,直至该文本文件里的所有NO全部改完,如果文本框为空并点击“下一个”键,则文本文件中对应的NO值不变。注意:还要有“上一个”的功能。按保存键保存。

第三种方法:
跳跃修改:按“开始”键,两个文本框解除锁定,分别输入一个数字,前一个文本框里的数字用于寻找,寻找文本文件中NO:后面的数字;后边文本框里的数字是用于替换的。逻辑是,先在文本文件中的NO:后面查找前一个数字,如果不存在就提醒,如果存在就将后面一个数字在文本文件中替换前面找到的数字。。按保存键保存。


您曾经给过我一段程序,关于第一种和第二种的,程序如下:
程序代码:
Option Explicit

Dim No() As Long            '原始序号
Dim No2() As Long           '新序号
Dim NoIndex As Long         '当前显示的序号

Private Sub Command1_Click()

'方法一:一键修改,第一种实现方法
'
Call 顺序写入("C:\Users\CC\Desktop\记录.txt")

'方法一:第二种实现方法,可以删 顺序写入 代码,提高代码复用性
Call 读序号("C:\Users\CC\Desktop\记录.txt", No())
Dim i As Long
For i = 1 To UBound(No)
    No(i) = i           '按顺序排序号
Next i
Call 保存顺序("C:\Users\CC\Desktop\记录.txt", No())

End Sub

Private Sub Command2_Click()
'上一条
No2(NoIndex) = Val(Text1.Text)
Call 显示序号(-1)

End Sub

Private Sub Command3_Click()
'下一条
No2(NoIndex) = Val(Text1.Text)
Call 显示序号(1)

End Sub

Private Sub Command4_Click()
'方法二初始化程序
Call 读序号("C:\Users\CC\Desktop\记录.txt", No())
NoIndex = 1

ReDim No2(UBound(No))
Dim i As Long
For i = 1 To UBound(No)
    No2(i) = No(i)
Next i

Call 显示序号

End Sub

Private Sub Command5_Click()
'方法二和方法三保存结果
Call 保存顺序("C:\Users\CC\Desktop\记录.txt", No2())

End Sub



'---------------------------------------------------
Public Sub 顺序写入(Filename As String)
'文件保存,修改NO序号为顺序号
'
方法一的第一种实现方法

Dim i As Long
Dim h1 As Long, h2 As Long
Dim p As String
Dim s As String

p = App.Path
If Right(p, 1) <> "\" Then p = p & "\"

If Dir(Filename) = "" Then
    MsgBox "原文件不存在", vbCritical, "错误"
    Exit Sub
End If

h1 = FreeFile
Open Filename For Input As h1
h2 = FreeFile
Open p & "~tmp.txt" For Output As h2

Do While Not EOF(h1)
    Line Input #h1, s
    If Left(Trim(s), 4) = "NO: " Then           '复制你的,含空格??
        i = i + 1
        s = "NO: " & i
    End If
    Print #h2, s
Loop
Close #h1
Close #h2

Kill Filename                      '删旧文件
Name p & "~tmp.txt" As Filename    '重命名为目标文件,Name 可以在不同驱动器之间移动文件以完成重命名

MsgBox "完成"

End Sub

Public Sub 读序号(Filename As String, order() As Long)
'读文件里的顺序
'
使用一次性读文件的方式

If Dir(Filename) = "" Then
    MsgBox "原文件不存在", vbCritical, "错误"
    Exit Sub
End If

Dim i As Long, j As Long
Dim s As String
Dim h As Long

Dim m() As String
Dim n() As Long

h = FreeFile

    Open Filename For Binary As #h        '打开文件
        '直接读整个文件的所有的内容,按字节读,并转换为 Unicode 的VB默认字符串类型
        s = StrConv(InputB$(LOF(h), #h), vbUnicode)
    Close #h
   
m = Split(s, vbCrLf)        'Windows 平台,没做 Linux 平台兼容
For i = 0 To UBound(m)
    m(i) = Trim(m(i))
    If Left(m(i), 4) = "NO: " Then
        j = j + 1
    End If
Next i

ReDim order(j)
j = 0
For i = 0 To UBound(m)
    m(i) = Trim(m(i))
    If Left(m(i), 4) = "NO: " Then
        j = j + 1
        order(j) = Val(Mid(m(i), 5))
    End If
Next i
End Sub

Public Sub 显示序号(Optional 位移 As Long = 0)
On Error Resume Next
If IsError(UBound(No)) Then
    Exit Sub            '发现错误时不显示
End If

On Error GoTo 0

NoIndex = NoIndex + 位移
If NoIndex > UBound(No) Then
    NoIndex = 1
End If

If NoIndex < 1 Then
    NoIndex = UBound(No)
End If

Label1.Caption = No(NoIndex)            '原始值
Text1.Text = No2(NoIndex)               '修改后的值

End Sub

Public Sub 保存顺序(Filename As String, order() As Long)

Dim i As Long
Dim h1 As Long, h2 As Long
Dim p As String
Dim s As String

p = App.Path
If Right(p, 1) <> "\" Then p = p & "\"

If Dir(Filename) = "" Then
    MsgBox "原文件不存在", vbCritical, "错误"
    Exit Sub
End If

h1 = FreeFile
Open Filename For Input As h1
h2 = FreeFile
Open p & "~tmp.txt" For Output As h2

Do While Not EOF(h1)
    Line Input #h1, s
    If Left(Trim(s), 4) = "NO: " Then           '复制你的,含空格??
        i = i + 1
        s = "NO: " & order(i)                   '仅此与顺序文件不同
    End If
    Print #h2, s
Loop
Close #h1
Close #h2

Kill Filename                      '删旧文件
Name p & "~tmp.txt" As Filename    '重命名为目标文件,Name 可以在不同驱动器之间移动文件以完成重命名

MsgBox "完成"
End Sub


如何将这段程序无缝的连接入您这次写入的程序中?您这次写的程序简直是神来之笔,对我来说如同圣经一般,里面N多N多不懂得啊!!!
#15
ZHRXJR2017-05-11 19:14
你的这个程序我以前好像给别人做过,他的数据不是特别多,大概就几十K,用时不到10秒,你的这个文件比较大,要求与他的有点不太一样,不过原理是一样的。
抽时间看看原来做的,可能稍稍改动一下应该就可以,如果需要联系我。
#16
风吹过b2017-05-11 20:44
只有本站会员才能查看附件,请 登录


数据都解析好 ,只需把修改部分放进去就可以了。
然后保存部分稍改一下,把修改的数据保存一下就可以了。

只有本站会员才能查看附件,请 登录
#17
ictest2017-05-11 20:44
@ZHRXJR版主,谢谢你了,我的思路刚刚才开始跟上 风吹过b 版主大大的脚后跟,我先跟着 风吹过b 版主大大学习学习吧。另外您也看到了,我的这个软件 风吹过b 版主大大一直在跟着起帮助我,我先跟着他学会一种思路后有机会再跟您学习吧。我知道各位前辈都是高人,但每个人编程都有自己的习惯和风格,比如像我这个菜鸟,就喜欢“堆”(各种功能不经优化就直接放在一起),学习了太多风格反而更混乱要打架了。所以谢谢你啦。
#18
风吹过b2017-05-11 20:56
程序,首先要从基础做起,先规划出模块来
像你这个程序,你首先要规划好,需要整理什么数据,然后哪些数据可以一次性进行分析。

其次,程序需要划分功能
按规划出来的模块,划分具体的功能

第三,根据每个功能,规划实现代码,然后才是码代码。

第四,组装。根据界面的控件事件,把个各功能的组合起来。面对象编程,这步是最容易体现出来。

------------------
你打开大文件,共耗时多少?编译后执行。
我自己家里的电脑,需要用时 27 秒。  AMD 7800 + 8G + SSD
占用内存: 428,800K
好占内存啊,第二次写这么占内存的程序。

第一次占内存的程序,最大可以占用超过1G的内存。
1