Using Pre-Made Stringbuilder Class

前端 未结 4 1870
夕颜
夕颜 2020-12-18 10:48

I am trying to learn how to use stringbuilder functions in VBA, but am having trouble finding resources in VBA on how to use them. I could use some help to use a pre-made st

相关标签:
4条回答
  • 2020-12-18 11:02

    I agree with @MatthewD but in case you are doing this for practice I will add some more info.

    Here are some links on declaring variables and using classes in VBA from Chip Pearson's great site.

    You need to add a new Class Module to your VBA project and paste in your code. Be careful - you speech marks " in your example might need to be replaced, they've been mangled into a similar looking but different character somewhere. Change the name of the class module to "StringBuilder" and then you can use the code in an ordinary module like this:

    Sub test1()
    
    Dim strBld As StringBuillder
    
        Set strBld = New StringBuillder
    
        strBld.Append "Hello"
        strBld.Append " world"
        strBld.Append "!"
    
        Debug.Print strBld.ToArray(0)
        Debug.Print strBld.ToString
    
    End Sub
    
    0 讨论(0)
  • 2020-12-18 11:18

    String concatenation (&) in VBA is notoriously slow, so often "stringbuilder" classes like this one are used to speed up the process if you have a large number of strings you need to combine.

    The general idea is to use an Array() to store individual string components and then combine all strings once, when you need them, using the Join() function. The array is automatically resized as strings are added. Many use a "GrowBy" feature (although this one doesn't) to grow the array by a static size or factor as the array limit is reached. That can improve performance as well, since calling ReDim Preserve for every string insertion can take its toll.

    To answer your question, pretend you needed to build a portion of an HTML file. You could use the shown string class like so:

    Dim sb
    Set sb = New StringBuilder      ' Guessing here. You haven't shown the class name.
    sb.Append "some string"
    sb.Append "another string"
    sb.Append "a third string"
    ....
    sb.Delimiter = "<br>"
    myHtmlFile.Write sb.ToString()
    

    Would print the following:

    some string<br>another string<br>a third string
    

    That's the general idea. Avoid the use of & as much as possible by using an array and you should see some significant performance improvements.

    0 讨论(0)
  • 2020-12-18 11:23

    This is an old question and the OP has probably moved on. However, when I looked for something on this problem, this was the page I found. In case others find it, here is my attempt at a better answer and a link to an even better answer.

    I cannot find an adequate explanation of the problem in either the question or any of the previous answers. In my experiments, the accepted answer performs worse than the inbuilt method of building a long string.

    The problem

    Suppose I write:

    StrVar = "abcde"
    StrVar = StrVar & "fghij"
    StrVar = StrVar & "klmno"
    

    For the first statement, the VBA interpreter finds memory sufficient to hold 5 characters, writes "abcde" to that memory and points StrVar at that memory.

    For the second statement, the VBA interpreter finds memory sufficient to hold 10 characters, copies the existing contents of StrVar to that memory, appends "fghij", points StrVar at that memory and releases the old memory for garbage collection.

    For the third statement, the VBA interpreter find memory sufficient to hold 15 characters, copies the existing contents of StrVar to that memory, appends "klmno", points StrVar at that memory and releases the old memory for garbage collection.

    The point is VBA strings are immutable; they are never changed. Instead, every time your macro amends a string, new memory is found for the amended string and the memory for the original string is released. For a few string amendments, nothing is done about the released memory so there is no obvious time wasted building a long string. But, if you do this often enough, the interpreter runs out of new memory and it has to call the garbage collector. The garbage collector works through all the memory identifying what bits are still being used and which bits aren’t. It moves all the bits that are still being used together leaving the unused bits as new, available memory. Control is returned to the interpreter which continues handling new string assignments until it runs out of new memory again.

    If you are building a longer and longer string, the interpreter runs out of memory faster and faster until your macro slows to a crawl.

    What I wanted to achieve, my solution and a faster solution

    I wanted to convert an Excel range to Html. Ignoring formatting, I wanted to create a string like this:

    <table>
      <tr>
        <td> … </td>
        <td> … </td>
      </tr>
      <tr>
        <td> … </td>
        <td> … </td>
      </tr>
      <tr>
        <td> … </td>
        <td> … </td>
      </tr>
    </table>
    

    On my laptop, the duration of macro building this string increased steadily until about 10,000 cells. It then started slowing down significantly:

    Duration
     in secs    Cells
      0.000       200
      0.016       400
      0.031     1,000
      0.062     2,000
      0.14      4,000
      0.44      8,000
      0.67     10,000
      0.97     12,000
      2.27     14,000
      6.79     16,000
     10.9      18,000
     63        32,000
    

    On my laptop, 8,000 cells took 3 times as long as 4,000 cells; 16,000 cells took 15 times as long as 8,000 cells and 32,000 cells took 9 times as long as 16,000 cells. If you repeat my experiment, the increase in duration on your computer will depend on how much memory you have and on whatever else you are doing.

    I have no expectation of needing to convert 32,000 cells. However, for this experiment, I was only converting the cell value. Once I started converting the formatting, string lengths would increase dramatically and the maximum number of cells with acceptable performance would drop,

    My next idea was to create a string array with one element per row. Output each table row to a different element of the string array and then Join the elements to create the full string. This is the same idea as in the accepted answer. When I tried this idea, 32,000 cells took 91 seconds. Perhaps I could have reduced the duration by optimising my code but this did not appear to be a promising approach.

    My next idea was to write each bit of the string to a disc file and then read the file to get the entire. I have not timed them properly but disc reads and write have always seems fast enough. With this approach, durations were:

    Duration
     in secs    Cells
       .375     10,000
       .609     18,000
      3.45     100,000
     34.8    1,000,000
    

    These durations are much more acceptable. The duration will increase as I start converting the formatting but since 1,000,000 cells is way more that I expect to convert, I should get acceptable performance.

    To show my technique, this is the important part of my code:

      ' Needs reference to Microsoft Scripting RunTime
    
      Dim Body As String
      Dim ColCrnt As Long, ColLeft As Long, ColRight As Long
      Dim FileOut As TextStream
      Dim Fso As FileSystemObject
      Dim PathFileName As String
      Dim RowBot As Long, RowCrnt As Long, RowTop As Long
      Dim Wsht As Excel.Worksheet
    
      Set Fso = CreateObject("Scripting.FileSystemObject")
      PathFileName = ThisWorkbook.Path & "\RtH.txt"
      ' Create text file. First True = Overwrite existing file. Second True = Unicode output
      Set FileOut = Fso.CreateTextFile(PathFileName, True, True)
    
        FileOut.WriteLine "<table>"
    
        For RowCrnt = RowTop To RowBot
          FileOut.WriteLine "  <tr>"
          For ColCrnt = ColLeft To ColRight
            FileOut.WriteLine "    <td>" & Wsht.Cells(RowCrnt, ColCrnt).Value & "</td>"
          Next
          FileOut.WriteLine "  </tr>"
        Next
        FileOut.WriteLine "</table>"
    
      FileOut.Close
      ' Open text file. 1 = Open for reading.  -1 = Unicode
      Set FileOut = Fso.OpenTextFile(PathFileName, 1, -1)
      Body = FileOut.ReadAll
      FileOut.Close
    

    Having got this far in this answer, I had another look for a solution to the string building problem and discovered: A lightning-fast StringBuilder

    This page contains several version of the solution as different people suggested improvements. I tried the version by Duran. The duration of my code to convert 1,000,000 cells reduced from 34.8 seconds with my disc solution to 3.92 seconds.

    My solution has the advantage that it only uses simple, easy VBA and is way faster than any other simple solution. I have tested it with a variety of characters across the 16-bit Unicode range without problems.

    The linked solution used more advanced VBA. I would not let that stop you from using it; you use VBA standard routines without knowing how they work. I suggest reading every version of the solution and every comment since there is much useful information. I chose the Duran version but you might prefer another. The only important point that I could not find in any of the answers or comments is that the code must be placed in a Class module named “StringBuilder”.

    0 讨论(0)
  • 2020-12-18 11:28

    I'm not sure I follow your question since VBA has no string builder. Strings can simply be built.

    Dim strTemp as string
    Dim strCountry as string
    
    strCountry = "Nowhere"
    strTemp = "I am from " & strCountry
    
    0 讨论(0)
提交回复
热议问题