REALbasic's Secret String Buffer

· Feb 21, 11:19 AM

From time to time, people ask for a mutable string class in REALbasic. As it turns out, REALbasic already has one. It is not feature-complete, but with the addition of a few extension methods, it can be complete with the features you want. The class is BinaryStream.

It does not appear to be well-known that the BinaryStream class has constructors that allow you to create streams for Strings and MemoryBlocks. Creating a BinaryStream object from a String returns a read-only BinaryStream object. This is useful for reading binary data stored in a string; you might have such data stored in a database, for example. It is even more useful for unit testing, as a String-backed BinaryStream allows you to easily mock the contents of a file.

Creating a BinaryStream object from a MemoryBlock gives you a mutable object that works quite nicely for building strings.

 dim b as new BinaryStream(new MemoryBlock(0))
  b.Write "<?xml version="1.0" encoding="utf-8"?>"
  b.Write "<example>
  b.Write "<foo>bar</foo>"
  b.Write "</example>"
  b.Position = 0
  dim xml as String = b.Read(b.Length, Encodings.UTF8)

And it’s certainly fast enough; some casual testing suggested that using a BinaryStream this way is significantly faster than appending to a String array and using Join.

With extension methods you can add the functionality you want in your string buffer class. Here are a few obvious ones, the first two of which would be nice to have for general BinaryStream use.

Function Read(extends b as BinaryStream, enc as TextEncoding = nil) As String
  return b.Read(CType(b.Length, Integer) - CType(b.Position, Integer), enc)
End Function
Function ReadAll(extends b as BinaryStream, enc as TextEncoding = nil) As String
  b.Position = 0
  return b.Read(enc)
End Function
Sub Insert(extends b as BinaryStream, offset as UInt64, s as String)
  b.Position = offset
  dim followingData as String = b.Read(CType(b.Length, Integer) - LenB(s))
  b.Position = offset
  b.Write s
  b.Write followingData
End Sub
Sub Delete(extends b as BinaryStream, offset as UInt64, length as UInt64)
  dim newLength as UInt64 = b.Length - length
  b.Position = offset + length
  dim s as String = b.Read(CType(b.Length, Integer) - CType(b.Position, Integer))
  b.Position = offset
  b.Write s
  b.Length = newLength
End Sub

I note one small gotcha. When a BinaryStream is created with a MemoryBlock,
the MemoryBlock is resized as needed using a simple doubling scheme that amortizes the cost of resizing over the number of operations. The BinaryStream correctly distinguishes between the size of the stream and the size of the MemoryBlock — except when the MemoryBlock is created. At that moment, the Length property of the BinaryStream is set to the size of the MemoryBlock. Thus, for example,

dim b as new BinaryStream(new MemoryBlock(1024))

returns a BinaryStream whose Length = 1024, and remains so unless it is resized. Thus I suggest that you forgo the apparent optimization and simply create such BinaryStream objects with a MemoryBlock of size 0.

---

Comment

  1. There is a bug in Sub Insert. If the offset is 0, the data will get truncated by LenB(s). Dropping `- LenB(s)` for offset 0 solves the problem.

    if offset = 0 then followingData = b.Read(CType(b.Length, Integer)) else followingData = b.Read(CType(b.Length, Integer) – LenB(s)) end if

    WB · May 2, 08:00 PM · #

Commenting is closed for this article.