死嗑Java容器—HashMap

深入了解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容器中添加元素时

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

将HashMap容器中的元素移除时

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

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

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值