1
0

Part 6 extra: the decision table measured on real data

This commit is contained in:
2026-08-04 17:33:49 +00:00
parent 2ca907d245
commit 4ea4845baf

View File

@@ -0,0 +1,101 @@
package com.ankurm.jackson3.part6streaming;
import tools.jackson.core.JsonParser;
import tools.jackson.core.JsonToken;
import tools.jackson.core.json.JsonFactory;
import tools.jackson.core.type.TypeReference;
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.json.JsonMapper;
import java.io.File;
import java.nio.file.Files;
import java.util.List;
/**
* BEYOND THE POST — the decision table, measured.
*
* The post ends with a table claiming data binding "loads full object", tree model
* "loads full tree", and streaming uses "constant" memory. This generates a real
* file and measures all three so the table has numbers behind it.
*
* These are indicative single-shot measurements on one JVM, not JMH benchmarks —
* the ordering is the point, not the absolute figures.
*/
public class G04ThreeApproachesMeasured {
public record LogEntry(long id, String level, String message) { }
private static final int ENTRIES = 200_000;
public static void main(String[] args) throws Exception {
File file = generate();
System.out.println("input file : " + (file.length() / 1024 / 1024) + " MB, "
+ ENTRIES + " entries");
System.out.println();
JsonMapper mapper = JsonMapper.builder().build();
measure("data binding (readValue)", () -> {
List<LogEntry> all = mapper.readValue(file, new TypeReference<List<LogEntry>>() { });
return all.stream().filter(e -> e.level().equals("ERROR")).count();
});
measure("tree model (readTree)", () -> {
JsonNode root = mapper.readTree(file);
long n = 0;
for (JsonNode node : root) if ("ERROR".equals(node.path("level").asString())) n++;
return n;
});
measure("streaming (JsonParser)", () -> {
long n = 0;
JsonFactory factory = new JsonFactory();
try (JsonParser p = factory.createParser(tools.jackson.core.ObjectReadContext.empty(), file)) {
p.nextToken();
while (p.nextToken() != JsonToken.END_ARRAY) {
String level = null;
while (p.nextToken() != JsonToken.END_OBJECT) {
String f = p.currentName();
p.nextToken();
if ("level".equals(f)) level = p.getString();
}
if ("ERROR".equals(level)) n++;
}
}
return n;
});
file.delete();
}
private interface Counter { long count() throws Exception; }
private static void measure(String label, Counter counter) throws Exception {
Runtime rt = Runtime.getRuntime();
System.gc();
Thread.sleep(120);
long heapBefore = rt.totalMemory() - rt.freeMemory();
long start = System.nanoTime();
long errors = counter.count();
long ms = (System.nanoTime() - start) / 1_000_000;
long heapAfter = rt.totalMemory() - rt.freeMemory();
System.out.printf("%-28s errors=%-6d %5d ms heap delta %5d MB%n",
label, errors, ms, (heapAfter - heapBefore) / 1024 / 1024);
}
private static File generate() throws Exception {
File f = File.createTempFile("logs", ".json");
f.deleteOnExit();
StringBuilder sb = new StringBuilder("[");
for (int i = 0; i < ENTRIES; i++) {
if (i > 0) sb.append(',');
sb.append("{\"id\":").append(i)
.append(",\"level\":\"").append(i % 50 == 0 ? "ERROR" : "INFO")
.append("\",\"message\":\"event number ").append(i)
.append(" with some padding to make the payload realistic\"}");
}
sb.append(']');
Files.writeString(f.toPath(), sb);
return f;
}
}