0

Given following code as sample:

Map<String, List<String>> warningMap = (Map<String, List<String>>) warningsOrErrorsMapVariable;
StringBuilder warningMessages = new StringBuilder(size?!);

for (Iterator<String> keyIterator = warningMap.keySet().iterator(); keyIterator.hasNext();) {
    Object key = keyIterator.next();
    if (!keyIterator.hasNext()) {
        isLastKey = true;
    }

    List<String> values = warningMap.get(key);

    if (values != null) {
        isLastValue = false;
        for (Iterator<String> valueIterator = values.iterator(); valueIterator.hasNext();) {
            String message = valueIterator.next();
            if (!valueIterator.hasNext()) {
                isLastValue = true;
            }               

            warningMessages.append(message);
            if (!(isLastKey && isLastValue)) {
                warningMessages.append(NEW_LINE);   
            }
        }
    }
}
return warningMessages.toString();

What the best practice to declare StringBuiler with proper size for compound structures?

One of options is predicting size o whole map by mapElems* listElems * predictedListElemSize, the other one is visiting each element and getting exact size, but both of them requires iterating thru all map twice - firstly to get size, secondly to get String values appened to buffer. Is it worth it?

Wont the whole "computing size of elems" be more time consuming than resizing buffer of builder when needed?

dantuch
  • 9,123
  • 6
  • 45
  • 68

3 Answers3

3

Sounds like a premature optimization to me. Default size isn't actually the best choice in most of the cases (rarely you use StringBuilder for strings shorter than 16 characters), so make a simple assumption.

Unless you really have a performance problem here, added complexity of computing the more-or-less accurate buffer size does not pay off. Just start with slightly bigger buffer 256 or 1024 bytes and forget about it.

In your code I would rather focus on an inefficient loop over Map: you are iterating over keySet() and fetching values on almost every iteration using Map.get(). Iterate using entrySet() from the beginning!

See also

Community
  • 1
  • 1
Tomasz Nurkiewicz
  • 334,321
  • 69
  • 703
  • 674
1

Unless you have a very good reason to get the StringBuilder size right, best practice is to use the default size, and let the StringBuilder grow as required.

The only "very good" reasons I can think of (off the top of my head) are:

  • your program is running under tight memory restrictions, and there won't be enough memory if the StringBuilder needs to expand, or

  • you have profiled your application, and the extra work incurred by expanding the StringBuilder is a critical performance bottleneck.

Otherwise, attempting to get the size right is an unnecessary optimization.


I believe that you wanted me to note this sentence in your edited question.

Wont the whole "computing size of elems" be more time consuming than resizing buffer of builder when needed?

It may or may not be. (I'd be inclined to think not in your example, since String.length() is cheap and O(1).) But the point I'm trying to make is that you shouldn't even be thinking about this unless you've got clear evidence that the optimization (and the extra code complexity) is really necessary.

Stephen C
  • 698,415
  • 94
  • 811
  • 1,216
1

Yes, multiplying the size of the map by the expected (estimated) average list size and by the expected (estimated) average String length should be enough.

The growing algorithm of StringBuilder isn't linear: it doubles each time, so you won't gain much by having a precise estimation of its final size.

I doubt this is where you would have any performance problem.

Side note: your code would be easier if you prepended the newline before each element except the first one, and used the foreach loop syntax. You should also iterate throught the entrySet() of the map, rather than iterating throught the keys and doing a lookup at each iteration:

boolean isFirst = true;
for (Map.Entry<String, List<String>> entry : warningMap.entrySet()) {
    List<String> warnings = entry.getValue();
    if (warnings != null) {
        for (String warning : warnings) {
            if (!isFirst) {
                warningMessages.append(NEW_LINE);
            }
            isFirst = false;
            warningMessages.append(warning);
        }
    }
}
JB Nizet
  • 678,734
  • 91
  • 1,224
  • 1,255