1 package com.fasterxml.jackson.databind.util; 2 3 import java.util.*; 4 5 /** 6 * Specialized lookup class that implements functionality similar to 7 * {@link java.util.Map}, but for special case of key always being 8 * {@link java.lang.String} and using more compact (and memory-access 9 * friendly) hashing scheme. Assumption is also that keys are typically 10 * intern()ed. 11 *<p> 12 * Generics are not used to avoid bridge methods and since these maps 13 * are not exposed as part of external API. 14 * 15 * @since 2.6 16 */ 17 public final class CompactStringObjectMap 18 implements java.io.Serializable // since 2.6.2 19 { 20 private static final long serialVersionUID = 1L; 21 22 /** 23 * Shared instance that can be used when there are no contents to Map. 24 */ 25 private final static CompactStringObjectMap EMPTY = new CompactStringObjectMap(1, 0, 26 new Object[4]); 27 28 private final int _hashMask, _spillCount; 29 30 private final Object[] _hashArea; 31 CompactStringObjectMap(int hashMask, int spillCount, Object[] hashArea)32 private CompactStringObjectMap(int hashMask, int spillCount, Object[] hashArea) 33 { 34 _hashMask = hashMask; 35 _spillCount = spillCount; 36 _hashArea = hashArea; 37 } 38 construct(Map<String,T> all)39 public static <T> CompactStringObjectMap construct(Map<String,T> all) 40 { 41 if (all.isEmpty()) { // can this happen? 42 return EMPTY; 43 } 44 45 // First: calculate size of primary hash area 46 final int size = findSize(all.size()); 47 final int mask = size-1; 48 // and allocate enough to contain primary/secondary, expand for spillovers as need be 49 int alloc = (size + (size>>1)) * 2; 50 Object[] hashArea = new Object[alloc]; 51 int spillCount = 0; 52 53 for (Map.Entry<String,T> entry : all.entrySet()) { 54 String key = entry.getKey(); 55 56 // 09-Sep-2019, tatu: [databind#2309] skip `null`s if any included 57 if (key == null) { 58 continue; 59 } 60 61 int slot = key.hashCode() & mask; 62 int ix = slot+slot; 63 64 // primary slot not free? 65 if (hashArea[ix] != null) { 66 // secondary? 67 ix = (size + (slot >> 1)) << 1; 68 if (hashArea[ix] != null) { 69 // ok, spill over. 70 ix = ((size + (size >> 1) ) << 1) + spillCount; 71 spillCount += 2; 72 if (ix >= hashArea.length) { 73 hashArea = Arrays.copyOf(hashArea, hashArea.length + 4); 74 } 75 } 76 } 77 hashArea[ix] = key; 78 hashArea[ix+1] = entry.getValue(); 79 } 80 return new CompactStringObjectMap(mask, spillCount, hashArea); 81 } 82 findSize(int size)83 private final static int findSize(int size) 84 { 85 if (size <= 5) { 86 return 8; 87 } 88 if (size <= 12) { 89 return 16; 90 } 91 int needed = size + (size >> 2); // at most 80% full 92 int result = 32; 93 while (result < needed) { 94 result += result; 95 } 96 return result; 97 } 98 find(String key)99 public Object find(String key) { 100 int slot = key.hashCode() & _hashMask; 101 int ix = (slot<<1); 102 Object match = _hashArea[ix]; 103 if ((match == key) || key.equals(match)) { 104 return _hashArea[ix+1]; 105 } 106 return _find2(key, slot, match); 107 } 108 _find2(String key, int slot, Object match)109 private final Object _find2(String key, int slot, Object match) 110 { 111 if (match == null) { 112 return null; 113 } 114 int hashSize = _hashMask+1; 115 int ix = hashSize + (slot>>1) << 1; 116 match = _hashArea[ix]; 117 if (key.equals(match)) { 118 return _hashArea[ix+1]; 119 } 120 if (match != null) { // _findFromSpill(...) 121 int i = (hashSize + (hashSize>>1)) << 1; 122 for (int end = i + _spillCount; i < end; i += 2) { 123 match = _hashArea[i]; 124 if ((match == key) || key.equals(match)) { 125 return _hashArea[i+1]; 126 } 127 } 128 } 129 return null; 130 } 131 132 /** 133 * @since 2.9 134 */ findCaseInsensitive(String key)135 public Object findCaseInsensitive(String key) { 136 for (int i = 0, end = _hashArea.length; i < end; i += 2) { 137 Object k2 = _hashArea[i]; 138 if (k2 != null) { 139 String s = (String) k2; 140 if (s.equalsIgnoreCase(key)) { 141 return _hashArea[i+1]; 142 } 143 } 144 } 145 return null; 146 } 147 keys()148 public List<String> keys() { 149 final int end = _hashArea.length; 150 List<String> keys = new ArrayList<String>(end >> 2); 151 for (int i = 0; i < end; i += 2) { 152 Object key = _hashArea[i]; 153 if (key != null) { 154 keys.add((String) key); 155 } 156 } 157 return keys; 158 } 159 } 160