REALbasic's Secret String Buffer
· Feb 21, 12:19 PMFrom 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
Commenting is closed for this article.
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, 09:00 PM · #