死嗑Java容器—HashMap

it2022-05-05  153

深入了解HashMap。

环境

eclipse 2019-03 (4.11.0)jdk-12.0.1

eclipse中查看源码的方法:按住Ctrl键,鼠标左键单击代码(更详细请百度)。

容器:在Java中,“集合”有另外的用途,所以ArrayList、HashMap等皆称为容器类,其创建的一个对象就是一个容器。

简介

HashMap是一个基于数组、单向链表以及树结构的Map,存储的元素中key值是唯一的,value没有要求。

HashMap源码分析

1、class声明

public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {...}

源码分析:HashMap继承自AbstractMap类,实现了Map接口,Cloneable接口以及Serializable接口

如果查看AbstractMap源码,就会发现它也实现了Map接口。那为什么在HashMap中继承了AbstractMap后还要实现Map接口呢?网上有很多种答案,觉得最站得住脚的有两点:1)、添加Map接口是为了Class类的getInterfaces这个方法可以直接返回Map接口。2)、这就是写法上的错误,并没有什么深意,这是得票最高的答案,回答者称曾问过此段代码的设计者Josh Bloch。

实现Cloneable接口(标识接口),该接口不包含任何方法,实现它仅仅是为了指示可以使用Object类中的clone()方法来进行克隆。

实现Serializable接口(标识接口),该接口不包含任何方法,实现它表示该类可以进行序列化。该接口表示所有的非transient和非static字段都可以被序列化。如果要实现transient字段与static字段的序列化保存,必须声明writeObject和readObject方法

2、静态常量字段

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 static final int MAXIMUM_CAPACITY = 1 << 30; static final float DEFAULT_LOAD_FACTOR = 0.75f; static final int TREEIFY_THRESHOLD = 8; static final int UNTREEIFY_THRESHOLD = 6; static final int MIN_TREEIFY_CAPACITY = 64;

源码分析:

DEFAULT_INITIAL_CAPACITY:容器的默认初始化容量,值为16。

MAXIMUM_CAPACITY :容器的最大容量,值为2的30次方。

DEFAULT_LOAD_FACTOR :默认的容量因子,值为0.75f。用于计算容器的实际可用容量(如:16*0.75f=12。原本初始化容器时指定了大小为16,但实际能用的只有12)。

TREEIFY_THRESHOLD :阈值,值为8。当桶上的链表数大于该值时,转红黑数。

UNTREEIFY_THRESHOLD: 阈值,值为6。当桶上的链表数小于该值时,转链表。

MIN_TREEIFY_CAPACITY :红黑树的最小容量,本为4 x TREEIFY_THRESHOLD =32,但为了避免resizing和treeifcation tresholds 则设为了64。

3、非静态字段

transient Node<K,V>[] table; transient Set<Map.Entry<K,V>> entrySet; transient int size; transient int modCount; int threshold; final float loadFactor;

源码分析:

table:装Node对象的列表,也叫桶。经hash算法计算后的值如果没有重复,则对象都是放于此。

entrySet:Set容器变量,存储一系列Map.Entry对象,主要用于实现元素遍历。

size:实际已经使用的容器大小,即容器中已经存在的元素个数。

modCount:记录HashMap结构性变化的次数。

threshold:容器大小,默认为16*0.75f=12。

loadFactor:容量因子,用于计算容器的真实大小(threshold)。

4、构造器

public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; this.threshold = tableSizeFor(initialCapacity); } public HashMap(int initialCapacity) { this(initialCapacity, DEFAULT_LOAD_FACTOR); } public HashMap() { this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted } public HashMap(Map<? extends K, ? extends V> m) { this.loadFactor = DEFAULT_LOAD_FACTOR; putMapEntries(m, false); }

 源码分析:HashMap有四个构造方法可供选择

HashMap(int initialCapacity, float loadFactor):该方法要求传入两个参数。容器的初始大小和容量因子。

HashMap(int initialCapacity):该方法只要求传入初始容量。而容量因子采用了默认的0.75。

HashMap():该方法为无参构造器。容量大小默认16,容量因子默认0.75。

HashMap(Map<? extends K, ? extends V> m):该方法根据已有的Map容器对象创建新的对象。

构造方法中涉及到容器大小参数的问题,自定义容量大小的构造器方法中会调用方法tableSizeFor(源码如下)来处理数据,最终保证容量大小是2的n次方(如自定义容量大小为7,则数据处理会将其修改为2的3次方,即8)。所以HashMap容器的最小容量可以定义为2的0次方,即1。

static final int tableSizeFor(int cap) { int n = -1 >>> Integer.numberOfLeadingZeros(cap - 1); return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; }

5、数据存储结构

static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; Node(int hash, K key, V value, Node<K,V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } public final K getKey() { return key; } public final V getValue() { return value; } public final String toString() { return key + "=" + value; } public final int hashCode() { return Objects.hashCode(key) ^ Objects.hashCode(value); } public final V setValue(V newValue) { V oldValue = value; value = newValue; return oldValue; } public final boolean equals(Object o) { if (o == this) return true; if (o instanceof Map.Entry) { Map.Entry<?,?> e = (Map.Entry<?,?>)o; if (Objects.equals(key, e.getKey()) && Objects.equals(value, e.getValue())) return true; } return false; } }

源码分析:此为HashMap中存储的数据的结构

每一个数据就是一个Node对象,实现了Map.Entry<K,V>接口,Entry是Map接口中定义的一个接口,该接口中定义的一些方法,都在Node中被实现。每一个数据主要包含四个字段

hash(经Hash算法获得的值)

key(具有唯一性,与字段value构成一组关系)

value

next(也是Node对象,主要用于hash值相同时将数据保存为链表)

6、hash算法

static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }

源码分析:哈希算法(确保长度为n的数组经过(n-1)&hash后的值正好为0—(n-1))

hash()方法用于计算key的hash值。如果key为null,返回0。否则将key的hashCode值异或上其右移了16位的值(如"a"的hashCode()为97,则97^(97>>>16)=97^(97/(int)Math.pow(2,16))=97)

当然只看此方法是看不出有什么用的,需要结合HashMap在保存、获取元素时计算元素索引的源码(下面是getNode方法中的部分源码)。在计算数据位置时,用到了hash值,即tab[(n-1)&hash] (n是数组table的真实大小),这里的hash值就是上面根据key计算出来的hash值,利用该算法可以有效减小不同数据在table中保存位置相同的情况。

并且,通过哈希算法可以保证只要元素的hash值不变,当数组table在扩容(每次扩容都是原来的两倍)的时候。(n-1)&hash都能得到唯一值(如:(8-1)&97是等于(16-1)&97的)。避免了重新分配数组大小时已有数据的索引被重新定义。

final Node<K,V> getNode(int hash, Object key) { Node<K,V>[] tab; Node<K,V> first, e; int n; K k; if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { ... }

7、添加/修改元素

public V put(K key, V value) { return putVal(hash(key), key, value, false, true); } final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }

源码分析:put()方法实现添加/修改数据。如果已经存在key值,则使用新的value替换旧的value并返回旧的value。如果不存在key值,添加完数据后返回null。

第一步检查数组table是否为空,如果是将调用resize方法为其分配存储空间进行初始化。

if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length;

第二步使用hash算法的结果来获得数据在数组table中的位置索引。再判断该位置是否已经存在数据,如果没有数据,直接添加数据。

if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null);

如果该位置已经存在数据,则

首先判断若确定旧数据与新数据的hash以及key均相同,直接用新value替换旧value,返回oldValue,程序结束。若确定旧数据类型是TreeNode,将数据添加到树结构中。之后进入第三步以上两步都没有执行,则表明旧数据与新数据的hash相同但key值不相同,即将新数据挂载于旧数据之下形成链表。此时有判断,判断其链上的数据量是否超过8,超过就将其链表结构转变成红黑树结构。之后进入第三步 else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; }

第三步由于添加了新数据,用于记录结构改变次数的modCount自增1(增加元素会自增,修改元素不会),用于记录已有数据个数的size自增1并判断其是否超过了能存储的实际元素个数threshold

++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null;

8、获取元素

public V get(Object key) { Node<K,V> e; return (e = getNode(hash(key), key)) == null ? null : e.value; } final Node<K,V> getNode(int hash, Object key) { Node<K,V>[] tab; Node<K,V> first, e; int n; K k; if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) { if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k)))) return first; if ((e = first.next) != null) { if (first instanceof TreeNode) return ((TreeNode<K,V>)first).getTreeNode(hash, key); do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null); } } return null; }

源码分析:get(Object key)方法实现获取元素。若不存在要获取的元素 返回null,存在即返回元素中的value值

代码中根据key以及key对应的hash值进行数据查找。

首先通过判断确定数据表table不为null,且通过hash值查找数据位置索引。

if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null)

如果获得的数据刚好key以及hash都符合要求,返回该数据的value,程序结束。

if (first.hash == hash && // always check first node ((k = first.key) == key || (key != null && key.equals(k)))) return first;

如果获得的数据不符合要求,但由于hash是相同的,所以继续判断数据的next是否存在数据。

if ((e = first.next) != null)

确认存在则判断数据类型是否是TreeNode,若是则表明是红黑树结构,进入红黑树结构中查询数据并返回,程序结束。

if (first instanceof TreeNode) return ((TreeNode<K,V>)first).getTreeNode(hash, key);

若不是红黑树结构,则表明该结构仅仅是一个链表结构,循环查找数据,找到后返回,程序结束。

do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) return e; } while ((e = e.next) != null);

9、移除元素

public V remove(Object key) { Node<K,V> e; return (e = removeNode(hash(key), key, null, false, true)) == null ? null : e.value; } final Node<K,V> removeNode(int hash, Object key, Object value, boolean matchValue, boolean movable) { Node<K,V>[] tab; Node<K,V> p; int n, index; if ((tab = table) != null && (n = tab.length) > 0 && (p = tab[index = (n - 1) & hash]) != null) { Node<K,V> node = null, e; K k; V v; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) node = p; else if ((e = p.next) != null) { if (p instanceof TreeNode) node = ((TreeNode<K,V>)p).getTreeNode(hash, key); else { do { if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) { node = e; break; } p = e; } while ((e = e.next) != null); } } if (node != null && (!matchValue || (v = node.value) == value || (value != null && value.equals(v)))) { if (node instanceof TreeNode) ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable); else if (node == p) tab[index] = node.next; else p.next = node.next; ++modCount; --size; afterNodeRemoval(node); return node; } } return null; }

源码分析:remove(Object key)方法实现删除元素。若要被删除的元素不存在就返回null,否则删除成功后返回被删除的元素的value

第一步找到要移除的数据的确切位置

首先依然是判断确定数据表table不为null,且通过hash值查找数据位置索引。

if ((tab = table) != null && (n = tab.length) > 0 && (p = tab[index = (n - 1) & hash]) != null)

接着若获得的数据刚好hash以及key均符合要求,找到数据,进入第二步。

if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k)))) node = p;

若获得的数据类型是TreeNode,进入红黑树结构中查找数据,找到后进入第二步。

if (p instanceof TreeNode) node = ((TreeNode<K,V>)p).getTreeNode(hash, key);

若以上两个判断均不成立,则表明数据存储方式是链式结构,循环查找数据,找到后进入第二步。

do { if (e.hash == hash &&((k = e.key) == key ||(key != null && key.equals(k)))) { node = e; break; } p = e; } while ((e = e.next) != null);

第二步移除目标元素

若数据类型为TreeNode,则调用对应的树形结构移除数据的方法(其中包括当树中的数据量低于6时,结构转变为链表等操作)。若数据就在数组table中,则用该位置的数据的next替换掉要移除的数据(可能node.next为null,但是没有任何问题)。若数据在链表结构中,进行对应的操作。

if (node != null && (!matchValue || (v = node.value) == value || (value != null && value.equals(v)))) { if (node instanceof TreeNode) ((TreeNode<K,V>)node).removeTreeNode(this, tab, movable); else if (node == p) tab[index] = node.next; else p.next = node.next; ++modCount;//删除数据,结构改变 --size;//数据量减一 afterNodeRemoval(node); return node; }

10、获得容器中已有元素个数

public int size() { return size; }

源码分析:size()方法返回元素个数

为什么说是元素个数而不是数组table大小呢?因为元素个数是永远小于table的真实大小的(容量因子*数组大小=size的最大值,在resize()方法中可以很明确的看到这三者的关系)

11、重新分配容量大小

final Node<K,V>[] resize() { Node<K,V>[] oldTab = table; int oldCap = (oldTab == null) ? 0 : oldTab.length; int oldThr = threshold; int newCap, newThr = 0; if (oldCap > 0) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold } else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr; else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY; newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); } if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); } threshold = newThr; @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { // preserve order Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } } return newTab; }

源码分析:resize()用于设置容器大小,包括初始化数组table大小和扩容,扩容一次扩大为原来的两倍。返回已经经过扩容或者初始化的新数组newTab(Node<K,V>)。

oldCap/newCap:数组table容量oldThr/newThr:元素容量,能添加数据元素的最大个数

第一步判断应该初始化数组还是进行扩容

若oldCap大于0(即数组不为null),这时若容量在允许的范围内(最大容量2的30次方),就对数组大小进行扩容(采用移位运算oldCap<<1)为原来的两倍,若扩容后的大小也低于最大容量,则将实际容量也进行扩容(oldThr<<1)为原来的两倍。进入第二步

if (oldCap > 0) { if (oldCap >= MAXIMUM_CAPACITY) { threshold = Integer.MAX_VALUE; return oldTab; } else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) newThr = oldThr << 1; // double threshold }

若oldCap等于0(即数组为空),但是oldThr>0(即初始化容器时设定了初始容量),将该值赋给newCap(用于设置数组的大小)。进入第二步。

else if (oldThr > 0) // initial capacity was placed in threshold newCap = oldThr;

若oldCap等于0且oldThr也等于0(即数组为空,且创建HashMap对象时调用了无参构造器),则设定newCap与newThr为默认值。进入第二步。

else { // zero initial threshold signifies using defaults newCap = DEFAULT_INITIAL_CAPACITY;//16 newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//0.75*16=12 }

第二步若newThr等于0,为其赋值

if (newThr == 0) { float ft = (float)newCap * loadFactor; newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ? (int)ft : Integer.MAX_VALUE); }

第三步重新创建一个大小为newCap的数组newTab,,并赋给table。将原数组中的数据转移到新数组中(这部分是本方法中最耗时的,故如果有可能,一定要在创建HashMap对象时设置好初始容量)

@SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap]; table = newTab; if (oldTab != null) { for (int j = 0; j < oldCap; ++j) { Node<K,V> e; if ((e = oldTab[j]) != null) { oldTab[j] = null; if (e.next == null) newTab[e.hash & (newCap - 1)] = e; else if (e instanceof TreeNode) ((TreeNode<K,V>)e).split(this, newTab, j, oldCap); else { // preserve order Node<K,V> loHead = null, loTail = null; Node<K,V> hiHead = null, hiTail = null; Node<K,V> next; do { next = e.next; if ((e.hash & oldCap) == 0) { if (loTail == null) loHead = e; else loTail.next = e; loTail = e; } else { if (hiTail == null) hiHead = e; else hiTail.next = e; hiTail = e; } } while ((e = next) != null); if (loTail != null) { loTail.next = null; newTab[j] = loHead; } if (hiTail != null) { hiTail.next = null; newTab[j + oldCap] = hiHead; } } } } }

12、自定义序列化

private void writeObject(java.io.ObjectOutputStream s) throws IOException { int buckets = capacity(); // Write out the threshold, loadfactor, and any hidden stuff s.defaultWriteObject(); s.writeInt(buckets); s.writeInt(size); internalWriteEntries(s); } private void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { // Read in the threshold (ignored), loadfactor, and any hidden stuff s.defaultReadObject(); reinitialize(); if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new InvalidObjectException("Illegal load factor: " + loadFactor); s.readInt(); // Read and ignore number of buckets int mappings = s.readInt(); // Read number of mappings (size) if (mappings < 0) throw new InvalidObjectException("Illegal mappings count: " + mappings); else if (mappings > 0) { // (if zero, use defaults) // Size the table using given load factor only if within // range of 0.25...4.0 float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f); float fc = (float)mappings / lf + 1.0f; int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ? DEFAULT_INITIAL_CAPACITY : (fc >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : tableSizeFor((int)fc)); float ft = (float)cap * lf; threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ? (int)ft : Integer.MAX_VALUE); // Check Map.Entry[].class since it's the nearest public type to // what we're actually creating. SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Map.Entry[].class, cap); @SuppressWarnings({"rawtypes","unchecked"}) Node<K,V>[] tab = (Node<K,V>[])new Node[cap]; table = tab; // Read the keys and values, and put the mappings in the HashMap for (int i = 0; i < mappings; i++) { @SuppressWarnings("unchecked") K key = (K) s.readObject(); @SuppressWarnings("unchecked") V value = (V) s.readObject(); putVal(hash(key), key, value, false, false); } } } void internalWriteEntries(java.io.ObjectOutputStream s) throws IOException { Node<K,V>[] tab; if (size > 0 && (tab = table) != null) { for (Node<K,V> e : tab) { for (; e != null; e = e.next) { s.writeObject(e.key); s.writeObject(e.value); } } } }

源码分析:writeObject(java.io.ObjectOutputStream s)方法和readObject(java.io.ObjectInputStream s)方法是实现序列化的两个重要方法,声明这两个方法的目的是为了实现自己可控制的序列化

序列化时,ObjectOutputStream会采用反射查找Serializable实现类内部是否声明了这两个方法,若有,则调用该方法。否则将采用默认的序列化进程。

13、遍历元素

在HashMap中遍历元素有两种方法,第一种是先调用map.keySet().iterator()得到元素的所有key值集合,再通过map.get(key)循环获得元素中所有的value值。第二种方法是通过调用map.entrySet().iterator()获得所有元素的Entry集合,通过Entry中的getKey()和getValue()获得所有的键—值;下面是实例:

import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.Map.Entry; public class Run { public static void main(String[] args) { Map<Integer, String> map=new HashMap(); map.put(1,"n"); map.put(2,"y"); map.put(3,"s"); map.put(4,"a"); map.put(5,"b"); //第一种,先获得所有键值集合,再一一获得对应的值 Iterator<Integer> keys=map.keySet().iterator(); while(keys.hasNext()) { Integer key=keys.next(); System.out.println(key+":"+map.get(key)); } //第二种,直接获得所有的键—值关系集合 Iterator<Entry<Integer, String>> entrys=map.entrySet().iterator(); while(entrys.hasNext()) { Entry<Integer, String> entry=entrys.next(); System.out.println(entry.getKey()+":"+entry.getValue()); } } }

第一种方法(keySet())源码

public Set<K> keySet() { Set<K> ks = keySet; if (ks == null) { ks = new KeySet(); keySet = ks; } return ks; } final class KeySet extends AbstractSet<K> { public final int size() { return size; } public final void clear() { HashMap.this.clear(); } public final Iterator<K> iterator() { return new KeyIterator(); } public final boolean contains(Object o) { return containsKey(o); } public final boolean remove(Object key) { return removeNode(hash(key), key, null, false, true) != null; } public final Spliterator<K> spliterator() { return new KeySpliterator<>(HashMap.this, 0, -1, 0, 0); } public final void forEach(Consumer<? super K> action) { Node<K,V>[] tab; if (action == null) throw new NullPointerException(); if (size > 0 && (tab = table) != null) { int mc = modCount; for (Node<K,V> e : tab) { for (; e != null; e = e.next) action.accept(e.key); } if (modCount != mc) throw new ConcurrentModificationException(); } } }

源码分析:方法中第一句Set<K> ks=keySet;这里的keySet是Set接口中定义的字段。

keySet()方法返回一个KeySet对象。KeySet是HashMap中的内部类,继承了AbstractSet抽象类

KeySet类中的iterator()方法返回一个可以获取所有key值的迭代器对象。

第二种方法(entrySet())源码

public Set<Map.Entry<K,V>> entrySet() { Set<Map.Entry<K,V>> es; return (es = entrySet) == null ? (entrySet = new EntrySet()) : es; } final class EntrySet extends AbstractSet<Map.Entry<K,V>> { public final int size() { return size; } public final void clear() { HashMap.this.clear(); } public final Iterator<Map.Entry<K,V>> iterator() { return new EntryIterator(); } public final boolean contains(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry<?,?> e = (Map.Entry<?,?>) o; Object key = e.getKey(); Node<K,V> candidate = getNode(hash(key), key); return candidate != null && candidate.equals(e); } public final boolean remove(Object o) { if (o instanceof Map.Entry) { Map.Entry<?,?> e = (Map.Entry<?,?>) o; Object key = e.getKey(); Object value = e.getValue(); return removeNode(hash(key), key, value, true, true) != null; } return false; } public final Spliterator<Map.Entry<K,V>> spliterator() { return new EntrySpliterator<>(HashMap.this, 0, -1, 0, 0); } public final void forEach(Consumer<? super Map.Entry<K,V>> action) { Node<K,V>[] tab; if (action == null) throw new NullPointerException(); if (size > 0 && (tab = table) != null) { int mc = modCount; for (Node<K,V> e : tab) { for (; e != null; e = e.next) action.accept(e); } if (modCount != mc) throw new ConcurrentModificationException(); } } }

源码分析:entrySet()方法返回一个Set对象

变量entrySet是HashMap中的实例字段(前面有说过的,即transient Set<Map.Entry<K,V>> entrySet;)。

类EntrySet继承于抽象类AbstractSet。

类EntrySet中的iterator()方法返回一个可以获得所有Map.Entry实例的迭代器对象(在HashMap中,数据的存储结构Node继承于Map.Entry接口,可以查看上面第5点“数据存储结构”)。

总结

HashMap的存储结构有三种:数组、链表、树。向HashMap容器中添加元素时

首先默认使用数组结构。当存在hash值与已有元素hash相同,且key值不同时,在数组上添加链表结构。当链表上的元素数量大于等于8时,链表转红黑树。

将HashMap容器中的元素移除时

若是红黑树结构,移除元素后若树上的元素数量小于6,红黑树转链表。

所以一个HashMap容器中可能同时存在数组、链表、树三种数据结构。

 

 

 

 

 


最新回复(0)