0

Suppose I have a list of Book where the number of books can be quite large. I'm logging the isbn of those books. I've come up with two approaches, would there be any performance difference / which approach is considered as better ?

My concern with 2) will be whether the length of String is too long to become an issue. Refer to How many characters can a Java String have?, it's not likely it will hit the max number of characters, but I'm not sure on the point about "Half your maximum heap size", and whether it's actually a good practice to construct a long String.

  1. Convert to list of String
List<Book> books = new ArrayList<>();
books.add(new Book().name("book1").isbn("001"));
books.add(new Book().name("book2").isbn("002"));

if (books != null && books.size() > 0) {
  List<String> isbns = books.stream()
                            .map(Book:getIsbn)
                            .collect(Collectors.toList());
  logger.info("List of isbn = {}", isbns);
} else {
  logger.info("Empty list of isbn");
}
  1. Using StringBuilder and concatenate as one long String
List<Book> books = new ArrayList<>();
books.add(new Book().name("book1").isbn("001"));
books.add(new Book().name("book2").isbn("002"));

if (books != null && books.size() > 0) {
  StringBuilder strB = new StringBuilder();
  strB.append("List of isbn: ");
  books.stream()
       .forEach(book -> {
         strB.append(book.getIsbn());
         strB.append("; ");
       });
  logger.info("List of isbn = {}", strB.toString());
} else {
  logger.info("Empty list of isbn");
}

Federico klez Culloca
  • 26,308
  • 17
  • 56
  • 95
once
  • 536
  • 7
  • 21
  • If the list of isbns is really long you're going to print a string that's hard to read anyway, so why not just print one per line and nip the problem in the bud? – Federico klez Culloca Sep 09 '22 at 06:51
  • 1
    Given that you have a list of books, and each book contains its ISBN, why do you need to extract the ISBNs into another list at all? You need to clarify why you are doing this. – tgdavies Sep 09 '22 at 06:52
  • So you have a requirement to log all of the ISBNs in a single log statement? – tgdavies Sep 09 '22 at 06:54
  • Because in the log, I would only want to log the ISBNs – once Sep 09 '22 at 06:57
  • Just would want to log those info, but no requirement to log in a single log statement – once Sep 09 '22 at 07:01
  • 1
    ISBNs are 13 chars long. at 2 GB, with a delimiter, that's 153M books. Do you really want to write that to a log file? This feels like you're doing something wrong, like you're using a log file when you really want a pubsub system, DB, or file. – David Ehrmann Sep 09 '22 at 07:03
  • If you use a `List` with your first variant, it also will very likely create first a String of all list entries, because probably the logger will use the `toString()` method to make the list loggable. Not saying that your second solution is any better. As you are trying to tackle an optimization problem: Did you get it running with a small number of ISBNs at all? First you shall find a solution that produces the output you need. Then start optimizing for an increasing number of ISBNs. – cyberbrain Sep 09 '22 at 07:15
  • We can’t answer your question about performance. You will have to make your own measurements. – Ole V.V. Sep 09 '22 at 07:23
  • If you really want to log all of the ISBNs, just do `books.stream().forEach(book -> logger.info(book.getIsbn()))`. This allocates little extra memory. But spamming your logs with all that data seems very strange. – tgdavies Sep 09 '22 at 07:24
  • 1
    Or a bit simpler `books.forEach(book -> logger.info(book.getIsbn()));`. @tgdavies – Ole V.V. Sep 09 '22 at 14:25

1 Answers1

1

... but I'm not sure on the point about "Half your maximum heap size"

The JVM heap has a maximum size set by command line options, or by defaults. If you fill the heap, your JVM will throw and OutOfMemoryError (OOME) and that will typically cause your application to terminate (or worse!).

When you construct a string using a StringBuilder the builder uses a roughly exponential resizing strategy. When you fill the builder's buffer, it allocates a new one with double the size. But the old and new buffers need to exist at the same time. So when the buffer is between 1/2 and 2/3rds of the size of the entire heap, and the buffer fills up, the StringBuilder will attempt allocate a new buffer that is larger than the remaining available space and an OOME will ensue.


Having said that, assembling a single string containing a huge amount of data is going to be bad for performance even if you don't trigger an OOME. A better idea is to write the data via a buffers OutputStream or Writer.

This may be problematic if you are outputting the data via a Logger. But I wouldn't try to use a Logger for that. And I certainly wouldn't try to do it using a single Logger.info(...) call.

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