集合—HashMap源码

4-1 HashMap源码(2021-11-9)

1 Map接口和常用方法(jdk1.8)

  • 用于保存具有映射关系数据key-value双列元素。

  • Map中的key和value可以是任何类型元素,会封装到HashMap$Node对象中。

  • Map中的key不允许重复,有相同的key时,就替换。

  • Map中的key可以为null,value也可以为null;但是key只允许有一个null,value可以有多个。

  • 常用key作key,但是其他对象也可以。

  • Node实现了Entry接口。

2 Map遍历方式

//第一组:先 取出 所有 key ,通过 Key 取出对应的Value。
//最简单:
System.out.println("============第一组============");
Set keyset = map.keySet();
for(Object key : keyset){//增强for
    System.out.println(key + "-" + map.get(key));
}

//迭代器
System.out.println("============第一组============");
Iterator iterator = keyset.iterator();
while (iterator.hasNext()) {
    Object key =  iterator.next();
    System.out.println(key + "-" + map.get(key));
}

//第二组:把所有的value取出。

System.out.println("============第二组============");
Collection values = map.values();
//增强for
for(Object value : values){
    System.out.println(value);
}
//迭代器
System.out.println("============第二组============");
Iterator iterator2 = values.iterator();
while (iterator2.hasNext()) {
    Object value =  iterator2.next();
    System.out.println(value);
}

//第三组:通过EntrySet 来获取 k-v。
System.out.println("============第三组============");
Set entrySet = map.entrySet();
for(Object entry : entrySet){
    Map.Entry m = (Map.Entry) entry;
    System.out.println(m.getKey() + "-" + m.getValue());
}

System.out.println("============第三组============");
Iterator iterator3 = entrySet.iterator();
while (iterator3.hasNext()) {
    Object entry = iterator3.next(); // Node
    Map.Entry m = (Map.Entry) entry;//向下转型
    System.out.println(m.getKey() + "-" + m.getValue());
}

3 put方法(HashSet分析过源码)

  • 无参构造:初始化加载因子0.75;

  • put一个元素:

    • putVal(传入key的hash值,key,value)

      • 如果table是null 或者 长度 = 0 就扩容,第一次扩为16,阈值是12.

      • 如果第一处是null,直接加入。

      • else第一处不是null:

        • 如果hash值相同 并且 ( 是相同对象 || equals 相同 ):e=p。
        • else if :判断是不是树,如果是,按照红黑树的方法添加。(此处还没有深究)
        • else:后面跟的是链表:
          • 循环对比:
            • 如果下一处是null,就加入;再判断这条链表是否需要树化;break;
            • 如果hash值相同 并且 ( 是相同对象 || equals 相同 );break;
        • 如果e不是null:
          • 替换。
      • ++modCount;

      • if (++size > threshold) //判断是否到达阈值
            resize(); //扩容
        afterNodeInsertion(evict);
        return null; //执行成功!
        

4 resize方法

final Node<K,V>[] resize() {
    
    Node<K,V>[] oldTab = table;
    
    int oldCap = (oldTab == null) ? 0 : oldTab.length;
    int oldThr = threshold; // 确定阈值 12
    int newCap, newThr = 0;
    
    if (oldCap > 0) { // 如果 oldCap > 0
        
        if (oldCap >= MAXIMUM_CAPACITY) {  // >= 2乘1073741824 ?
            threshold = Integer.MAX_VALUE; // table最大值
            return oldTab;
        }
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&  // ( newCap = oldCap*2 < 2乘1073741824) && (oldCap >= 16)
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; // 新的阈值 = 旧的阈值 * 2
        
    }
    else if (oldThr > 0) // 啥时 候进来??
        newCap = oldThr; // 新容量 = 旧的阈值
    
    else {        // 0容量时,进入。
        newCap = DEFAULT_INITIAL_CAPACITY;  // 16
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY); // 12
        
    }
    
    
    if (newThr == 0) { // 新的阈值 = 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) { // 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; // 返回更新后的 table。
}

5 hash计算

  • 得到hash值——>得到索引值。
static final int hash(Object key) { //hash方法(key)
    int h;
    return (key == null) ? 0 : ( h = key.hashCode() ) ^ ( h >>> 16 ); //求出hashCode值。
}
// Object.java 中
public native int hashCode(); // 不知道

5-1 为什么h = key.hashCode()) 与 (h >>> 16) 异或?

  • 在jdk1.7中有indexFor(int h, int length)方法。jdk1.8里没有,但原理没变。1.8中用**tab[(n - 1) & hash]**代替但原理一样。
  • 1.7源码:
static int indexFor(int h, int length) {  // jdk1.7 得到下标方法。
    return h & (length-1); //返回的是 数组下表 = 索引值。
}
  • 原因总结:

    • 由于和(length-1)运算,length 绝大多数情况小于2的16次方。所以始终是hashcode 的低16位(甚至更低)参与运算。要是高16位也参与运算,会让得到的下标更加散列
    • 所以这样高16位是用不到的,如何让高16也参与运算呢?所以才有**hash(Object key)**方法。让他的hashCode()和自己的高16位^运算。
  • 例子:

  • 补充:

当length=8时 下标运算结果取决于哈希值的低三位。

当length=16时 下标运算结果取决于哈希值的低四位。

当length=32时 下标运算结果取决于哈希值的低五位

当length=2的N次方, 下标运算结果取决于哈希值的低N位。

6 remove方法

public V remove(Object key) { //按照key 删除元素
    Node<K,V> e;  // 创建新的结点
    return (e =  removeNode( hash(key) , key, null, false, true) ) == null ? null : e.value;
    // 没有这个key就返回null
} 
final Node<K,V> removeNode(int hash, Object key, Object value,
                           boolean matchValue, boolean movable) { //传入要删除的元素信息。
    
    Node<K,V>[] tab; //临时数组
    Node<K,V> p;  //临时Node元素
    int n, index;  // 临时变量
    
    if ((tab = table) != null && (n = tab.length) > 0 &&
            (p = tab[index = (n - 1) & hash]) != null) { // 如果数组有内容 并且 长度大于0 并且 要删除的key位置有内容
        
        Node<K,V> node = null, e; //临时Node
        K k; 
        V v; 
        
        if (p.hash == hash &&  //如果第一个就找到了!!!node = p
                ((k = p.key) == key || (key != null && key.equals(k))))
            node = p; //第一个就找到了!
        
        else if ((e = p.next) != null) { // else:
            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; //node 
                        break;  // 退出循环 
                    }
                    p = e; //后移。
                } while ((e = e.next) != null);//循环到尾巴!
            }
        }
        
        // 找到了node && 
        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; // size-1。
            afterNodeRemoval(node);
            return node; // 返回 node
        }
        
    }
    return null;
}

7 get方法

public V get(Object key) { // 传入key
    Node<K,V> e; // 临时Node
    return (e =  getNode( hash(key), key ) ) == null ? null : e.value; // 没找到就返回null ;找到了就返回 值。
}
// getNode( hash(key), key )  返回的永远是一个对象(找不到就是null)。
final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; //临时 table 
    Node<K,V> first, e; //临时Node
    int n; K k; // 临时变量
    
    if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) {
        //有table && 长度>0 && 第一处有元素 
        if (first.hash == hash && // 检查第一处元素 是否相同 
                ((k = first.key) == key || (key != null && key.equals(k)))) 
            return first; // 一次查看相同返回 first(Node)!!!!!幸运!
        
        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; 
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值