美文网首页
HashMap源码解析续篇

HashMap源码解析续篇

作者: 几行代码 | 来源:发表于2018-12-28 16:57 被阅读0次

这篇把上篇没有聊完的 “JDK8映射扩展方法的重写” 来简单聊聊。
上篇HashMap分析的传送门https://www.jianshu.com/p/715918ac18f4

@Override
public V getOrDefault(Object key, V defaultValue) {   // 当Map集合中有这个key时,就使用这个key值,如果没有就使用默认值defaultValue
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? defaultValue : e.value;
}
例子如下:
public class Demo {
    public static void main(String[] strs){
        HashMap<String,String> hashMap = new HashMap<>();
        hashMap.put("aa","北京");
        String orDefault = hashMap.getOrDefault("aa", "上海");
        System.out.println(orDefault); // map中存在"aa",所以值为北京
        String valueA = hashMap.getOrDefault("bb", "上海");
        System.out.println(valueA);   // map中不存在"bb",所以值为上海
    }
}

putIfAbsent(key,value) 这个方法:
当value为null的时候,putIfAbsent()方法会覆盖null值,直到value不为null为止
当value初始值不为null的时候,putIfAbsent()保证返回值始终是唯一的,并且是多线程安全的(可以用在单例中)
putIfAbsent()是有返回值的,应该对他的返回值进行非空判断(可以用在单例中)

@Override
public V putIfAbsent(K key, V value) {
    return putVal(hash(key), key, value, true, true);
}

// main方法中
HashMap<String,Object> hashMap = new HashMap<>();
Object obj = hashMap.putIfAbsent("cc", null);
if (obj == null) {
    System.out.println("打印返回值 : null");
}
hashMap.putIfAbsent("cc",1);
hashMap.putIfAbsent("cc",2);
hashMap.putIfAbsent("dd", 100);
hashMap.putIfAbsent("dd", 200);
Integer i = (Integer)hashMap.putIfAbsent("dd", 300);
hashMap.put("ee", 400);
hashMap.put("ee", 500);
System.out.println(hashMap);
System.out.println(i);

打印结果:

打印返回值 : null
{aa=北京, cc=1, dd=100, ee=500}
100

再来看个移除方法:

@Override
public boolean remove(Object key, Object value) {  // 根据key匹配node,如果value也相同则删除
    return removeNode(hash(key), key, value, true, true) != null;
}

这个方法和上面源码中的remove(Object key)方法其实是一样的,只是remove(Object key)这个方法需要自己把key,value取出来,最后都是走removeNode(hash(key), key, value, true, true) != null;这个方法进行移除操作的。建议使用这个,效率会高点

接下来是替换方法:

@Override
public boolean replace(K key, V oldValue, V newValue) {  // 根据key匹配node,如果value也相同则使用newValue覆盖返回true,否则返回false
    Node<K,V> e; V v;
    if ((e = getNode(hash(key), key)) != null &&
        ((v = e.value) == oldValue || (v != null && v.equals(oldValue)))) {
        e.value = newValue;
        afterNodeAccess(e);
        return true;
    }
    return false;
}

@Override
public V replace(K key, V value) { // 根据key匹配node,使用value覆盖原来的值返回原来的老的值,否则返回null
    Node<K,V> e;
    if ((e = getNode(hash(key), key)) != null) {
        V oldValue = e.value;
        e.value = value;
        afterNodeAccess(e);
        return oldValue;
    }
    return null;
}

computeIfAbsent方法:
根据key做匹配Node,(匹配不到则新建然后重排)如果Node有value,则直接返回oldValue,如果没有value则根据Function接口的apply方法获取value,返回value。
Function接口的apply的入参为key,调用computeIfAbsent时重写Function接口可以根据key进行逻辑处理,apply的返回值即为要存储的value

@Override
public V computeIfAbsent(K key,
                         Function<? super K, ? extends V> mappingFunction) {
    if (mappingFunction == null)
        throw new NullPointerException();
    int hash = hash(key);  //计算hash
    Node<K,V>[] tab; Node<K,V> first; int n, i;
    int binCount = 0;
    TreeNode<K,V> t = null;
    Node<K,V> old = null;
// 如果没初始化则先进行初始化,
        // resize()方法在table为空时执行初始化逻辑
    if (size > threshold || (tab = table) == null ||
        (n = tab.length) == 0)
        n = (tab = resize()).length;
    // 下面的逻辑就是通过key找到节点node
    if ((first = tab[i = (n - 1) & hash]) != null) {
        if (first instanceof TreeNode)  //如果已经转为树,按照树的规则进行处理
            old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
        else {
            Node<K,V> e = first; K k;
//查找整个链表,找到对应的key
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k)))) {
                    old = e;
                    break;
                }
                ++binCount;
            } while ((e = e.next) != null);
        }
        V oldValue;
// 如果key存在且其value!=null,则返回该value
        if (old != null && (oldValue = old.value) != null) {
            afterNodeAccess(old);
            return oldValue;
        }
    }

      // 下面的逻辑是:如果没找到则新建节点,
      // 节点value为Function返回值;如果找到但value为null,
      // 则将Function返回值作为其value 
    V v = mappingFunction.apply(key); //根据重写逻辑计算返回value
    if (v == null) {
        return null;
    } else if (old != null) {
        old.value = v;
        afterNodeAccess(old);
        return v;
    }
    else if (t != null)
        t.putTreeVal(this, tab, hash, key, v);
    else {
//如果匹配不到则table加入数据
        tab[i] = newNode(hash, key, v, first);
        if (binCount >= TREEIFY_THRESHOLD - 1)
            treeifyBin(tab, hash);
    }
    ++modCount;
    ++size;
    afterNodeInsertion(true);
    return v;
}

PS:
存在且value不为null,则返回value
不满足上述条件下,先检查Function返回值,若为null,返回null
存在但value为null,则将Function返回值作为value,返回value
不存在,新建节点,将Function返回值作为value,返回value

computeIfPresent方法:
根据key做匹配,如果匹配不上则返回null,匹配上根据BiFunction的apply方法获取value,返回value。BiFunction接口的apply的入参为key、oldValue,调用computeIfPresent时重写Function接口可以根据key和oldValue进行逻辑处理,apply的返回值如果为null则删除该节点,否则即为要存储的value

public V computeIfPresent(K key,
                          BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
    if (remappingFunction == null)
        throw new NullPointerException();
    Node<K,V> e; V oldValue;
    int hash = hash(key);
    if ((e = getNode(hash, key)) != null &&
        (oldValue = e.value) != null) {
//使用key和原value作为入参
        V v = remappingFunction.apply(key, oldValue);
        if (v != null) {
            e.value = v;
            afterNodeAccess(e);
            return v;
        }
        else
            removeNode(hash, key, null, false, true);
    }
    return null;
}

PS:
1.存在且value不为null:1,BiFunction返回值为null,删除该节点;2,BiFunction返回值不为null,作为新value,返回其值
2.不存在或其value为null,返回null

compute方法:
根据key做匹配,根据BiFunction的apply返回做存储的value。匹配到Node做value替换,匹配不到新增node。apply的返回值如果为null则删除该节点,否则即为要存储的value

@Override
public V compute(K key,
                 BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
    if (remappingFunction == null)
        throw new NullPointerException();
    int hash = hash(key);
    Node<K,V>[] tab; Node<K,V> first; int n, i;
    int binCount = 0;
    TreeNode<K,V> t = null;
    Node<K,V> old = null;
    if (size > threshold || (tab = table) == null ||
        (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((first = tab[i = (n - 1) & hash]) != null) {
        if (first instanceof TreeNode)
            old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
        else {
            Node<K,V> e = first; K k;
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k)))) {
                    old = e;
                    break;
                }
                ++binCount;
            } while ((e = e.next) != null);
        }
    }
    V oldValue = (old == null) ? null : old.value;
    //使用key和原value作为入参
    V v = remappingFunction.apply(key, oldValue);
    if (old != null) {
        if (v != null) {
            old.value = v;
            afterNodeAccess(old);
        }
        else
            removeNode(hash, key, null, false, true);
    }
    else if (v != null) {
        if (t != null)
            t.putTreeVal(this, tab, hash, key, v);
        else {
            tab[i] = newNode(hash, key, v, first);
            if (binCount >= TREEIFY_THRESHOLD - 1)
                treeifyBin(tab, hash);
        }
        ++modCount;
        ++size;
        afterNodeInsertion(true);
    }
    return v;
}

PS: 如果key不存在,新建key进行存储;如果key存在,value为null则删除此节点,不为null替换节点value并返回此value

merge方法:
功能大部分与compute相同,不同之处在于BiFunction中apply的参数,入参为oldValue、value,调用merge时根据两个value进行逻辑处理并返回value

@Override
public V merge(K key, V value,
               BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
    if (value == null)
        throw new NullPointerException();
    if (remappingFunction == null)
        throw new NullPointerException();
    int hash = hash(key);
    Node<K,V>[] tab; Node<K,V> first; int n, i;
    int binCount = 0;
    TreeNode<K,V> t = null;
    Node<K,V> old = null;
    if (size > threshold || (tab = table) == null ||
        (n = tab.length) == 0)
        n = (tab = resize()).length;
    if ((first = tab[i = (n - 1) & hash]) != null) {
        if (first instanceof TreeNode)
            old = (t = (TreeNode<K,V>)first).getTreeNode(hash, key);
        else {
            Node<K,V> e = first; K k;
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k)))) {
                    old = e;
                    break;
                }
                ++binCount;
            } while ((e = e.next) != null);
        }
    }
    if (old != null) {
        V v;
        if (old.value != null)
     //使用新老value作为入参
            v = remappingFunction.apply(old.value, value);
        else
            v = value;
        if (v != null) {
            old.value = v;
            afterNodeAccess(old);
        }
        else
            removeNode(hash, key, null, false, true);
        return v;
    }
    if (value != null) {
        if (t != null)
            t.putTreeVal(this, tab, hash, key, value);
        else {
            tab[i] = newNode(hash, key, value, first);
            if (binCount >= TREEIFY_THRESHOLD - 1)
                treeifyBin(tab, hash);
        }
        ++modCount;
        ++size;
        afterNodeInsertion(true);
    }
    return value;
}

forEach方法:
调用此方法时实现BiConsumer接口重写void accept(Object o, Object o2)方法,其中o为key,o2为value,可根据自己的实现对map中所有数据进行处理

@Override
public void forEach(BiConsumer<? super K, ? super V> action) {
    Node<K,V>[] tab;
    if (action == null)
        throw new NullPointerException();
    if (size > 0 && (tab = table) != null) {
        int mc = modCount;
        for (int i = 0; i < tab.length; ++i) {
            for (Node<K,V> e = tab[i]; e != null; e = e.next)
                action.accept(e.key, e.value);
        }
        if (modCount != mc)
            throw new ConcurrentModificationException();
    }
}

replaceAll方法:
调用此方法时重写BiFunction的Object apply(Object o, Object o2)方法,其中o为key,o2为value,根据重写方法逻辑进行重新赋值

@Override
public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
    Node<K,V>[] tab;
    if (function == null)
        throw new NullPointerException();
    if (size > 0 && (tab = table) != null) {
        int mc = modCount;
        for (int i = 0; i < tab.length; ++i) {
            for (Node<K,V> e = tab[i]; e != null; e = e.next) {
                e.value = function.apply(e.key, e.value);
            }
        }
        if (modCount != mc)
            throw new ConcurrentModificationException();
    }
}

JDK8映射扩展方法的重写到这里就结束了,下面扩展一下HashMap的clone()方法
这个方法最终是继承于Object的方法
这里涉及到一个概念:
浅拷贝:对一个对象进行clone生成新的对象,新的对象要开辟一块新的内存来存储,新对象中的基本类型属性和String类型属性都会开辟新的空间存储,但是如果是引用类型的属性,那这个引用类型的属性还是指向原对象的引用属性内存,当对新的对象或原对象的引用属性做出改变的时候,两方的引用属性类型的值同时做出改变

/**
 * Returns a shallow copy of this <tt>HashMap</tt> instance: the keys and
 * values themselves are not cloned.
 * key和value不被复制
 * @return a shallow copy of this map
 */
@SuppressWarnings("unchecked")
@Override
public Object clone() {
    HashMap<K,V> result;
    try {
        result = (HashMap<K,V>)super.clone();
    } catch (CloneNotSupportedException e) {
        // this shouldn't happen, since we are Cloneable
        throw new InternalError(e);
    }
    result.reinitialize();  // 重置为初始默认状态
    result.putMapEntries(this, false);  // 实现Map.putAll和Map构造函数
    return result;
}

PS:
1.HashMap的clone方法生成新的对象,新的对象有自己独立的存储空间。
2.虽然HashMap的clone方法生成了新的对象,但新的HashMap和原来的HashMap所存储的引用类型都是指向的同一存储空间。

这里用个简单例子来论证一下:

public class Person {
    private String name;
    private String sex;
    public Person(String name, String sex) {
        this.name = name;
        this.sex = sex;
    }
    @Override
    public String toString() {
        return "Person{" +
                "name='" + name + '\'' +
                ", sex='" + sex + '\'' +
                '}';
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getSex() {
        return sex;
    }
    public void setSex(String sex) {
        this.sex = sex;
    }
}

public static void main(String[] strs){
        HashMap<String,Object> hashMap = new HashMap<>();
//        hashMap.put("aa","北京");
//        String orDefault = (String) hashMap.getOrDefault("aa", "上海");
//        System.out.println(orDefault); // map中存在"aa",所以值为北京
//        String valueA = (String) hashMap.getOrDefault("bb", "上海");
//        System.out.println(valueA);   // map中存在"bb",所以值为上海
//
//        Object obj = hashMap.putIfAbsent("cc", null);
//        if (obj == null) {
//            System.out.println("打印返回值 : null");
//        }
//        hashMap.putIfAbsent("cc",1);
//        hashMap.putIfAbsent("cc",2);
//        hashMap.putIfAbsent("dd", 100);
//        hashMap.putIfAbsent("dd", 200);
//        Integer i = (Integer)hashMap.putIfAbsent("dd", 300);
//        hashMap.put("ee", 400);
//        hashMap.put("ee", 500);
//        System.out.println(hashMap);
//        System.out.println(i);

        Person person = new Person("lucy","女");
        hashMap.put("lucy", person);
        HashMap<String,Object> cloneMap = (HashMap<String, Object>) hashMap.clone();
        System.out.println("*************************   不做改变   ***********************************");
        System.out.println("未改变之前, hashMap的值:"+hashMap.toString());
        System.out.println("未改变之前,cloneMap的值:"+cloneMap.toString());
        System.out.println("hashMap和cloneMap是否指向同一内存地址:"+(hashMap==cloneMap));
        System.out.println("hashMap和cloneMap中存储的Person是否指向同一内存地址:"+(hashMap.get("lucy")==cloneMap.get("lucy")));
        System.out.println();
        System.out.println("*************************  对cloneMap中的值进行改变,看是否能影响到hashMap  ***********************************");
        Person clonePerson = (Person) cloneMap.get("lucy");
        clonePerson.setSex("男");
        System.out.println("改变之后,cloneMap的值:" + cloneMap.toString());
        System.out.println("改变之后, hashMap的值:" + hashMap.toString());
        System.out.println();
        System.out.println("*************************  对hashMap新增  **********************************");
        Person newPerson = new Person("jack","男");
        hashMap.put("jack", newPerson);
        System.out.println("改变之后,cloneMap的值:" + cloneMap.toString());
        System.out.println("改变之后, hashMap的值:" + hashMap.toString());
    }

打印出的数据:

*************************   不做改变   ***********************************
未改变之前, hashMap的值:{lucy=Person{name='lucy', sex='女'}}
未改变之前,cloneMap的值:{lucy=Person{name='lucy', sex='女'}}
hashMap和cloneMap是否指向同一内存地址:false
hashMap和cloneMap中存储的Person是否指向同一内存地址:true

*************************  对cloneMap中的值进行改变,看是否能影响到hashMap  ***********************************
改变之后,cloneMap的值:{lucy=Person{name='lucy', sex='男'}}
改变之后, hashMap的值:{lucy=Person{name='lucy', sex='男'}}

*************************  对hashMap新增  **********************************
改变之后,cloneMap的值:{lucy=Person{name='lucy', sex='男'}}
改变之后, hashMap的值:{lucy=Person{name='lucy', sex='男'}, jack=Person{name='jack', sex='男'}}

总结:
从上可以看出clone方法虽然生成了新的HashMap对象,新的HashMap中的table数组虽然也是新生成的,但是数组中的元素还是引用以前的HashMap中的元素。这就导致在对HashMap中的元素进行修改的时候,即对数组中元素进行修改,会导致原对象和clone对象都发生改变,但进行新增或删除就不会影响对方,因为这相当于是对数组做出的改变,clone对象新生成了一个数组

相关文章

网友评论

      本文标题:HashMap源码解析续篇

      本文链接:https://www.haomeiwen.com/subject/pykelqtx.html