Why You Should Stop Misusing String: Complete Guide to String, StringBuffer, and StringBuilder
This article thoroughly examines Java's String, StringBuffer, and StringBuilder, explaining their internal implementations, immutability, JDK 9 changes, performance characteristics, and best‑practice usage scenarios, while providing concrete code examples, benchmarks, and interview questions to help developers choose the right tool.
Introduction
In Java, string handling is ubiquitous, but many developers misuse String concatenation with +=, causing severe performance problems.
1. String – immutable secret
1.1 How String is implemented
String is a final class that holds a final char[] (or byte[] from JDK 9) called value. Once created, the array cannot be modified, so any apparent modification creates a new String object.
public final class String {
private final char[] value;
}Internal array is final → cannot be changed.
All "modifications" actually allocate a new String.
1.2 JDK 9 change
Before JDK 9 the value was a char[]. Since JDK 9 it is a byte[] plus a coder field, allowing Latin‑1 storage and halving memory for ASCII strings.
public final class String {
private final byte[] value;
private final byte coder; // LATIN1 or UTF16
}Memory saving: Latin‑1 strings use half the memory.
Performance boost: less GC pressure, better cache hits.
The change is transparent to developers; existing code continues to work.
1.3 Cost of immutability
Each concatenation or substring creates a new object. Example:
String s = "hello";
s = s + " world"; // creates a new String, old one discardedIn a loop of 10 000 iterations, nearly 100 000 objects are created:
String result = "";
for (int i = 0; i < 10000; i++) {
result += i; // each iteration creates a new String
}Answer: almost 100 000 objects.
1.4 String constant pool
When a literal is used, the JVM checks the constant pool. If the same literal exists, the same reference is returned; otherwise a new String is created and placed in the pool.
String s1 = "hello";
String s2 = "hello";
String s3 = new String("hello");
System.out.println(s1 == s2); // true
System.out.println(s1 == s3); // false
System.out.println(s1.equals(s3)); // trueThe pool saves memory by sharing identical literals.
1.5 intern() method
Runtime‑generated strings can be added to the pool with intern():
String s1 = new String("hello") + new String("world");
s1.intern(); // puts "helloworld" into the pool
String s2 = "helloworld";
System.out.println(s1 == s2); // true on JDK7+Before JDK 7 the pool lived in PermGen; from JDK 7 onward it resides on the heap.
1.6 Advantages of String
Thread‑safe because immutable.
Can be used as Map key; hashCode is cached.
Works reliably with class loaders, resource paths, and as lock objects.
2. StringBuffer – thread‑safe mutable string
2.1 Implementation
public final class StringBuffer {
char[] value; // mutable, no final
}All public methods are synchronized , guaranteeing thread safety.
public synchronized StringBuffer append(String str) {
// …
return this;
}2.2 Cost of synchronization
Lock acquisition/release on each method call.
Lock contention in multithreaded scenarios.
Memory barriers force cache invalidation.
Micro‑benchmark (single thread, 100 000 appends):
StringBuilder ~8 ms
StringBuffer ~15 ms (≈2× slower)
2.3 When to use StringBuffer
Multithreaded environments where the same buffer is shared.
Scenarios requiring atomic string operations, e.g., log collectors.
public class LogCollector {
private StringBuffer buffer = new StringBuffer();
public synchronized void appendLog(String log) {
buffer.append(log).append("
");
}
public synchronized String flush() {
String result = buffer.toString();
buffer.setLength(0);
return result;
}
}3. StringBuilder – high‑performance mutable string
3.1 Implementation
public final class StringBuilder {
char[] value; // mutable
}Methods are not synchronized, so no locking overhead.
3.2 Performance comparison
Simple benchmark (100 000 iterations):
// String (very slow, seconds, may OOM)
// StringBuffer (~10‑20 ms)
// StringBuilder (~5‑10 ms) – up to 1000× faster than String3.3 Why it is fast
No lock – eliminates synchronization cost.
Mutable char[] – modifies the existing array.
Efficient growth: capacity roughly doubles.
3.4 Chaining
String result = new StringBuilder()
.append("hello")
.append(" ")
.append("world")
.toString();4. How to choose
Guidelines:
String literals → use String (immutable, safe, can be a map key).
Heavy single‑thread concatenation → use StringBuilder (best performance).
Shared mutable buffer in multithreaded code → use StringBuffer.
When the string is a method return value, a key in a map, or a lock object → use String.
4.2 Code refactoring example
Before (inefficient):
public String getUserInfo(User user) {
String info = "用户信息:";
info += "姓名:" + user.getName();
info += ",年龄:" + user.getAge();
// …
return info;
}After (efficient):
public String getUserInfo(User user) {
StringBuilder sb = new StringBuilder("用户信息:");
sb.append("姓名:").append(user.getName())
.append(",年龄:").append(user.getAge());
// …
return sb.toString();
}4.3 Real‑world case: logging
High‑frequency logging with string concatenation created tens of thousands of temporary objects per second, causing high CPU usage. Replacing concatenation with placeholder logging eliminated the overhead.
// Bad
logger.info("用户" + userId + "下单成功,订单号:" + orderId + ",金额:" + amount);
// Good
logger.info("用户{}下单成功,订单号:{},金额:{}", userId, orderId, amount);4.4 Alibaba Java coding guidelines
Mandatory – In loops, use StringBuilder.append . Mandatory – Use StringBuffer only when thread safety is required; otherwise prefer StringBuilder . Recommendation – Use String.charAt instead of regex for simple matches.
5. Underlying mechanisms
5.1 Expansion algorithm
When capacity is insufficient, StringBuilder expands roughly by 2×.
void expandCapacity(int minimumCapacity) {
int newCapacity = (value.length + 1) * 2; // roughly double
if (newCapacity < minimumCapacity) {
newCapacity = minimumCapacity;
}
char[] newValue = new char[newCapacity];
System.arraycopy(value, 0, newValue, 0, count);
value = newValue;
}Initial capacity is 16.
Growth factor reduces the number of reallocations.
Each expansion copies the array, incurring some cost.
5.2 Pre‑estimating capacity
Providing an estimated final length avoids repeated expansions:
StringBuilder sb = new StringBuilder(1024);
for (String data : largeDataList) {
sb.append(data);
}5.3 toString() caching
StringBuildercaches the char[] after the first toString() call; the cache is invalidated by any mutating operation.
private transient char[] toStringCache;
public String toString() {
if (toStringCache == null) {
toStringCache = Arrays.copyOfRange(value, 0, count);
}
return new String(toStringCache, true); // shares the same char[]
}5.4 reverse(), delete(), insert(), replace()
StringBuilder provides many mutable operations, e.g., reverse:
StringBuilder sb = new StringBuilder("hello");
sb.reverse(); // "olleh"6. Best practices
6.1 Five ways to concatenate strings
"+" operator – fine for a few literals.
String.concat() StringBuilder.append() StringBuffer.append() String.join()(JDK 8+)
Performance order (outside loops): + ≈ concat ≈ StringBuilder > StringBuffer.
Recommendations:
Simple concatenation → + or String.join.
Multiple appends → StringBuilder.
Multithreaded shared buffer → StringBuffer.
6.2 Correct use of StringUtils
// Empty check
StringUtils.isEmpty(""); // true
StringUtils.isBlank(" "); // true
// Safe concatenation
StringUtils.defaultString(null, "default"); // "default"
// Reverse
StringUtils.reverse("hello"); // "olleh"6.3 String splitting pitfalls
split()uses regex and can be costly. For high‑frequency splitting, prefer StringTokenizer or manual indexOf parsing.
6.4 Common performance traps
// Bad: concatenation in a loop
String result = "";
for (String item : list) {
result = result + item; // creates many temporary Strings
}
// Good: use StringBuilder
StringBuilder sb = new StringBuilder();
for (String item : list) {
sb.append(item);
}
String result = sb.toString();6.5 String comparison best practice
// Wrong
if (str1 == str2) { /* compares references */ }
// Correct
if (str1.equals(str2)) { /* compares content */ }
// Null‑safe
if ("constant".equals(str)) { /* safe */ }7. Interview questions
7.1 Is String a primitive type?
No. Java primitives are byte, short, int, long, float, double, char, boolean. String is an object.
7.2 Differences between String, StringBuffer, StringBuilder
Mutability: String immutable; StringBuffer & StringBuilder mutable.
Thread safety: String safe; StringBuffer synchronized; StringBuilder not safe.
Performance (concatenation): String (slow) < StringBuffer < StringBuilder (fastest).
Underlying storage: String uses final char[] (or byte[] after JDK 9); the others use mutable char[].
7.3 How many objects does new String("hello") create?
Either one or two objects: one in the heap, plus possibly one in the constant pool if the literal is not already present.
7.4 Why is String immutable?
String is declared final .
Internal array is final .
No method modifies the internal array.
All modifying operations return a new String.
7.5 Why cache hashCode?
String is frequently used as a HashMap key; caching avoids recomputing the hash for an immutable object, improving map performance.
Conclusion
Understanding the trade‑offs among String, StringBuilder, and StringBuffer lets you write Java code that is up to a thousand times faster and far more memory‑efficient. Remember: use String for constants, StringBuilder for single‑thread concatenation, and StringBuffer only when thread‑safe mutable buffers are required.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Java Tech Workshop
Focused on Java backend technologies, sharing fundamentals, multithreading, JVM, the Spring ecosystem, microservices, distributed systems, high concurrency, source‑code analysis, and practical experience. Continuously delivers high‑quality original content, interview guides, and learning roadmaps to help Java developers progress from beginner to advanced, enhancing technical skills and core competitiveness.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
