String, StringBuffer, StringBuilder

The Java most natural class to deal with character strings is String. But you should be aware that its instances are immutable objects so, when some mutation is required, better to use StringBuffer or StringBuilder, when no synchronization is required.

I recently bumped in some code like this:
Properties p = new Properties(); // ?!
p.put("A", aString);
p.put("B", bString);
p.put("C", cString);
String x = "Current relevant parameters: " + p;
// ...
A creative, and unexpected, usage of Properties as an string formatter helper. There are here a couple of issues that should be considered. Firstly, the casual code reader could be puzzled by seeing a Properties object here. It is usually better writing code that is more readable, and using such a connotated class out of context doesn't look ideal. Secondly: performances. Properties class is not designed for string manipulation, so we shouldn't expect it being especially fast.

If we really want to use a collection in this way, it would be better to get a less specialized class, probably HashMap:
Map<String,String> p = new HashMap<String, String>();
p.put("A", aString);
p.put("B", bString);
p.put("C", cString);
String x = "Current relevant parameters: " + p;
// ...
This solution is substantially faster, and could replace the original implementation, if we don't mind about the loss of ordering in the values. Besides, notice the common practice of working with an interface (Map) instead of a concrete class (HashMap).

In any case, it would be better to use String, both from a logical point of view and performance-wise:
String s = "Current relevant parameters: {";
s += "A=" + aString + ", ";
s += "B=" + bString + ", ";
s += "C=" + cString + '}';
Still there is a performance bottleneck due to the immutable and thread-safe nature of String. Being immutable, each time we think we are changing the content of a String instance, we are actually creating a new String object and discarding the old one. Being thread-safe, each time we operate on it, a lock is used to ensure synchronization.

Using mutable StringBuffer would make our code a bit faster:
StringBuffer sb = new StringBuffer(150); // 1
sb.append("Current relevant parameters: {");
sb.append("A=").append(aString).append(", "); // 2
sb.append("B=").append(bString).append(", ");
sb.append("C=").append(cString).append('}');
String x = sb.toString(); // 3
1. Setting the initial capacity to the expected length of the resulting string saves some extra time otherwise spent resizing.
2. StringBuffer.append() returns a reference to this object, this is useful to chain multiple calls to methods of the same object, making the code more readable (well, this point could be questionable).
3. StringBuffer is unrelated to String, an explicit conversion is required.
If we can assume we are already in a thread safe part of our code, we can shift to the faster StringBuilder class:
StringBuilder sb = new StringBuilder(150);
sb.append("Current relevant parameters: {");
sb.append("A=").append(aString).append(", ");
sb.append("B=").append(bString).append(", ");
sb.append("C=").append(cString).append('}');
String x = sb.toString();
It looks just the same of StringBuffer, the difference is all under the hood, no synchronization is done.

I have done an approximative performance check, and I have found, as expected, that StringBuilder is the fastest solution. StringBuffer is 33% slower; String 50%; HashMap 100%; and using Properties is worse of more than a whopping 200%.

In brief, we could extrapolate a couple of guidelines:
  • use a class for what it is meant;
  • locking is expensive, don't do it if you don't need it.

No comments:

Post a Comment