Java中常用的Map实现类主要有:HashMap、HashTable、TreeMap、LinkedHashMap。
一:HashMap
HashMap介绍
HashMap的底层其实是“链表的数组”,即:每个元素其实存放着一个链表,链表存放着哈希值相同的对象们。HashMap是线程不安全的。
1)HashMap不是简单的用key的hashcode()值作为元素的存放下标的,而是通过二次哈希——把key的hashcode()传进HashMap自定义的hash(h)方法中计算位置(有可能大于数组长度了,所以还要对数组长取余),然后通过indexFor(h,len)方法计算出具体的数组下标(用按位与取代取余加快效率),尽量让key尽可能均匀的分配到数组上去,避免造成Hash堆积(某一下标处存放的链表过长)。
2)HashMap的冲突解决:如果有两个key的hashcode相同,那么经过hash(h)二次哈希后得到的数组索引是一样的,此时就要判断这两个key是否是同一对象:如果两个key的equals方法返回true,则说明两个key是同一对象,则此时把新值覆盖掉旧值;如果equals返回false,则说明是两个不同的key但分配到了同一数组索引位存放,则此时把新增的value添加到该索引位的链表尾。
3)插入元素时,通过key的hashcode()以及hash算法决定索引,通过equals()决定是插入链表还是覆盖原有值;
读取元素时,通过key的hashcode()以及hash算法找到索引,通过equals()遍历链表找到相对应的结点值;
(注:Map存储的是 键值对,不是单指用 key 来索引 value。而是用key 来索引 Entry!Entry就是我们说的 键值对!因此,get()时确定槽位后,在遍历Entry链表时才可以把查找的key与链表结点的key进行比较。)
主要源码:
1 //新建HashMap,其实是新建了一个数组 2 public HashMap(int initialCapacity, float loadFactor) { 3 // initialCapacity代表初始化HashMap的容量,它的最大容量是MAXIMUM_CAPACITY = 1 << 30。 4 if (initialCapacity < 0) 5 throw new IllegalArgumentException("Illegal initial capacity: " + 6 initialCapacity); 7 if (initialCapacity > MAXIMUM_CAPACITY) 8 initialCapacity = MAXIMUM_CAPACITY; 9 10 // loadFactor代表它的负载因子,默认是是DEFAULT_LOAD_FACTOR=0.75,用来计算threshold临界值的。 11 if (loadFactor <= 0 || Float.isNaN(loadFactor)) 12 throw new IllegalArgumentException("Illegal load factor: " + 13 loadFactor); 14 15 // Find a power of 2 >= initialCapacity 16 int capacity = 1; 17 while (capacity < initialCapacity) 18 capacity <<= 1; 19 20 this.loadFactor = loadFactor; 21 threshold = (int)(capacity * loadFactor); 22 table = new Entry[capacity];//创建数组 23 init(); 24 } 25 26 //插入元素 27 public V put(K key, V value) { 28 // HashMap允许存放null键和null值。 29 // 当key为null时,调用putForNullKey方法,将value放置在数组第一个位置。 30 if (key == null) 31 return putForNullKey(value); 32 // 根据key的hashCode重新计算hash值。 33 int hash = hash(key.hashCode()); 34 // 搜索指定hash值所对应table中的索引。 35 int i = indexFor(hash, table.length); 36 // 如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素。 37 for (Entry<K,V> e = table[i]; e != null; e = e.next) { 38 Object k; 39 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { 40 V oldValue = e.value; 41 e.value = value; 42 e.recordAccess(this); 43 return oldValue; 44 } 45 } 46 // 如果i索引处的Entry为null,表明此处还没有Entry。 47 // modCount记录HashMap中修改结构的次数 48 modCount++; 49 // 将key、value添加到i索引处。 50 addEntry(hash, key, value, i); 51 return null; 52 } 53 void addEntry(int hash, K key, V value, int bucketIndex) { 54 // 获取指定 bucketIndex 索引处的 Entry 55 Entry<K,V> e = table[bucketIndex]; 56 // 将新创建的 Entry 放入 bucketIndex 索引处,并让新的 Entry 指向原来的 Entry 57 table[bucketIndex] = new Entry<K,V>(hash, key, value, e); 58 // 如果 Map 中的 key-value 对的数量超过了极限 59 if (size++ >= threshold) 60 // 把 table 对象的长度扩充到原来的2倍。 61 resize(2 * table.length); 62 } 63 64 static int hash(int h) { 65 h ^= (h >>> 20) ^ (h >>> 12); 66 return h ^ (h >>> 7) ^ (h >>> 4); 67 } 68 69 static int indexFor(int h, int length) { 70 return h & (length-1); 71 } 72 //它通过 h & (table.length -1) 来得到该对象的保存位,而HashMap底层数组的长度总是 2 的n 次方,这是HashMap在速度上的优化。 73 //当length总是 2 的n次方时,h& (length-1)运算等价于对length取模,也就是h%length,但是&比%具有更高的效率。 74 75 //读取元素 76 public V get(Object key) { 77 if (key == null) 78 return getForNullKey(); 79 int hash = hash(key.hashCode()); 80 for (Entry<K,V> e = table[indexFor(hash, table.length)]; 81 e != null; 82 e = e.next) { 83 Object k; 84 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) 85 return e.value; 86 } 87 return null; 88 } 89 90 //HashMap数组扩容 91 void resize(int newCapacity) { 92 Entry[] oldTable = table; 93 int oldCapacity = oldTable.length; 94 //如果当前的数组长度已经达到最大值,则不在进行调整 95 if (oldCapacity == MAXIMUM_CAPACITY) { 96 threshold = Integer.MAX_VALUE; 97 return; 98 } 99 //根据传入参数的长度定义新的数组 100 Entry[] newTable = new Entry[newCapacity]; 101 //按照新的规则,将旧数组中的元素转移到新数组中 102 transfer(newTable); 103 table = newTable; 104 //更新临界值 105 threshold = (int)(newCapacity * loadFactor); 106 } 107 108 //旧数组中元素往新数组中迁移 109 void transfer(Entry[] newTable) { 110 //旧数组 111 Entry[] src = table; 112 //新数组长度 113 int newCapacity = newTable.length; 114 //遍历旧数组 115 for (int j = 0; j < src.length; j++) { 116 Entry<K,V> e = src[j]; 117 if (e != null) { 118 src[j] = null; 119 do { 120 Entry<K,V> next = e.next; 121 int i = indexFor(e.hash, newCapacity); 122 e.next = newTable[i]; 123 newTable[i] = e; 124 e = next; 125 } while (e != null); 126 } 127 } 128 }