StringBuilder and StringBuffer
Explore how to use StringBuilder and StringBuffer in Java for efficient text processing. Understand the performance cost of immutable Strings and learn to manipulate text dynamically with methods like append, insert, delete, and reverse. Discover when to choose StringBuilder for fast single-threaded operations or StringBuffer for thread-safe scenarios. This lesson helps you write high-performance, maintainable Java code for dynamic string handling.
We discovered that Java String objects are immutable once created, they cannot be changed. While this ensures safety and security, it incurs a hidden performance cost when we frequently modify text. Every time we concatenate strings in a loop or modify a sentence, Java quietly creates entirely new objects in memory, discarding the old ones.
To solve this, Java provides powerful tools designed specifically for heavy-duty text manipulation. In this lesson, we will learn how to use StringBuilder and StringBuffer to write efficient, high-performance code that handles dynamic text with ease.
The cost of immutability
When we modify a String using the + operator, Java does not actually change the original object. Instead, it copies the existing characters, appends the new ones, and allocates memory for a brand-new object.
If we do this once or twice, the cost is negligible. However, inside a loop, this behavior can severely impact performance. Consider a scenario where we want to build a long sentence word by word.
If we use string concatenation, we flood memory with intermediate objects that are discarded immediately. This puts unnecessary pressure on the Garbage Collector, slowing down our application.
Here is how StringBuilder solves this problem by using a single, resizable container:
Line 4: We initialize an empty string.
Line 6: Inside the loop,
slowText +=causes the creation of a new string object for every iteration (e.g., “0 ”, then “0 1 ”, then “0 1 2 ”).Line 11: We initialize a
StringBuilder, which maintains a mutable sequence of characters.Line 13: The
append()method adds characters directly to the existing internal buffer without creating new objects.Line 15: We call
toString()to convert the mutable sequence back into an immutableStringfor printing.
Working with StringBuilder
The StringBuilder class functions like a dynamic array for characters. We can initialize it with no arguments (default capacity) or provide an initial String or capacity. The most common operation is append(), which adds data to the end of the sequence.
StringBuilder is flexible; it can accept primitives, arrays, objects, and strings.
Line 3: We create a
StringBuildercontaining “Welcome”.Line 6: We append the string “ to”. The internal buffer expands if necessary.
Line 7: We append a single character literal.
Line 9: We append an integer;
StringBuilderautomatically converts the number21to its string representation “25” before appending.Line 11: Printing the object automatically calls its
toString()method.
Modifying data (insert, delete, and reverse)
Unlike String, which requires complex substring operations to simulate editing, StringBuilder lets us insert, remove, or flip characters in place. This makes it ideal for tasks like formatting user input or parsing complex data.
insert(index, value): Adds characters at a specific position, shifting subsequent characters to the right.delete(start, end): Removes characters from thestartindex up to (but not including) theendindex.deleteCharAt(index): Removes a single character.reverse(): Reverses the sequence of characters in place.
Line 6: We locate the starting index of the word “Python”.
Line 7:
deleteremoves characters from index 7 to 13. The buffer now holds “I love ”.Line 8:
insertplaces “Java” at index 7. The buffer now holds “I love Java”.Line 13:
reverseflips the entire sequence to “avaJ evol I”.
Method chaining (fluent API)
Most methods in StringBuilder return the StringBuilder object itself (this). This design pattern, known as a fluent API, allows us to chain multiple method calls into a single, readable line of code.
Instead of writing separate statements for each operation, we can combine them.
Line 5:
sb.append("Java")adds “Java” and returns thesbobject.Line 6: The next
.append(" ")is called on that same returned object.Line 7: The
.append(25)method accepts an integer, converts it to a string representation, and adds it to the sequence, resulting in “Java 25”.Line 8:
.reverse()flips “Java 25” to “52 avaJ”.Line 9: Finally,
.toString()converts the builder into a standardStringfor the assignment.
StringBuffer and thread safety
Java provides another class called StringBuffer. It offers the exact same methods as StringBuilder (append, insert, delete, etc.), but with one critical difference: thread safety.
Methods in StringBuffer are synchronized. This means that if multiple threads try to modify a StringBuffer at the same time, Java ensures that only one thread can access it at a time, preventing data corruption. However, this safety comes with a performance cost. Synchronization requires extra processing overhead, making StringBuffer slower than StringBuilder.
In modern Java development, we rarely use StringBuffer because we usually keep text buffers local to a single thread.
Line 4: We instantiate
StringBufferinstead ofStringBuilder.Line 6: The
appendoperation works exactly the same way but is internally synchronized.
Choosing between String, StringBuilder, and StringBuffer
Choosing the correct text class is a common design decision. We can decide based on two factors: mutability (do we need to change it?) and thread usage (is it shared?).
The following guidelines help us choose the right tool:
Use
Stringwhen the value is constant or rarely changes. This includes string literals, constants, identifiers, and map keys. Immutability makes it safe and easy to cache.Use
StringBuilderfor almost all text manipulation. If you are building strings in a loop, formatting complex output, or modifying text within a method, this is the standard choice. It is fast and efficient.Use
StringBufferonly in specific legacy scenarios or when a single text buffer must be shared and modified by multiple threads simultaneously. If the buffer is not shared,StringBuffersimply adds unnecessary overhead.
We have now unlocked the ability to manipulate text efficiently in Java. By switching from String concatenation to StringBuilder, we can construct complex data strings and process large amounts of text without filling memory with garbage. We also understand that while StringBuffer exists for thread-safe scenarios, StringBuilder is our default tool for performance.