忍者ブログ

テキストファイルを開くスピード

2020年03月21日
仕事でログの調査をするとき、
ログファイルのサイズが100MBあったりする。

otabeditで開けないこともないけど、
小さいファイルにまとめようと切り貼りしていると
メモリ不足の警告が出る。めんどくさ。


必要な情報は大したことないので、
欲しい内容をマクロで取り出そうかと。


今回は、本題に入る前にテキストファイルの読み込み速度について、
こんな記事をみつけて早速試してみた。


VBA テキストファイルの最も高速な読み込み方法

・OpenステートメントのBinaryモードにより一括バイナリ読み込み、Unicode変換が最も高速という結果になりました。
 まぁ当然ですね。配列化も簡単なので今後はこれをメインに使っていこう。

出展元
http://tetsucom.blogspot.com/2011/03/vba_9799.html



でかいテキストファイルがないので、
Dドライブの全ファイル、フォルダを書き出して作った。

コマンドプロンプトから
Dir /S D: > フルネーム.txt

ファイルのフルネームは、当該ファイルをコピーして
ファイル名を指定して実行のウィンドウにペーストすると出てくる。


出力結果は3Mぐらい。ちょっと小さめだけどまあいいか。

ついでにCドライブも試したら、10MB。


実行結果(10MB)
TextFileToBuf :2.0009765625
Line    :0.00390625
binary :0.154296875

実行結果(3MB)
TextFileToBuf :0.716796875
Line    :0.0009765625
binary :0.0654296875
記事に書いてあったバイナリよりも
1行ずつ読み込むLineInputのほうが圧倒的に早かった。むー。

【3/23追記】
54,618kBのファイルで再試験したところ、バイナリが最速でした。
TextFileToBuf :33.6
Line    :645.9
binary :6.2

以下、ソース。


Option Explicit
Sub Main()
    'ファイル選択ダイアログでファイルを指定
    Dim vFilePath As Variant, buf As String
    vFilePath = Application.GetOpenFilename
    Dim tictoc As Double
    tictoc = Timer
    buf = TextFileToBuf(vFilePath)
    Debug.Print "TextFileToBuf :" & Timer - tictoc
    tictoc = Timer
    buf = txtファイル読み込み_LineInput(vFilePath)
    Debug.Print "Line :" & Timer - tictoc
    tictoc = Timer
    buf = txtファイル読み込み_binary(vFilePath)
    Debug.Print "binary :" & Timer - tictoc
    
End Sub
Function ファイル名()
        Dim vFilePath As Variant
    vFilePath = Application.GetOpenFilename
ファイル名 = vFilePath
End Function
Function TextFileToBuf(FullName) As String
    Dim buf As String
    With CreateObject("Scripting.FileSystemObject")
        With .GetFile(FullName).OpenAsTextStream
            buf = .ReadAll
            .Close
        End With
    End With
    TextFileToBuf = buf
End Function
Function txtファイル読み込み_binary(vFilePath)
    If vFilePath = False Then
        txtファイル読み込み_binary = ""
    End If
    'ファイルサイズが0バイトの場合も処理終了
    Dim nFileLen As Long
    nFileLen = FileLen(vFilePath)
    If nFileLen = 0 Then
        txtファイル読み込み_binary = ""
    End If
    '指定されたファイルを取得したファイル番号としてバイナリモードで開く
    Open vFilePath For Binary As #1
    'ファイルサイズ分のバイト配列を用意
    Dim bData() As Byte
    ReDim bData(0 To nFileLen - 1)
    'バイト配列に指定ファイルを展開
    Get #1, , bData
    Close #1
    
    txtファイル読み込み_binary = StrConv(bData(), vbUnicode) 'Unicodeに変換
End Function
Function txtファイル読み込み_LineInput(vFilePath)
    If vFilePath = False Then
        txtファイル読み込み_LineInput = ""
    End If
    'ファイルサイズが0バイトの場合も処理終了
    Dim nFileLen As Long
    nFileLen = FileLen(vFilePath)
    If nFileLen = 0 Then
        txtファイル読み込み_LineInput = ""
    End If
    
    Dim buf As String, TextLine As String
    '指定されたファイルを取得したファイル番号としてバイナリモードで開く
    Open vFilePath For Input As #1
    Line Input #1, TextLine
        buf = buf & TextLine
    Close #1
    
    txtファイル読み込み_LineInput = buf
End Function

PR

条件付最大値

2020年03月21日
エクセルを使っていて条件付の最大値を計算したいことが何度かあった。
が、ピボットを使っても、SumifsやSumproduct関数を使ってもうまくいかず。

悶々としてましたが、今日、やり方を思いついたので備忘。

内容に入る前に、ネットで調べた。
「配列式を使う」なんてのを発見。

=MAX(IF(C3:C14="みかん",D3:D14)) と入力し、Shift+Ctrl+Enter で数式を確定します。

出展
http://www.eurus.dti.ne.jp/~yoneyama/Excel/waza/max.html


うまく計算できて、喜んだのもつかの間、
複数範囲まとめて計算できないのね。。。



で、思いついた方法は以下の手順。

1.元データが消えないようにシートをコピー

2.オートフィルタで最大値をとりたい列を降順に並べる

3.重複データの削除で、条件を指定するセルのみを指定


拍子抜けするくらい簡単にできた。
複数条件も、重複削除条件で指定するだけだから、簡単にできそう。


Grep結果をシート出力するマクロ

2020年02月29日
ファイルサイズの大きいテキストデータを扱うときにotbEditを使っている。

otbEdit(オタベエディタ)には、Grep機能があって、
特定の文字列を含む行をフォルダ内のテキストファイルから検索して一覧で出してくれる。

便利なんだけど、出力結果が扱いづらい形式で出てくるので、
エクセルに転記するマクロを作ってみた。

出力形式は以下のとおり。

ファイルフルネーム(行数):行内容


フルネームからフォルダ名、ファイル名、拡張子に分割して、
№をつけて出力。

クリップボードから読み込みとファイルから読み込みをできるようにした。


以下、ソース。

Option Explicit
Sub ◆oGrep結果の取り込み()
    Dim RC As Long
    RC = MsgBox("取り込み対象テキストの指定。クリップボードから取り込みますか?", vbYesNoCancel)
        
    Dim TextBuf As String
    If RC = vbCancel Then
        Exit Sub
    ElseIf RC = vbYes Then
        TextBuf = クリップボードの値を取得   'オートフィルタ Function クリップボードの値を取得() As String
    Else    'ファイルから
        TextBuf = テキストファイルをバッファへ(txtファイル名取得)
    End If
    
    Dim TextArray:  TextArray = Split(TextBuf, Chr(10))
    Dim TextLine As String, i As Long, cnt As Long: cnt = 1
    Dim cPos As Long, LPos As Long, pPos As Long, yPos As Long, yPos2 As Long
    Dim Arr():     ReDim Arr(UBound(TextArray), 6)
    Arr(0, 0) = "№"
    Arr(0, 1) = "FullName"
    Arr(0, 2) = "Folder"
    Arr(0, 3) = "File"
    Arr(0, 4) = "Ext"
    Arr(0, 5) = "Line"
    Arr(0, 6) = "Text"
    
    For i = 1 To UBound(TextArray) - 1 '1行目は「解析中」なので無視
        TextLine = TextArray(i)
        If Mid(TextLine, 2, 1) = ":" Then
            cPos = InStr(3, TextLine, ":")
            LPos = InStrRev(Left(TextLine, cPos), "(")
            pPos = InStrRev(Left(TextLine, cPos), ".")
            yPos = InStrRev(Left(TextLine, cPos), "\")
            yPos2 = InStrRev(Left(TextLine, yPos - 1), "\")
            Arr(cnt, 0) = cnt - 0
            Arr(cnt, 1) = Left(TextLine, LPos - 1)
            Arr(cnt, 2) = Mid(TextLine, yPos2 + 1, yPos - yPos2 - 1)
            Arr(cnt, 3) = Mid(TextLine, yPos + 1, LPos - yPos - 1)
            Arr(cnt, 4) = Mid(TextLine, pPos + 1, LPos - pPos - 1)  '"Ext"
            Arr(cnt, 5) = Mid(TextLine, LPos + 1, cPos - LPos - 2)    '"Line"
            Arr(cnt, 6) = Right(TextLine, Len(TextLine) - cPos) '"Text"
            cnt = cnt + 1
        End If
    Next i
    
    '出力
    Worksheets.Add
    Call ArrayToCell_2d(Cells(1, 1), Arr)
    ActiveSheet.Name = "oGrep"
    
End Sub
Private Function txtファイル名取得(Optional デフォルト As String = "")
    If デフォルト = "" Then
        デフォルト = デスクトップパス
    End If
    Dim i As Long
    '’https://excel-ubara.com/excelvba1/EXCELVBA376.html
    Dim FSO: Set FSO = CreateObject("Scripting.FileSystemObject")
    Dim FulName As String
    With Application.FileDialog(msoFileDialogFilePicker)
        .InitialFileName = デフォルト
        .AllowMultiSelect = False
        .Filters.Clear
        .Filters.Add "テキストファイル", "*.txt"
        .Filters.Add "すべてのファイル", "*.*"
        .Title = "テキストファイルの選択"
        If .Show = True Then
            FulName = .SelectedItems(1)
        Else
            MsgBox "キャンセルしました"
        End If
    End With
    Set FSO = Nothing
    txtファイル名取得 = FulName
End Function
 Function クリップボードの値を取得() As String
'クリップボードから文字列を取得
'https://www.ka-net.org/blog/?p=7537
  With CreateObject("Forms.TextBox.1")
    .MultiLine = True
    If .CanPaste = True Then .Paste
    クリップボードの値を取得 = .Text
  End With
End Function
Function テキストファイルをバッファへ(FullName As String) As String
    Dim buf As String
    With CreateObject("Scripting.FileSystemObject")
        With .GetFile(FullName).OpenAsTextStream
            buf = .ReadAll
            .Close
        End With
    End With
    テキストファイルをバッファへ = buf
End Function
Sub ArrayToCell_2d(Target As Range, oArr, Optional ColCnt As Long = 0) '配列貼り付け便利モジュール
    Dim iRowMax:    iRowMax = UBound(oArr, 1) - LBound(oArr, 1) + 1 '// 1次元目の要素数を取得 '// 二次元配列の最大行数
    Dim iColMax:    iColMax = UBound(oArr, 2) - LBound(oArr, 2) + 1 '// 2次元目の要素数を取得'// 二次元配列の最大列数
    
    '// Rangeオブジェクトで開始セルから貼り付けるセル範囲を拡張する場合
    If ColCnt > 0 And ColCnt <= iColMax Then
        Target.Resize(iRowMax, ColCnt).Value = oArr
    Else
        Target.Resize(iRowMax, iColMax).Value = oArr
    End If
End Sub
Private Function デスクトップパス()
    Dim UserName As String
    UserName = ユーザー名取得
    デスクトップパス = "C:\Users\" & UserName & "\Desktop"
End Function
Private Function ユーザー名取得()
    Dim WshNetworkObject As Object
    Set WshNetworkObject = CreateObject("WScript.Network")
    ユーザー名取得 = WshNetworkObject.UserName
    Set WshNetworkObject = Nothing
End Function

コマンドプロンプトでDIRコマンドを実行するマクロ

2020年02月13日
最近CMDのDIRに凝ってます。

普通にマクロ組むより圧倒的に早い。ただ使いづらい。

前回は、DIRコマンドの結果を出力したファイルを取り込んで
解析するマクロを作ったものの、
いちいちファイル指定するの面倒なのでまとめることに。

ひとまず今回は、ファイル出力するまで。

例によって今回もoffice TANAKAさんのHPのお世話になりました。
http://officetanaka.net/excel/vba/tips/tips27.htm

解析したフォルダのひとつしたの階層に本ブックを保存して
モジュールを実行。

パスについては、thisworkbook.pathで指定しているので、
カスタマイズすればOK。

CMDのコマンドについてはモジュール化したので、
引数にコマンドを入れればOK

ダブルコーテーションの中でダブルコーテーションの入力は
2つ重ねることで1文字表示する。すげー見づらい。。。

以下ソース。

 Sub 上のフォルダをDIR解析()
    'CMD で DIRコマンド
    'ターゲットフォルダはひとつ上の階層
    'txtファイルはここの階層に出力
    '出力したテキストを自動的に取り込み
    Dim txtPath As String:      txtPath = ThisWorkbook.Path 'ここのフォルダパス
    Dim yPos As Long:           yPos = InStrRev(txtPath, "\")
    Dim tarPath As String:      tarPath = Left(txtPath, yPos - 1) '対象フォルダパス
    Dim txtFulName As String:   txtFulName = txtPath & "\" & "Dirリスト.txt"
    Dim sCmd As String
    sCmd = "Dir """ & tarPath & """ /S /A-D > """ & txtFulName & """ "   'ファイルとDir 指定できてない
    
    Call ExcuteCommand_cmd(sCmd)
    'Stop
End Sub
Private Sub ExcuteCommand_cmd(sCmd As String)
    Dim WSH, wExec, Result As String
    Set WSH = CreateObject("WScript.Shell")         ''(1)WSHへの参照を作ります
    
    Set wExec = WSH.Exec("%ComSpec% /c " & sCmd)    ''(3)Execメソッドを実行
    Do While wExec.Status = 0                       ''(4)ループで完了を待ち
        DoEvents
    Loop
   
   Set wExec = Nothing
    Set WSH = Nothing
End Sub

ファイル一覧解析マクロ(その2)

2020年02月12日
前回作ったマクロの利便性が微妙だったので手直し。

/Bありで解析するパターンは、
FSOを使ってファイルの全行を読み込み、
split関数で配列に格納して1つずつ処理していた。

1行ずつ処理するならlinereadで処理したほうがいいなと思い
手を入れてみたら、IF文の分岐を結合して
ちょっと手直しするくらいでいけた。

If Right(buf, Len("のディレクトリ")) = "のディレクトリ" Then

ElseIf Mid(buf, 5, 1) = "/" And Mid(buf, 8, 1) = "/" Then

ElseIf Mid(buf, 2, 1) = ":" Then    'dirB用に追記

End If

あと、ファイル選択をオプションにすべく、
実行モジュールとメインモジュールに分割。

optional fname as string = ""とした。以下ソース。

Option Explicit
Private Sub ストップウォッチ(ByRef tictoc)
    If tictoc = 0 Then
        tictoc = Timer
    Else
        Debug.Print "[" & Now & "] "; Format(Timer - tictoc, "0.00秒")
        tictoc = Timer
    End If
End Sub
Sub ■dir結果の解析()
    Call dir結果の解析
'    Call dir結果の解析("C:\Users\Akihiro\Desktop\sadb.txt")
End Sub
Private Sub dir結果の解析(Optional fPath As String = "")
'    dir "D:\Dropbox\VBA" /s /a-d > "D:\Dropbox\VBA\dirout.txt"
    If fPath = "" Then fPath = txtファイル名取得("DIR /A-D /Sの解析結果ファイルの選択")
    If fPath = "" Then Exit Sub
    
Dim tictoc As Single
Call ストップウォッチ(tictoc)
    
    Dim Dic:    Set Dic = CreateObject("Scripting.Dictionary")
    Dim buf As String, Path As String
    Dim strDate As String, fSize As String, fname As String, Ext As String
    
    'タイトル行作成
    On Error Resume Next
    Dic.Add "FulName", "Folder" & "‡" & "File" & "‡" & "Date" & "‡" & "Size" & "‡" & "Ext"
    Open fPath For Input As #1
        Do Until EOF(1)
            Line Input #1, buf
            If Right(buf, Len("のディレクトリ")) = "のディレクトリ" Then
                Path = Trim(Replace(buf, "のディレクトリ", ""))
            ElseIf Mid(buf, 5, 1) = "/" And Mid(buf, 8, 1) = "/" Then
                strDate = Replace(Trim(Mid(buf, 1, 17)), "  ", " ")
                fSize = Trim(Mid(buf, 19, 17))
                fname = Trim(Mid(buf, 37, Len(buf) - 37 + 1))
                Ext = 拡張子(fname)
                Dic.Add Path & "\" & fname, AddressToLink(Path, ファイル名(Path)) & "‡" & fname & "‡" & strDate & "‡" & fSize & "‡" & Ext
            ElseIf Mid(buf, 2, 1) = ":" Then    'dirBの場合
                fname = ファイル名(buf)
                Path = Left(buf, Len(buf) - Len(fname) - 1)
                Ext = 拡張子(buf)
                strDate = FileDateTime(buf)
                fSize = FileLen(buf)
                Dic.Add buf, AddressToLink(Path, ファイル名(Path)) & "‡" & fname & "‡" & strDate & "‡" & fSize & "‡" & Ext
                strDate = ""
                fSize = ""
            End If
        Loop
    Close #1
    
    Dim keys: keys = Dic.keys
    Dim Items: Items = Dic.Items
    Set Dic = Nothing
    
    Dim Arr(): ReDim Arr(UBound(keys), 5)
    
    Dim oBuf, i As Long, j As Long
    For i = 0 To UBound(keys)
        oBuf = Split(Items(i), "‡")
        For j = 0 To UBound(oBuf)
            Arr(i, j) = oBuf(j)
        Next j
    Next i
    
    '出力
    Worksheets.Add
    Call ChangeSheetName("dir_" & ファイル名(fPath))
    
    Dim OutPutCell As Range: Set OutPutCell = Cells(1, 1)
    Call ArrayToCell_1d_trns(OutPutCell, keys)
    Call ArrayToCell_2d(OutPutCell.Offset(0, 1), Arr)
    Call オートフィルタと枠固定と罫線(OutPutCell)
    
    Dim OutPutRange As Range: Set OutPutRange = OutPutCell.Resize(UBound(Arr, 1) + 1, 1)
    OutPutRange.Offset(1, 3).NumberFormatLocal = "yyyy/mm/dd hh:mm"
    OutPutRange.Offset(1, 4).NumberFormatLocal = "#,##0"
    
    Cells(1, "H") = AddressToLink(fPath, "dir > " & fPath)
    
Call ストップウォッチ(tictoc)
End Sub
Private Sub オートフィルタと枠固定と罫線(OutPutCell As Range)
    OutPutCell.Offset(1, 1).Select
    ActiveWindow.FreezePanes = True 'ウィンドウ枠の固定ON
    
    OutPutCell.AutoFilter
    With ActiveSheet.AutoFilter.Range
        .CurrentRegion.Borders.LineStyle = xlContinuous
    End With
End Sub
Private Sub ChangeSheetName(shName As String)
    Dim flag As Boolean
    If UsedSheetName(shName) = False Then
        ActiveSheet.Name = shName
        Exit Sub
    End If
    
    Dim cnt As Long: cnt = 2
    Dim NewName As String: NewName = shName & " (" & cnt & ")"
    Do While UsedSheetName(NewName) = True
        cnt = cnt + 1
        NewName = shName & " (" & cnt & ")"
    Loop
    ActiveSheet.Name = NewName
End Sub
Private Function UsedSheetName(shName As String) As Boolean
    Dim ws As Worksheet, flag As Boolean
    For Each ws In Worksheets
        If ws.Name = shName Then flag = True
    Next ws
    UsedSheetName = flag
End Function
Private Sub ArrayToCell_1d_trns(Target As Range, oArr) '配列貼り付け便利モジュール
    Dim iRowMax:        iRowMax = UBound(oArr, 1) - LBound(oArr, 1) + 1
    Dim iColMax:        iColMax = 1 '1列データは転置が必要
       
    Target.Resize(iRowMax, iColMax).Value = WorksheetFunction.Transpose(oArr)
End Sub
Function AddressToLink(Address As String, Caption As String) As String
    AddressToLink = "=hyperlink(""" & Address & """,""" & Caption & """)"
End Function
Function Path2Link(Path As String) As String
    Path2Link = "=Hyperlink(""" & Path & """,""" & Path & """)"
End Function
Function txtファイル名取得(Optional タイトル As String = "テキストファイルの選択", Optional デフォルト As String = "")
    If デフォルト = "" Then
        デフォルト = デスクトップパス
    End If
    Dim i As Long
    '’https://excel-ubara.com/excelvba1/EXCELVBA376.html
    Dim FSO: Set FSO = CreateObject("Scripting.FileSystemObject")
    Dim FulName As String
    With Application.FileDialog(msoFileDialogFilePicker)
        .InitialFileName = デフォルト
        .AllowMultiSelect = False
        .Filters.Clear
        .Filters.Add "テキストファイル", "*.txt"
        .Filters.Add "すべてのファイル", "*.*"
        .Title = タイトル
        If .Show = True Then
            FulName = .SelectedItems(1)
        Else
            MsgBox "キャンセルしました"
        End If
    End With
    Set FSO = Nothing
    txtファイル名取得 = FulName
End Function
Private Function ユーザー名取得()
    Dim WshNetworkObject As Object
    Set WshNetworkObject = CreateObject("WScript.Network")
    ユーザー名取得 = WshNetworkObject.UserName
    Set WshNetworkObject = Nothing
End Function
Private Function デスクトップパス()
    Dim UserName As String
    UserName = ユーザー名取得
    デスクトップパス = "C:\Users\" & UserName & "\Desktop"
End Function
Sub ArrayToCell_2d(Target As Range, oArr, Optional ColCnt As Long = 0) '配列貼り付け便利モジュール
    Dim iRowMax:    iRowMax = UBound(oArr, 1) - LBound(oArr, 1) + 1 '// 1次元目の要素数を取得 '// 二次元配列の最大行数
    Dim iColMax:    iColMax = UBound(oArr, 2) - LBound(oArr, 2) + 1 '// 2次元目の要素数を取得'// 二次元配列の最大列数
    
    '// Rangeオブジェクトで開始セルから貼り付けるセル範囲を拡張する場合
    If ColCnt > 0 And ColCnt <= iColMax Then
        Target.Resize(iRowMax, ColCnt).Value = oArr
    Else
        Target.Resize(iRowMax, iColMax).Value = oArr
    End If
End Sub
Function 拡張子(FileName As String) As String
    Dim pPos As Long
        pPos = InStrRev(FileName, ".")
    If pPos <> 0 Then
        拡張子 = Right(FileName, Len(FileName) - pPos)
    Else
        拡張子 = ""
    End If
End Function
Function ファイル名(FileName As String) As String
    Dim yPos As Long
        yPos = InStrRev(FileName, "\")
    If yPos <> 0 Then
        ファイル名 = Right(FileName, Len(FileName) - yPos)
    Else
        ファイル名 = ""
    End If
End Function
Function TextFileToBuf(FullName As String) As String
    If FullName = "" Then Exit Function
    Dim buf As String
    With CreateObject("Scripting.FileSystemObject")
        With .GetFile(FullName).OpenAsTextStream
            buf = .ReadAll
            .Close
        End With
    End With
    TextFileToBuf = buf
End Function