3030 * primitive. By using immutable entries, the entry objects can be shared between builders & maps
3131 * freely.
3232 *
33- * <p>This map lacks some features of a regular java.util.Map...
34- *
35- * <ul>
36- * <li>Entry object mutation
37- * <li>size tracking - falls back to computeSize
38- * <li>manipulating Map through the entrySet() or values()
39- * </ul>
40- *
41- * <p>Also lacks features designed for handling large maps...
33+ * <p>This map lacks the ability to mutate an entry via @link {@link Entry#setValue(Object)}.
34+ * Entries must be replaced by re-setting / re-putting the key which will create a new Entry object.
4235 *
36+ * <p>This map also lacks features designed for handling large long lived mutable maps...
4337 * <ul>
4438 * <li>bucket array expansion
4539 * <li>adaptive collision
5246 * When there is only a single Entry in a particular bucket, the Entry is stored into the bucket directly.
5347 * <p>
5448 * Because the Entry objects can be shared between multiple TagMaps, the Entry objects cannot contain
55- * form a link list to handle collisions.
49+ * directly form a link list to handle collisions.
5650 * <p>
5751 * Instead when multiple entries collide in the same bucket, a BucketGroup is formed to hold multiple entries.
5852 * But a BucketGroup is only formed when a collision occurs to keep allocation low in the common case of no collisions.
6155 * to hold the additional Entry-s. And the BucketGroup-s are connected via a linked list instead of the Entry-s.
6256 * <p>
6357 * This does introduce some inefficiencies when Entry-s are removed.
64- * In the current system, given that removals are rare, BucketGroups are never consolidated.
58+ * The assumption is that removals are rare, so BucketGroups are never consolidated.
6559 * However as a precaution if a BucketGroup becomes completely empty, then that BucketGroup will be
6660 * removed from the collision chain.
6761 */
@@ -125,44 +119,10 @@ public final int size() {
125119 return this .size ;
126120 }
127121
128- final int computeSize () {
129- Object [] thisBuckets = this .buckets ;
130-
131- int size = 0 ;
132- for (int i = 0 ; i < thisBuckets .length ; ++i ) {
133- Object curBucket = thisBuckets [i ];
134-
135- if (curBucket instanceof Entry ) {
136- size += 1 ;
137- } else if (curBucket instanceof BucketGroup ) {
138- BucketGroup curGroup = (BucketGroup ) curBucket ;
139- size += curGroup .sizeInChain ();
140- }
141- }
142- return size ;
143- }
144-
145122 @ Override
146123 public final boolean isEmpty () {
147124 return (this .size == 0 );
148125 }
149-
150- final boolean checkIfEmpty () {
151- Object [] thisBuckets = this .buckets ;
152-
153- for (int i = 0 ; i < thisBuckets .length ; ++i ) {
154- Object curBucket = thisBuckets [i ];
155-
156- if (curBucket instanceof Entry ) {
157- return false ;
158- } else if (curBucket instanceof BucketGroup ) {
159- BucketGroup curGroup = (BucketGroup ) curBucket ;
160- if (!curGroup .isEmptyChain ()) return false ;
161- }
162- }
163-
164- return true ;
165- }
166126
167127 @ Override
168128 public boolean containsKey (Object key ) {
@@ -399,7 +359,8 @@ public final void putAll(Map<? extends String, ? extends Object> map) {
399359 this .putAll ((TagMap ) map );
400360 } else {
401361 for (Map .Entry <? extends String , ?> entry : map .entrySet ()) {
402- this .put (entry .getKey (), entry .getValue ());
362+ // use set which returns a prior Entry rather put which may box a prior primitive value
363+ this .set (entry .getKey (), entry .getValue ());
403364 }
404365 }
405366 }
@@ -412,21 +373,29 @@ public final void putAll(Map<? extends String, ? extends Object> map) {
412373 * source TagMap
413374 */
414375 public final void putAll (TagMap that ) {
415- this .checkWriteAccess ();
416-
376+ this .checkWriteAccess ();
377+
378+ if ( this .size == 0 ) {
379+ this .putAllIntoEmptyMap (that );
380+ } else {
381+ this .putAllMerge (that );
382+ }
383+ }
384+
385+ private final void putAllMerge (TagMap that ) {
417386 Object [] thisBuckets = this .buckets ;
418387 Object [] thatBuckets = that .buckets ;
419388
420389 // Since TagMap-s don't support expansion, buckets are perfectly aligned
390+ // Check against both thisBuckets.length && thatBuckets.length is to help the JIT do bound check elimination
421391 for (int i = 0 ; i < thisBuckets .length && i < thatBuckets .length ; ++i ) {
422- Object thatBucket = thatBuckets [i ];
423-
424- // nothing in the other hash, just skip this bucket
425- if (thatBucket == null ) continue ;
392+ Object thatBucket = thatBuckets [i ];
393+
394+ // if nothing incoming, nothing to do
395+ if ( thatBucket == null ) continue ;
426396
427397 Object thisBucket = thisBuckets [i ];
428-
429- if (thisBucket == null ) {
398+ if (thisBucket == null ) {
430399 // This bucket is null, easy case
431400 // Either copy over the sole entry or clone the BucketGroup chain
432401
@@ -475,19 +444,21 @@ public final void putAll(TagMap that) {
475444 // there's already an entry w/ the same tag from the incoming TagMap
476445 // incoming entry clobbers the existing try, so we're done
477446 thisBuckets [i ] = thisNewGroup ;
478- this . size +=
479- thisNewGroupSize
480- - 1 ; // overlapping group - subtract one for clobbered existing entry
447+
448+ // overlapping group - subtract one for clobbered existing entry
449+ this . size += thisNewGroupSize - 1 ;
481450 } else if (thisNewGroup .insertInChain (thisHash , thisEntry )) {
482451 // able to add thisEntry into the existing groups
483452 thisBuckets [i ] = thisNewGroup ;
484- this .size +=
485- thisNewGroupSize ; // non overlapping group - existing entry already accounted for
453+
454+ // non overlapping group - existing entry already accounted for in this.size
455+ this .size += thisNewGroupSize ;
486456 } else {
487457 // unable to add into the existing groups
488458 thisBuckets [i ] = new BucketGroup (thisHash , thisEntry , thisNewGroup );
489- this .size +=
490- thisNewGroupSize ; // non overlapping group - existing entry already accounted for
459+
460+ // non overlapping group - existing entry already accounted for in this.size
461+ this .size += thisNewGroupSize ;
491462 }
492463 }
493464 } else if (thisBucket instanceof BucketGroup ) {
@@ -523,6 +494,32 @@ public final void putAll(TagMap that) {
523494 }
524495 }
525496 }
497+
498+ /*
499+ * Optimized special case method for the common case of putAll into an empty TagMap used by copy, etc
500+ */
501+ private final void putAllIntoEmptyMap (TagMap that ) {
502+ Object [] thisBuckets = this .buckets ;
503+ Object [] thatBuckets = that .buckets ;
504+
505+ // Check against both thisBuckets.length && thatBuckets.length is to help the JIT do bound check elimination
506+ for (int i = 0 ; i < thisBuckets .length && i < thatBuckets .length ; ++i ) {
507+ Object thatBucket = thatBuckets [i ];
508+
509+ // faster to explicitly null check first, then do instanceof
510+ if ( thatBucket == null ) {
511+ // do nothing
512+ } else if ( thatBucket instanceof BucketGroup ) {
513+ // if it is a BucketGroup, then need to clone
514+ BucketGroup thatGroup = (BucketGroup )thatBucket ;
515+
516+ thisBuckets [i ] = thatGroup .cloneChain ();
517+ } else { // if ( thatBucket instanceof Entry )
518+ thisBuckets [i ] = thatBucket ;
519+ }
520+ }
521+ this .size = that .size ;
522+ }
526523
527524 public final void fillMap (Map <? super String , Object > map ) {
528525 Object [] thisBuckets = this .buckets ;
@@ -615,7 +612,7 @@ public final Entry removeEntry(String tag) {
615612 /** Returns a mutable copy of this TagMap */
616613 public final TagMap copy () {
617614 TagMap copy = new TagMap ();
618- copy .putAll (this );
615+ copy .putAllIntoEmptyMap (this );
619616 return copy ;
620617 }
621618
@@ -745,6 +742,10 @@ public final void checkWriteAccess() {
745742 }
746743
747744 final void checkIntegrity () {
745+ // Decided to use if ( cond ) throw new IllegalStateException rather than assert
746+ // That was done to avoid the extra static initialization needed for an assertion
747+ // While that's probably an unnecessary optimization, this method is only called in tests
748+
748749 Object [] thisBuckets = this .buckets ;
749750
750751 for ( int i = 0 ; i < thisBuckets .length ; ++i ) {
@@ -789,6 +790,40 @@ final void checkIntegrity() {
789790 }
790791 }
791792
793+ final int computeSize () {
794+ Object [] thisBuckets = this .buckets ;
795+
796+ int size = 0 ;
797+ for (int i = 0 ; i < thisBuckets .length ; ++i ) {
798+ Object curBucket = thisBuckets [i ];
799+
800+ if (curBucket instanceof Entry ) {
801+ size += 1 ;
802+ } else if (curBucket instanceof BucketGroup ) {
803+ BucketGroup curGroup = (BucketGroup ) curBucket ;
804+ size += curGroup .sizeInChain ();
805+ }
806+ }
807+ return size ;
808+ }
809+
810+ final boolean checkIfEmpty () {
811+ Object [] thisBuckets = this .buckets ;
812+
813+ for (int i = 0 ; i < thisBuckets .length ; ++i ) {
814+ Object curBucket = thisBuckets [i ];
815+
816+ if (curBucket instanceof Entry ) {
817+ return false ;
818+ } else if (curBucket instanceof BucketGroup ) {
819+ BucketGroup curGroup = (BucketGroup ) curBucket ;
820+ if (!curGroup .isEmptyChain ()) return false ;
821+ }
822+ }
823+
824+ return true ;
825+ }
826+
792827 @ Override
793828 public final String toString () {
794829 return toPrettyString ();
0 commit comments