Imagine you are building the next Billion Dollar app 💵 that has a lot of innovative features which are new to the tech world. But your app crashes a lot! Further investigation proved that the code you wrote for the app is not optimized.
Uff ! Thats a bummer 😐. What would you do?
Well, you call me 📲 !
Nahh, just open this tutorial.✅
Most of the apps out there incorporate String Concatenation in some or the other way. It could be to join the First Name and Last Name of a person, or get the list of cities the user has travelled this year, and the list goes on.
Most of the developers including me, follow one approach for string concatenation, ie, using the '+' operator.
Here is a basic example to illustrate what i meant:
String? firstName = "Aswin";
String? lastName = "Gopinathan";
String? fullName = firstName + " " + lastName;
This is the most typical approach, and it's easier too. But this approach comes with a price.
And the price is memory !!
Let's see another example where we concatenate strings from a List:
List<String> cities = ["Bangalore","Mumbai","Delhi","Chennai"];
String finalCities = "";
for(String city in cities) {
finalCities += (city+' ');
}
print(finalCities);
The output will be:
Bangalore Mumbai Delhi Chennai
You might have read that Strings in dart are immutable. But, we just updated finalCities
multiple times. How is it even possible?
Well, the answer to this magic is that every time we concatenate a new string, the compiler allocates a new memory for the resultant string, and the garbage collector is called to de-allocate the previous memory location.
So literally the garbage collector is called for every item you concatenate, and for each of those iterations a new memory location is assigned !!
Finding it hard to believe? Its okay, i understand.
Let's do one thing. Write this line of code inside the for loop from the previous code snippet:
print(identityHashCode(finalCities));
This line of code prints the hashcode of the memory address of the variable finalCities
.
What do you see? The output should be like this:
260230835
327507326
116916136
283166850
The values may differ for you.
So, it is pretty clear from this that the variable finalCities
uses different memory location in every iteration.
But dont worry, if i bummed you out with this, i will fix it too !!
Enter : StringBuffer 🥁🥁
Me : "Welcome StringBuffer to this article"
StringBuffer : "Ahoy ! Let me introduce myself. My name is StringBuffer. I take care of the mess that my ancestor String creates. You know he is pretty old-school.
Me : "I know but he is still one of the best. So without any further delays, let me tell everyone how you work."
So, as the official dart docs says :
"A class for concatenating strings efficiently. Allows for the incremental building of a string using write() methods. The strings are concatenated to a single string only when toString is called."
Let me break that down for you.
StringBuffer
is a class that lets you concatenate string efficiently. Duh! you just read that above.
Well, how it works is you use the inbuilt method write()
to concatenate strings instead of the +
operator. But what happens with StringBuffer
is that, it stores the string in a buffer and not in a memory location. It uses the memory only when the toString()
method is called to create a String
object.
Tada! Now you don't have to worry about the memory being allocated every time you concatenate.
See, StringBuffer is a superhero 😎
Well, thats all for the theory part 😉
But don't go yet, let's get our hands dirty. Shall we? 😋
So, lets open a new dart file and start off with the main():
void main() {
// Enter code here
}
Let's create a StringBuffer
object first. We will be using the same List as before:
StringBuffer sb = StringBuffer();
List<String> cities = ["Bangalore","Mumbai","Delhi","Chennai"];
Now, rewrite the for loop as follows:
for(String city in cities) {
sb.write(city+" ");
// print(identityHashCode(sb));
}
We use the write()
to append new strings to the end. You can uncomment the print()
statement to confirm no new memory location is created for every concatenation.
Well, now print the result:
String cityList = sb.toString();
print(cityList);
It is at this point a memory location is created to print the concatenated string.
Wow! Thats really great. Now you have reduced one of the many causes of memory leaks in your app 👏👏.
Now, you are one step closer to releasing that Billion-dollar app.
So, with that i am ending this article. But this is not the actual end. I am still exploring Flutter and Dart and as i learn anything new, i will be back with more articles.
So till then, Bye bye !!! 👋
Top comments (9)
"So literally the garbage collector is called for every item you concatenate" - this is not true, GC does not happen on every concatenation. It is true that every concatenation allocates a new string, though.
If you use string builder then you might want to go all in and do:
sb.write(city); sb.write(" ");
instead of doing concatenation and then adding it to the builder.Yet better way is simply to do
cities.join(" ")
.Ohh ! That i didnt know about GC. Thanks for letting me know. Will keep that in mind.
So i have a doubt. When will the GC be called then for disposing the previously allocated memory ?
When GC heuristics decide that it is time to do so. There are multiple things GC monitors (e.g. how much space is used in the nursery, how much space is used in the old space, etc) and few external signals (e.g. Flutter can tell Dart that it's in between frames and Dart has time to do GC, OS can tell Flutter that memory is getting low - which Flutter will then forward to Dart, etc). GC takes all of this into account and decides when and what to do. It's a pretty complicated logic which tries to find balance between memory usage and performance.
Okay ! Now i get it.
It's really interesting how GC is handled in Dart. Makes me wanna explore more on that !
Thanks again for sharing your insights.
FWIW GCs are never invoked after each allocation - that would be prohibitively expensive, so there is nothing Dart specific here.
You might have been thinking about how some languages use reference counting which would free the memory occupied by an object immediately after the last reference to it disappears.
Yeah, i have been following the same concept here. Guess i didnt go about it the right way !
So, the languages that incorporate reference counting approach to freeing a memory immediately is less efficient ? Or did i miss anything ?
This is was i was also thinking, if we are supposed we concatenation operator as argument of .write() then enventually our space problem similar. Thanks for comment. Cleared many thing. 😊
Amazing! Thank you for it 💯
😋 Thanks Aditya !!