HashMap

HashMap

在这里插入图片描述

  1. 线程是否安全: HashMap 是非线程安全的
  2. 效率: 因为线程安全的问题,HashMap 要比 HashTable 效率高一点。
  3. 对Null key 和Null value的支持: HashMap 中,null 可以作为键,这样的键只有一个,可以有一个或多个键所对应的值为 null。
  4. 初始容量大小和每次扩充容量大小的不同 : HashMap 默认的初始化大小为16。之后每次扩充,容量变为原来的2倍。②创建时如果给定了容量初始值 HashMap 会将其扩充为2的幂次方大小
  5. 底层数据结构: JDK1.8 以后的 HashMap 在解决哈希冲突时有了较大的变化,当链表长度大于阈值(默认为8)时,将链表转化为红黑树,以减少搜索时间。Hashtable 没有这样的机制。
基本属性
//	默认初始化大小
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//	最大容量
static final int MAXIMUM_CAPACITY = 1 << 30;
//	默认负载因子0.75
static final float DEFAULT_LOAD_FACTOR = 0.75f;
//	链表转红黑树阈值
static final int TREEIFY_THRESHOLD = 8;
//	红黑树转链表阈值
static final int UNTREEIFY_THRESHOLD = 6;
//	该阈值作用是(当链表个数超过64,可以转红黑树,否则进行扩容)
static final int MIN_TREEIFY_CAPACITY = 64;
//	hash表
transient Node<K,V>[] table;

transient Set<Map.Entry<K,V>> entrySet;
//	实际大小
transient int size;
//	结构发生变化次数
transient int modCount;
//	最大容量,table.length * loadFactor
int threshold;
//	负载因子
final float loadFactor;
保证返回大于等于输入参数且最近的2的整数次幂的方法
static final int tableSizeFor(int cap) {
    //	为什么要减一,请使用cap=8来测试这条语句作用
    int n = cap - 1;
    n |= n >>> 1;
    n |= n >>> 2;
    n |= n >>> 4;
    n |= n >>> 8;
    n |= n >>> 16;
    return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
HashMap的hash算法

解决hash冲突的四种办法

  1. 开放地址法(HashMap扩容)
  2. 链地址法 (HashMap的链表结构)
  3. 再hash法
  4. 公共溢出法
static final int hash(Object key) {
    int h;
    // 如果key为null,则hash值为0,否则调用key的hashCode()方法
    // 并让高16位与整个hash异或,这样做是为更好的避免hash碰撞
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
扩容算法
final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold;
    int newCap, newThr = 0;
/***************************************给threshold赋值*********************************/
    //	判断原表的实际长度
    if (oldCap > 0) {
        //	长度大于最大值
        if (oldCap >= MAXIMUM_CAPACITY) {
            //	最大容量赋值为int类型最大值
            threshold = Integer.MAX_VALUE;
            //	不能扩容,直接返回原表
            return oldTab;
        }else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&   //	这里将newCap空充两倍
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            //	扩容成原来两倍
            newThr = oldThr << 1; 
    }
	/** 基本上不会进入这个分支
	 * table中没有数据,且threshold大于0,进入该分支,实际上表示原来的table中没有数据
	 */
    else if (oldThr > 0)
        //	这里实际上没有扩容,而是 threshold = threshold * loadFactor
        newCap = oldThr;
    else {  //	其他状态,相当于空参构造方法             
        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;
/**************************************给threshold赋值结束*******************************/
    
    @SuppressWarnings({"rawtypes","unchecked"})
    //	创建一个新的Node<K,V>[],并将地址给table
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    /**
     * 判断之前的table是否有数据
     * 若不为空,则进行数据迁移
     **/
    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)
                    //	如果是树类型结构,且next不为null,则进入这个分支
                    ((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;
                    }
                    //	位置有变化,存放在j+oldCap位置,这就是扩容后,改变的数据存放位置
                    if (hiTail != null) {
                        hiTail.next = null;
                        newTab[j + oldCap] = hiHead;
                    }
                }
            }
        }
    }
    return newTab;
}
若Node下面节点为树,则调用这个方法:split(this, newTab, j, oldCap);
//	和上面扩容方法逻辑一样,这里只不过是遍历树结构,增加了tree转table,以及变化后的tree,table的转换
final void split(HashMap<K,V> map, Node<K,V>[] tab, int index, int bit) {
    TreeNode<K,V> b = this;
    TreeNode<K,V> loHead = null, loTail = null;
    TreeNode<K,V> hiHead = null, hiTail = null;
    int lc = 0, hc = 0;
    for (TreeNode<K,V> e = b, next; e != null; e = next) {
        next = (TreeNode<K,V>)e.next;
        e.next = null;
        if ((e.hash & bit) == 0) {
            if ((e.prev = loTail) == null)
                loHead = e;
            else
                loTail.next = e;
            loTail = e;
            ++lc;
        }
        else {
            if ((e.prev = hiTail) == null)
                hiHead = e;
            else
                hiTail.next = e;
            hiTail = e;
            ++hc;
        }
    }

    if (loHead != null) {
        //	位置不改变的,长度是否大于8
        if (lc <= UNTREEIFY_THRESHOLD)
            //	直接转为Node链表
            tab[index] = loHead.untreeify(map);
        else {
            //	生成新的树
            tab[index] = loHead;
            if (hiHead != null) // (else is already treeified)
                loHead.treeify(tab);
        }
    }
    //	同上,只不过位置不一样,上面已经讲解过
    if (hiHead != null) {
        if (hc <= UNTREEIFY_THRESHOLD)
            tab[index + bit] = hiHead.untreeify(map);
        else {
            tab[index + bit] = hiHead;
            if (loHead != null)
                hiHead.treeify(tab);
        }
    }
}
生成TreeNode的方法treeify()
final void treeify(Node<K,V>[] tab) {
    TreeNode<K,V> root = null;
    //	每次循环 x=x.next
    for (TreeNode<K,V> x = this, next; x != null; x = next) {
        next = (TreeNode<K,V>)x.next;
        x.left = x.right = null;
        //	根节点复制
        if (root == null) {
            x.parent = null;
            x.red = false;
            root = x;
        }
        else { //	非根节点
            K k = x.key;
            int h = x.hash;
            Class<?> kc = null;
            //	循环树,从根节点开始
            for (TreeNode<K,V> p = root;;) {
                int dir, ph;
                K pk = p.key;
                if ((ph = p.hash) > h) // 左边
                    dir = -1;
                else if (ph < h) //	右边
                    dir = 1;
                else if ((kc == null &&	
                          (kc = comparableClassFor(k)) == null) ||
                         (dir = compareComparables(kc, k, pk)) == 0) //	发生hash碰撞
                    /**
                     * 底层调用C语言来做hash比较,
                     * 如果两个对象有任意一对象为null,
                     * 或者两个对象指的同一地址,则调用C语言的hash处理
                     * 否则直接返回 0
                     */
                    dir = tieBreakOrder(k, pk);

                TreeNode<K,V> xp = p;
                // 通过前面的比较dir值,确定加入树末尾的那边
                if ((p = (dir <= 0) ? p.left : p.right) == null) {
                    x.parent = xp;
                    if (dir <= 0)
                        xp.left = x;
                    else
                        xp.right = x;
                    //	调整树的平衡,左旋,右旋
                    root = balanceInsertion(root, x);
                    break;
                }
            }
        }
    }
    moveRootToFront(tab, root);
}
树的调整方法balanceInsertion()

在这里插入图片描述

红黑树的特性:

 		1. 节点是红色或黑色。
 		2. 根节点是黑色。
 		3. 每个红色节点的两个子节点都是黑色。(从每个叶子到根的所有路径上不能有两个连续的红色节点)
 		4. 从任一节点到其每个叶子的所有路径都包含相同数目的黑色节点。
static <K,V> TreeNode<K,V> balanceInsertion(TreeNode<K,V> root,
                                            TreeNode<K,V> x) {
    x.red = true;
    for (TreeNode<K,V> xp, xpp, xppl, xppr;;) {
        if ((xp = x.parent) == null) {
            //	若是根节点,则置黑,返回根节点
            x.red = false;
            return x;
        }
        else if (!xp.red || (xpp = xp.parent) == null)
            //	若x的父节点为黑色,或者x父节点为根节点(tree根节点必须是黑色),则直接返回该tree
            return root;
        if (xp == (xppl = xpp.left)) {
            //	若x的父节点,是某节点的左子叶
            
            if ((xppr = xpp.right) != null && xppr.red) {
                //	若xpp的有有右节点,且右节点为红色
                xppr.red = false;
                xp.red = false;
                xpp.red = true;
                x = xpp;
            }
            else {	//当xp是某节点的左子叶,且右子叶为空,或者右子叶不为空,但为黑色
                if (x == xp.right) {
                    //	x为某节点的右节点,则左旋
                    root = rotateLeft(root, x = xp);
                    xpp = (xp = x.parent) == null ? null : xp.parent;
                }
                if (xp != null) {
                    xp.red = false;
                    if (xpp != null) {//若xp,xpp值都不为空,则右旋
                        xpp.red = true;
                        root = rotateRight(root, xpp);
                    }
                }
            }
        }
        else {
            //	若x的父节点,是某节点的右子叶
            
            if (xppl != null && xppl.red) {
                //	若xpp的有有左节点,且右节点为红色
                xppl.red = false;
                xp.red = false;
                xpp.red = true;
                x = xpp;
            }//	与上面类似,就不多说了
            else {
                if (x == xp.left) {
                    root = rotateRight(root, x = xp);
                    xpp = (xp = x.parent) == null ? null : xp.parent;
                }
                if (xp != null) {
                    xp.red = false;
                    if (xpp != null) {
                        xpp.red = true;
                        root = rotateLeft(root, xpp);
                    }
                }
            }
        }
    }
}
右旋方法
static <K,V> TreeNode<K,V> rotateRight(TreeNode<K,V> root,
                                       TreeNode<K,V> p) {
    TreeNode<K,V> l, pp, lr;
    //	左旋判断p节点是否为空,以及p节点的左子节点
    if (p != null && (l = p.left) != null) {
        if ((lr = p.left = l.right) != null)
            //	这一步就是把l的右节点变为p的左节点
            lr.parent = p;
        if ((pp = l.parent = p.parent) == null)
            //	判断p是否是根节点,并将p.parent 赋值给l.parent
            (root = l).red = false;
        else if (pp.right == p)
            //	若p是右子叶,那么l将代替他的位置
            pp.right = l;
        else
            //	若p是左子叶,那么l将代替他的位置
            pp.left = l;
        //	将p和l关联起来
        l.right = p;
        p.parent = l;
    }
    return root;
}
左旋方法
static <K,V> TreeNode<K,V> rotateLeft(TreeNode<K,V> root,
                                              TreeNode<K,V> p) {
    TreeNode<K,V> r, pp, rl;
    if (p != null && (r = p.right) != null) {
        if ((rl = p.right = r.left) != null)
            rl.parent = p;
        if ((pp = r.parent = p.parent) == null)
            (root = r).red = false;
        else if (pp.left == p)
            pp.left = r;
        else
            pp.right = r;
        r.left = p;
        p.parent = r;
    }
    return root;
}
hashMap的get方法
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;
    //table不为空,通过tabtab[(n - 1) & hash] 找到改key的数组位置赋值给first
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {
        //若first的key与查找的key相同,则返回first
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        if ((e = first.next) != null) {
            //如果是tree ,则调用tree查找方法
            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;
}
// tree查找方法
final TreeNode<K,V> getTreeNode(int h, Object k) {
    return ((parent != null) ? root() : this).find(h, k, null);
}

final TreeNode<K,V> find(int h, Object k, Class<?> kc) {
    TreeNode<K,V> p = this;
    //树的遍历
    do {
        int ph, dir; K pk;
        TreeNode<K,V> pl = p.left, pr = p.right, q;
        if ((ph = p.hash) > h)
            p = pl;
        else if (ph < h)
            p = pr;
        else if ((pk = p.key) == k || (k != null && k.equals(pk)))
            return p;
        else if (pl == null)
            p = pr;
        else if (pr == null)
            p = pl;
        else if ((kc != null ||
                  // 如果k是Comparable的子类则返回其真实的类,否则返回null
                  (kc = comparableClassFor(k)) != null) &&
                 // 如果k和pk不是同样的类型则返回0,否则返回两者比较的结果
                 (dir = compareComparables(kc, k, pk)) != 0)
            // 通过compare方法比较key值的大小决定使用左子树还是右子树
            p = (dir < 0) ? pl : pr;
        else if ((q = pr.find(h, k, kc)) != null)
            // 如果以上条件都不通过,则尝试在右子树查找
            return q;
        else
             // 都没找到就在左子树查找
            p = pl;
    } while (p != null);
    return null;
}
HashMap的put方法
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)
        //	初始化table
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null)
        //	该table节点没Node节点
        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))))
            // hash,key判断插入的位置是根Node节点
            e = p;
        else if (p instanceof TreeNode)
            //	该节点是tree结构
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            //	p不是根Node节点,这里主要作用是找到,插入的位置节点Node
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) {
                    //	若p.next节点为空,则创建一个Node节点,并赋值
                    p.next = newNode(hash, key, value, null);
                    if (binCount >= TREEIFY_THRESHOLD - 1)
                        // 链表长度大于8,则需要判断是是否扩容,或者转红黑树
                        treeifyBin(tab, hash);
                    break;
                }
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    // 如果待插入的key在链表中找到了,则退出循环
                    break;
                p = e;
            }
        }
        if (e != null) {
            //替换旧值,并返回旧值
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null)
                e.value = value;
            //将该节点移动到最后
            afterNodeAccess(e);
            return oldValue;
        }
    }
    //	结构发生变化,次数加一
    ++modCount;
    if (++size > threshold)
        // 是否需要扩容
        resize();
    //该方法无用
    afterNodeInsertion(evict);
    return null;
}



final TreeNode<K,V> putTreeVal(HashMap<K,V> map, Node<K,V>[] tab,
                               int h, K k, V v) {
    Class<?> kc = null;
    // 标记是否找到key
    boolean searched = false;
    //	给根节点赋值
    TreeNode<K,V> root = (parent != null) ? root() : this;
    for (TreeNode<K,V> p = root;;) {
        // dir标记左右边,ph是节点的hash值,pk是p的key
        int dir, ph; K pk;
        if ((ph = p.hash) > h)
            dir = -1;
        else if (ph < h)
            dir = 1;
        else if ((pk = p.key) == k || (k != null && k.equals(pk)))
            // 找到p节点
            return p;
        else if ((kc == null &&
                  // 如果k是Comparable的子类则返回其真实的类,否则返回null
                  (kc = comparableClassFor(k)) == null) ||
                 // 如果k和pk不是同样的类型则返回0,否则返回两者比较的结果
                 (dir = compareComparables(kc, k, pk)) == 0) {
            // 这个条件表示两者hash相同但是其中一个不是Comparable类型或者两者类型不同
            // 比如key是Object类型,这时可以传String也可以传Integer,两者hash值可能相同
            // 在红黑树中把同样hash值的元素存储在同一颗子树,这里相当于找到了这颗子树的顶点
            // 从这个顶点分别遍历其左右子树去寻找有没有跟待插入的key相同的元素
            if (!searched) {
                TreeNode<K,V> q, ch;
                searched = true;
                // 遍历左右子树找到了直接返回
                if (((ch = p.left) != null &&
                     (q = ch.find(h, k, kc)) != null) ||
                    ((ch = p.right) != null &&
                     (q = ch.find(h, k, kc)) != null))
                    return q;
            }
            // 如果两者类型相同,再根据它们的内存地址计算hash值进行比较
            dir = tieBreakOrder(k, pk);
        }

        TreeNode<K,V> xp = p;
        // 从跟向下遍历
        if ((p = (dir <= 0) ? p.left : p.right) == null) {
            // 如果最后确实没找到对应key的元素,则新建一个节点
            Node<K,V> xpn = xp.next;
            TreeNode<K,V> x = map.newTreeNode(h, k, v, xpn);
            if (dir <= 0)
                xp.left = x;
            else
                xp.right = x;
            xp.next = x;
            x.parent = x.prev = xp;
            if (xpn != null)
                ((TreeNode<K,V>)xpn).prev = x;
            // 插入树节点后平衡
            // 把root节点移动到链表的第一个节点
            moveRootToFront(tab, balanceInsertion(root, x));
            return null;
        }
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值