3636 * <li>TreeMap — when a custom Comparator is needed (see CaseInsensitiveMapBenchmark)
3737 * <li>LinkedHashMap — only when insertion-order iteration is required; cost is paid at
3838 * construction and in per-entry memory
39+ * <li>FlatHashtable — a find-or-create table over self-contained entries (not a general Map: no
40+ * arbitrary put/remove). Compared here on the ops it does support — build, get, iterate —
41+ * where its self-contained entry stores the value <i>unboxed</i> (one object per key, no
42+ * {@code Integer}). Fixed-capacity, so it must be sized to the working set up front.
3943 * </ul>
4044 *
4145 * <p><b>Uncontended synchronization tax.</b> A {@link Collections#synchronizedMap} case is included
@@ -81,12 +85,54 @@ static TagMap fillTagMap(TagMap map) {
8185 return map ;
8286 }
8387
88+ // FlatHashtable is a find-or-create table over self-contained entries — no arbitrary put/remove,
89+ // so only the comparable ops appear here: build (via the comparison-free insert of distinct
90+ // keys),
91+ // get, and iterate. Its entry carries the value UNBOXED (no Integer), one object per key.
92+ static final class IntEntry {
93+ final String key ;
94+ final int value ;
95+
96+ IntEntry (String key , int value ) {
97+ this .key = key ;
98+ this .value = value ;
99+ }
100+ }
101+
102+ static final class IntEntryKeyStrategy extends FlatHashtable .StringKeyStrategy <IntEntry > {
103+ // Canonical exact-typed singleton: one instance, private ctor => the static-poly discipline is
104+ // enforced by the class, not left to each caller to declare correctly.
105+ static final IntEntryKeyStrategy INSTANCE = new IntEntryKeyStrategy ();
106+
107+ private IntEntryKeyStrategy () {}
108+
109+ @ Override
110+ public boolean matches (String key , IntEntry entry ) {
111+ return key .equals (entry .key );
112+ }
113+
114+ @ Override
115+ public long hashOf (IntEntry entry ) {
116+ return hash (entry .key );
117+ }
118+ }
119+
120+ static IntEntry [] newFilledFlat () {
121+ // Sized to the key count (FlatHashtable is fixed-capacity, no resize): load factor <= 0.5.
122+ IntEntry [] table = FlatHashtable .create (IntEntry .class , INSERTION_KEYS .length );
123+ for (int i = 0 ; i < INSERTION_KEYS .length ; ++i ) {
124+ FlatHashtable .insert (table , new IntEntry (INSERTION_KEYS [i ], i ), IntEntryKeyStrategy .INSTANCE );
125+ }
126+ return table ;
127+ }
128+
84129 // Per-thread prebuilt maps for the read + clone benchmarks (built once per trial, per thread).
85130 HashMap <String , Integer > hashMap ;
86131 Map <String , Integer > synchronizedHashMap ;
87132 TreeMap <String , Integer > treeMap ;
88133 LinkedHashMap <String , Integer > linkedHashMap ;
89134 TagMap tagMap ;
135+ IntEntry [] flatTable ;
90136 int index = 0 ;
91137
92138 @ Setup (Level .Trial )
@@ -99,6 +145,7 @@ public void setUp() {
99145 linkedHashMap = new LinkedHashMap <>();
100146 fill (linkedHashMap );
101147 tagMap = fillTagMap (TagMap .create ());
148+ flatTable = newFilledFlat ();
102149 }
103150
104151 String nextLookupKey () {
@@ -159,6 +206,11 @@ public TagMap create_tagMap_via_ledger() {
159206 return ledger .build ();
160207 }
161208
209+ @ Benchmark
210+ public IntEntry [] create_flatHashtable () {
211+ return newFilledFlat ();
212+ }
213+
162214 // ---- copy ----
163215
164216 @ Benchmark
@@ -200,6 +252,11 @@ public Integer get_synchronizedHashMap() {
200252 return synchronizedHashMap .get (nextLookupKey ());
201253 }
202254
255+ @ Benchmark
256+ public IntEntry get_flatHashtable () {
257+ return FlatHashtable .get (flatTable , nextLookupKey (), IntEntryKeyStrategy .INSTANCE );
258+ }
259+
203260 @ Benchmark
204261 public void iterate_hashMap (Blackhole blackhole ) {
205262 for (Map .Entry <String , Integer > entry : hashMap .entrySet ()) {
@@ -219,4 +276,16 @@ public void iterate_synchronizedHashMap(Blackhole blackhole) {
219276 }
220277 }
221278 }
279+
280+ @ Benchmark
281+ public void iterate_flatHashtable (Blackhole blackhole ) {
282+ // Context-passing forEach: blackhole rides through as context, so the lambda doesn't capture.
283+ FlatHashtable .forEach (
284+ flatTable ,
285+ blackhole ,
286+ (bh , e ) -> {
287+ bh .consume (e .key );
288+ bh .consume (e .value );
289+ });
290+ }
222291}
0 commit comments