Four Ways to Clean Master Strings
When I first wrote about master strings I proposed a function that would help to trim them down and potentially save a lot of memory. However, that method still resulted in a string with a master string one longer than it. Ideally, we’d have no master string at all. Since then, three astute readers chimed in with alternate solutions to the problem. Today I put try all three out to see which method does the best job of cleaning master strings.
As a refresher, here’s the version I came up with in the previous article. For clarity, I’ve renamed it to cleanMasterStringUnderscore
since it appends an underscore to the String
and uses that as a master string instead.
public static function cleanMasterStringUnderscore(str:String): String { return ("_"+str).substr(1); }
Next is the suggestion to use two String.substr()
calls. The first call gets the first character and the second gets the rest. These two parts are then concatenated.
public static function cleanMasterStringDoubleSubstr(str:String): String { return str.substr(0,1)+str.substr(1); }
A third solution is to put the String
in an Object
.
public static function cleanMasterStringObject(str:String): String { var map:Object = {}; map[str] = str; return str; }
Lastly, the Alternativa3D solution is to write the String
to a ByteArray
and then read it back.
public static function cleanMasterStringByteArray(str:String): String { var bytes:ByteArray = new ByteArray(); bytes.writeUTFBytes(str); bytes.position = 0; return bytes.readUTFBytes(bytes.length); }
The following test app tries out all four methods and prints the results on each frame, just in case the garbage collector ever kicks in.
package { import flash.system.Capabilities; import flash.events.Event; import flash.sampler.getMasterString; import flash.display.*; import flash.text.*; import flash.utils.*; public class MasterStringTest extends Sprite { private var logger:TextField = new TextField(); private var str:String; private var strCleanUnderscore:String; private var strCleanDoubleSubstr:String; private var strCleanObject:String; private var strCleanByteArray:String; public function MasterStringTest() { stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; logger.autoSize = TextFieldAutoSize.LEFT; addChild(logger); if (!Capabilities.isDebugger) { logger.text = "Debug version of Flash Player required to run this test"; return; } // Get a String with a big master String var xml:XML = new XML( '<people><person first="John"/></people>' ); str = xml.person[0].@first; // Clean with the underscore-prepending method strCleanUnderscore = cleanMasterStringUnderscore(xml.person[0].@first); // Clean with the double substr() method strCleanDoubleSubstr = cleanMasterStringDoubleSubstr(xml.person[0].@first); // Clean with an Object strCleanObject = cleanMasterStringObject(xml.person[0].@first); strCleanByteArray = cleanMasterStringByteArray(xml.person[0].@first); // Display the master string status every frame addEventListener(Event.ENTER_FRAME, onEnterFrame); } /** * Replace a string's "master string"-- the string it was built from-- * with the same string plus a single character to save memory * @param str String to clean * @return The input string, but with a master string only one * character larger than it * @author Jackson Dunstan, JacksonDunstan.com */ public static function cleanMasterStringUnderscore(str:String): String { return ("_"+str).substr(1); } /** * Replace a string's "master string"-- the string it was built from-- * with the string's first character to save memory * @param str String to clean * @return The input string, but with a single character master string * @author Jackson Dunstan, JacksonDunstan.com */ public static function cleanMasterStringDoubleSubstr(str:String): String { return str.substr(0,1)+str.substr(1); } /** * Try to replace a string's "master string"-- the string it was built * from-- by using an Object. This function is not successful. * @param str String to clean * @return The input string, but with a single character master string * @author Jackson Dunstan, JacksonDunstan.com */ public static function cleanMasterStringObject(str:String): String { var map:Object = {}; map[str] = str; return str; } /** * Replace a string's "master string"-- the string it was built from-- * with the string's first character to save memory * @param str String to clean * @return The input string, but with a single character master string * @author Jackson Dunstan, JacksonDunstan.com */ public static function cleanMasterStringByteArray(str:String): String { var bytes:ByteArray = new ByteArray(); bytes.writeUTFBytes(str); bytes.position = 0; return bytes.readUTFBytes(bytes.length); } private function onEnterFrame(ev:Event): void { function line(label:String, str:String): void { logger.appendText(label + ": " + getMasterString(str) + "\n"); } logger.text = "Master strings:\n\n"; line("Original", str); line("Clean (underscore)", strCleanUnderscore); line("Clean (double substr())", strCleanDoubleSubstr); line("Clean (Object)", strCleanObject); line("Clean (ByteArray)", strCleanByteArray); } } }
Here is what Flash Player 12.0 outputs:
Master strings: Original: <people><person first="John"/></people> Clean (underscore): _John Clean (double substr()): J Clean (Object): <people><person first="John"/></people> Clean (ByteArray): null
First off, the Object
version does not change the master string at all. I’m not sure why it would be expected to, other than some quirk or bug of the Flash Player.
Second, the underscore method continues working as before and serves as a decent baseline. It still dramatically cuts down on master string memory usage but ultimately ends up using memory for 2*N+1
characters for a string N
characters long. For a big string, this could be a big hit.
The double substr()
method helps this out a lot. The first substr()
seems to end up as the master string. That’s good, because it’s only the first character of the string. In this case, we’re only using N+1
characters worth of memory: a huge improvement!
As good as the double substr()
method is, it can’t beat the ByteArray
solution. Here we end up with absolutely no master string. This is the solution to use to really clean your master string out and potentially save a ton of unnecessary memory usage.
Spot a bug? Have a question or suggestion? Post a comment!
#1 by henke37 on March 31st, 2014 ·
This shows which one is best size wise. It does however ignore the execution speed.
#2 by zeh on March 31st, 2014 ·
This is relevant to my interests, so here’s some *quick* testing, for 100000 conversions of the same string used in the post (testing with a much longer master string produced the same results):
Underscore: 71-72ms
Double substr: 78-81ms
ByteArray: 270-278ms
Underscore and double substr are pretty similar, about 0.72 nano seconds per conversion. The ByteArray conversion is definitely slower, at 2.78 nanoseconds per conversion. Still, I would call this difference negligible; the whole conversion is so fast that it makes sense to do this as much as needed.
#3 by zeh on April 8th, 2014 ·
Jason, just noticed a bug in your code. In the
ByteArray
version of your code, you do areadUTFBytes()
with the length of the string. That means multi-byte (UTF-8) strings will be cut short (e.g “resumé” will return length 6, but the actual length is 7). Instead, one should use theByteArray
length as the parameter:public static function cleanMasterStringByteArray(str:String): String
{
var bytes:ByteArray = new ByteArray();
bytes.writeUTFBytes(str);
bytes.position = 0;
return bytes.readUTFBytes(bytes.length);
}
#4 by jackson on April 8th, 2014 ·
I’ve updated the article with your proposed fix. Thanks for catching this!
#5 by xxbbcc on May 3rd, 2014 ·
Thanks for your article, very informative and helpful. I have a questions regarding the ByteArray test – is there a reason you didn’t make the ByteArray a static member variable? That would speed up removing the master string since you’d avoid allocating and freeing the ByteArray every time you call this function. You could just set the length of the ByteArray to 0 instead.
#6 by jackson on May 3rd, 2014 ·
A real utility class should re-use the
ByteArray
to avoid unnecessary GC work. I’d make one alteration though: set thelength
to zero after cleaning the master string so as to not hold a duplicate copy of theString
after cleaning its master string. For the purposes of this article, I tried to keep the code as simple as possible and contained everything within a single, small function.