好吧,让我们看看我在阁楼里有什么......
我有一个符合要求的 VBA Array To File功能:对于您正在做的工作可能有点矫枉过正,因为您不需要标题行、转置和检查带有错误陷阱的预先存在文件的选项读取文件的日期戳并防止对函数的重复调用不断覆盖文件。但这是我手头的代码,简化它比按原样使用更麻烦。
你想要的是这个函数默认使用制表符作为字段分隔符。当然,您可以将其设置为逗号... csv 文件的普遍接受定义是用逗号分隔的字段和封装在双引号中的文本字段(可能包含逗号字符)。但是我不能声称可以证明这种迂腐的道德制高点,因为下面的代码没有强加封装引号。
编码说明:
- 您需要对 Windows 脚本运行时库的引用:scrrun.dll - 这可以在系统文件夹(通常是 C:\WINDOWS\system32)中找到 - 因为我们使用的是文件系统对象;
- ArrayToFile 将数据写入临时文件夹中的命名文件。如果您指定“CopyFilePath”,它将被复制到其他地方:永远不要写入网络文件夹,写入本地驱动器并使用本机文件系统功能移动或复制完成的文件总是更快;
- 数据以块的形式写入文件,而不是逐行写入;
- 有进一步优化的空间:使用 Split 和 Join 函数将消除循环中的字符串连接;
- 您可能希望使用 VbCrLF 而不是 VbCr 作为行分隔符:回车通常可以工作,但某些系统和应用程序需要回车和换行符组合才能正确读取或显示换行符。
使用 ArrayToFile 函数:
这很简单:只需输入工作表使用范围的 .Value2 属性:
ArrayToFile Worksheets("Sheet1").UsedRange.Value2, "MyData.csv"
'Value2' 的原因是 'Value' 属性捕获格式,您可能需要日期字段的基础序列值。
VBA ArrayToFile 函数的源代码:
分享和享受......并注意有用的换行符,插入任何可以通过浏览器(或 StackOverflow 的有用格式化功能)破坏代码的地方:
Public Sub ArrayToFile(ByVal arrData As Variant, _
ByVal strName As String, _
Optional MinFileAge As Double = 0, _
Optional Transpose As Boolean = False, _
Optional RowDelimiter As String = vbCr, _
Optional FieldDelimiter = vbTab, _
Optional CopyFilePath As String, _
Optional NoEmptyRows As Boolean = True, _
Optional arrHeader1 As Variant, _
Optional arrHeader2 As Variant)
' Output an array to a file. The field delimiter is tab (char 9); rows use CarriageReturn(char 13).
' The file will be named as specified by strName, and saved in the user's Windows Temp folder.
' Specify CopyFilePath (the full name and path) to copy this temporary file to another folder.
' Saving files locally and copying them is much faster than writing data across the network.
' If a Min File Age 'n' is specified, and n is greater than zero, an existing file will not be
' replaced, and no data will be written unless the file is more than MinFileAge seconds old.
' Transpose = TRUE is useful for arrays generated by Recordset.GetRows and ListControl.Column
' Note that ADODB.Recordset has a native 'save' method (rows delimited by VbCr, fields by Tab)
' **** This code is in the Public Domain **** Nigel Heffernan http://Excellerando.blogspot.com
On Error Resume Next
Dim objFSO As Scripting.FileSystemObject
Set objFSO = New Scripting.FileSystemObject
If objFSO Is Nothing Then
Shell "Regsvr32.exe /s scrrun.dll", vbHide
Application.Wait Now + (0.25 / 3600 / 24)
Set objFSO = CreateObject("Scripting.FileSystemObject")
End If
If objFSO Is Nothing Then
Exit Sub
End If
Dim strFile As String
Dim strTemp As String
Dim i As Long, j As Long
Dim strData As String
Dim strLine As String
Dim strEmpty As String
Dim dblCount As Double
Const BUFFERLEN As Long = 255
strName = Replace(strName, "[", "")
strName = Replace(strName, "]", "")
Set objFSO = New Scripting.FileSystemObject
If objFSO Is Nothing Then
Shell "Regsvr32.exe /s scrrun.dll", vbHide
Application.Wait Now + (0.25 / 3600 / 24)
Set objFSO = CreateObject("Scripting.FileSystemObject")
End If
If objFSO Is Nothing Then
Exit Sub
End If
strTemp = objFSO.GetSpecialFolder(Scripting.TemporaryFolder).ShortPath
strFile = objFSO.BuildPath(strTemp, strName)
If objFSO.FileExists(strFile) Then
If MinFileAge > 0 Then
If objFSO.GetFile(strFile).DateCreated + (MinFileAge / 3600 / 24) > Now Then
Set objFSO = Nothing
Exit Sub
End If
End If
Err.Clear
objFSO.DeleteFile strFile, True
If Err.Number = 70 Then
VBA.FileSystem.Kill strFile
End If
End If
If objFSO.FileExists(strFile) Then
Exit Sub
End If
Application.StatusBar = "Cacheing data in a temp file... "
strData = vbNullString
With objFSO.OpenTextFile(strFile, ForWriting, True)
' **** **** **** HEADER1 **** **** ****
If Not IsMissing(arrHeader1) Then
If Not IsEmpty(arrHeader1) Then
If InStr(1, TypeName(arrHeader1), "(") > 1 Then ' It's an array...
Select Case ArrayDimensions(arrHeader1)
Case 1 ' Vector array
.Write Join(arrHeader1, RowDelimiter)
Case 2 ' 2-D array... 3-D arrays are not handled
If Transpose = True Then
For i = LBound(arrHeader1, 2) To UBound(arrHeader1, 2)
For j = LBound(arrHeader1, 1) To UBound(arrHeader1, 1)
strData = strData & FieldDelimiter & CStr(arrHeader1(j, i))
Next j
strData = strData & RowDelimiter
Next i
Else ' not transposing:
For i = LBound(arrHeader1, 1) To UBound(arrHeader1, 1)
For j = LBound(arrHeader1, 2) To UBound(arrHeader1, 2)
strData = strData & CStr(arrHeader1(i, j))
If j < UBound(arrHeader1, 2) Then
strData = strData & FieldDelimiter
End If
Next j
strData = strData & RowDelimiter
Next i
End If ' Transpose
End Select
' .Write strData
' strData = vbNullString
Erase arrHeader1
Else ' treat it as a string
If LenB(arrHeader1) > 0 Then
.Write arrHeader1
End If
End If
End If 'Not IsMissing(arrHeader1)
End If 'Not IsEmpty(arrHeader1)
' **** **** **** HEADER2 **** **** ****
If Not IsMissing(arrHeader2) Then
If Not IsEmpty(arrHeader2) Then
If InStr(1, TypeName(arrHeader2), "(") > 1 Then ' It's an array...
Select Case ArrayDimensions(arrHeader2)
Case 1 ' Vector array
.Write Join(arrHeader2, RowDelimiter)
Case 2 ' 2-D array... 3-D arrays are not handled
If Transpose = True Then
For i = LBound(arrHeader2, 2) To UBound(arrHeader2, 2)
For j = LBound(arrHeader2, 1) To UBound(arrHeader2, 1)
strData = strData & FieldDelimiter & CStr(arrHeader2(j, i))
Next j
strData = strData & RowDelimiter
Next i
Else ' not transposing:
For i = LBound(arrHeader2, 1) To UBound(arrHeader2, 1)
For j = LBound(arrHeader2, 2) To UBound(arrHeader2, 2)
strData = strData & CStr(arrHeader2(i, j))
If j < UBound(arrHeader2, 2) Then
strData = strData & FieldDelimiter
End If
Next j
strData = strData & RowDelimiter
Next i
End If ' Transpose
End Select
' .Write strData
' strData = vbNullString
Erase arrHeader2
Else ' treat it as a string
If LenB(arrHeader2) > 0 Then
.Write arrHeader2
End If
End If
End If 'Not IsMissing(arrHeader2)
End If 'Not IsEmpty(arrHeader2)
' **** **** **** BODY **** **** ****
If InStr(1, TypeName(arrData), "(") > 1 Then
' It's an array...
Select Case ArrayDimensions(arrData)
Case 1
If NoEmptyRows Then
.Write Replace$(Join(arrData, RowDelimiter), RowDelimiter & RowDelimiter, "")
Else
.Write Join(arrData, RowDelimiter)
End If
Case 2
If Transpose = True Then
strEmpty = String(UBound(arrData, 1) - 1, FieldDelimiter) & RowDelimiter
For i = LBound(arrData, 2) To UBound(arrData, 2)
For j = LBound(arrData, 1) To UBound(arrData, 1)
strData = strData & FieldDelimiter & CStr(arrData(j, i))
Next j
strData = strData & RowDelimiter
If (Len(strData) \ 1024) > BUFFERLEN Then
If NoEmptyRows Then
strData = Replace$(strData, strEmpty, "")
'strData = Replace$(strData, RowDelimiter & RowDelimiter, "")
End If
Application.StatusBar = "Cacheing data in a temp file... (" & Format(dblCount + (Len(strData) \ 1024), "0,000") & "kB)"
dblCount = dblCount + (Len(strData) \ 1024)
.Write strData
strData = vbNullString
End If
Next i
Else ' not transposing:
strEmpty = String(UBound(arrData, 2) - 1, FieldDelimiter) & RowDelimiter
For i = LBound(arrData, 1) To UBound(arrData, 1)
For j = LBound(arrData, 2) To UBound(arrData, 2)
strData = strData & CStr(arrData(i, j))
If j < UBound(arrData, 2) Then
strData = strData & FieldDelimiter
End If
Next j
strData = strData & RowDelimiter
If (Len(strData) \ 1024) > BUFFERLEN Then
If NoEmptyRows Then
strData = Replace$(strData, strEmpty, "")
'strData = Replace$(strData, RowDelimiter & RowDelimiter, "")
End If
Application.StatusBar = "Cacheing data in a temp file... (" & Format(dblCount + (Len(strData) \ 1024), "0,000") & "kB)"
dblCount = dblCount + (Len(strData) \ 1024)
.Write strData
strData = vbNullString
End If
Next i
End If ' Transpose
End Select
If NoEmptyRows Then
strData = Replace$(strData, strEmpty, "")
'strData = Replace$(strData, RowDelimiter & RowDelimiter, "")
End If
If Right$(strData, Len(RowDelimiter)) = RowDelimiter Then
Mid$(strData, Len(strData) - Len(RowDelimiter), Len(RowDelimiter)) = ""
End If
.Write strData
strData = vbNullString
Erase arrData
Else ' treat it as a string
.Write arrData
End If
.Close
End With ' textstream object from objFSO.OpenTextFile
If CopyFilePath <> "" Then
Application.StatusBar = "Copying " & strName & " to " & CopyFilePath & "..."
objFSO.CopyFile strFile, CopyFilePath, True
End If
Application.StatusBar = False
Set objFSO = Nothing
strData = vbNullString
End Sub
为了完整起见,这里是从文件读取到数组的补充函数,以及清理临时文件的粗略子程序:
Public Sub FileToArray(arrData As Variant, strName As String, Optional MaxFileAge As Double = 0, Optional RowDelimiter As String = vbCr, Optional FieldDelimiter = vbTab, Optional CoerceLowerBound As Long = 0) ' Load a file created by FileToArray into a 2-dimensional array
' The file name is specified by strName, and it is exected to exist in the user's temporary folder.
' This is a deliberate restriction: it's always faster to copy remote files to a local drive than to edit them across the network
' If a Max File Age 'n' is specified, and n is greater than zero, files more than n seconds old will NOT be read.
' **** This code is in the Public Domain **** Nigel Heffernan http://Excellerando.blogspot.com
On Error Resume Next
Dim objFSO As Scripting.FileSystemObject
Set objFSO = New Scripting.FileSystemObject
If objFSO Is Nothing Then
Shell "Regsvr32.exe /s scrrun.dll", vbHide
Application.Wait Now + (0.25 / 3600 / 24)
Set objFSO = CreateObject("Scripting.FileSystemObject")
End If
If objFSO Is Nothing Then
Exit Sub
End If
Dim strFile As String
Dim strTemp As String
Dim i As Long
Dim j As Long
Dim i_n As Long
Dim j_n As Long
Dim i_lBound As Long
Dim i_uBound As Long
Dim j_lBound As Long
Dim j_uBound As Long
Dim arrTemp1 As Variant
Dim arrTemp2 As Variant
Dim dblCount As Double
Set objFSO = New Scripting.FileSystemObject
If objFSO Is Nothing Then
Shell "Regsvr32.exe /s scrrun.dll", vbHide
Application.Wait Now + (0.25 / 3600 / 24)
Set objFSO = CreateObject("Scripting.FileSystemObject")
End If
If objFSO Is Nothing Then
Exit Sub
End If
strTemp = objFSO.GetSpecialFolder(Scripting.TemporaryFolder).ShortPath
strFile = objFSO.BuildPath(strTemp, strName)
If Not objFSO.FileExists(strFile) Then
Exit Sub
End If
If MaxFileAge > 0 Then
' If the file's a bit elderly, bail out - the calling function will refresh the data from source
If objFSO.GetFile(strFile).DateCreated + (MaxFileAge / 3600 / 24) < Now Then
Set objFSO = Nothing
Exit Sub
End If
End If
Application.StatusBar = "Reading the file... (" & strName & ")"
arrData = Split2d(objFSO.OpenTextFile(strFile, ForReading).ReadAll, RowDelimiter, FieldDelimiter, CoerceLowerBound)
Application.StatusBar = "Reading the file... Done"
Set objFSO = Nothing
End Sub
Public Sub RemoveTempFiles(ParamArray FileNames())
On Error Resume Next
Dim objFSO As Scripting.FileSystemObject
Set objFSO = New Scripting.FileSystemObject
If objFSO Is Nothing Then
Shell "Regsvr32.exe /s scrrun.dll", vbHide
Application.Wait Now + (0.25 / 3600 / 24)
Set objFSO = CreateObject("Scripting.FileSystemObject")
End If
If objFSO Is Nothing Then
Exit Sub
End If
Dim varName As Variant
Dim strName As String
Dim strFile As String
Dim strTemp As String
strTemp = objFSO.GetSpecialFolder(Scripting.TemporaryFolder).ShortPath
For Each varName In FileNames
strName = vbNullString
strFile = vbNullString
strName = CStr(varName)
strFile = objFSO.BuildPath(strTemp, strName)
If objFSO.FileExists(strFile) Then
objFSO.DeleteFile strFile, True
End If
Next varName
Set objFSO = Nothing
End Sub
我建议您将其保留在 Option Private Module 下的模块中 - 这不是我希望其他用户直接从工作表调用的那种功能。