HashMap在jdk1.7和1.8中的区别

学习jdk1.8中的HashMap之前,需要先了解下什么是红黑树(了解红黑树的同学直接从共同点开始看即可):

参考:https://www.cnblogs.com/ysocean/p/8032642.html

https://www.cnblogs.com/ysocean/p/8004211.html

类型二叉树红黑树
说明每个节点最多只能有两个子节点的树型结构。超过两个节点的成为多路树。 二叉搜索树:二叉搜索树是特殊的二叉树,需满足要求:若它的左子树不为空,则左子树上所有节点的值均小于它的根节点的值;若它的右子树不为空,则它的右子树上所有节点的值均大于它的根节点的值。它的左右子树也分别为二叉排序树。是一种用来保证树总是平衡的(或大部分是平衡的),也就是说,每个节点的左子树节点个数和右子树节点个数尽量相等。对于要插入的数据项(删除也是),插入例程要检查会不会破坏树的平衡,如果破坏了,程序就会通过改变节点的颜色、左旋、右旋的方式进行纠正,根据需要改变树的结构,从而保证树的平衡。
特点如果是在平衡的二叉搜索树上,也就是说如果插入的数据是随机的,则其效率很高,其查找、插入和删除的时间复杂度都是O(logn),底数为2;如果插入的数据是有序的,比如从小到大的顺序,则数据的排布效果全部在根节点的右边,和链表没什么区别,这种情况下的时间复杂度为O(n),而不是O(logn),当然这是在最不平衡的条件下,实际情况下,二叉搜索树的效率应该在O(n)和O(logn)之间,这取决于树的不平衡度。①、节点都有颜色;②、在插入和删除的过程中,要遵循保持这些颜色的不同排列规则。
 第一个很好理解,在红-黑树中,每个节点的颜色或者是黑色或者是红色的。当然也可以是任意别的两种颜色,这里的颜色用于标记,我们可以在节点类Node中增加一个boolean型变量isRed,以此来表示颜色的信息。

 第二点,在插入或者删除一个节点时,必须要遵守的规则称为红-黑规则:

  1.每个节点不是红色就是黑色的;

  2.根节点总是黑色的;

  3.如果节点是红色的,则它的子节点必须是黑色的(反之不一定),(也就是从每个叶子到根的所有路径上不能有两个连续的红色节点);

  4.从根节点到叶节点或空子节点的每条路径,必须包含相同数目的黑色节点(即相同的黑色高度)。

  从根节点到叶节点的路径上的黑色节点的数目称为黑色高度,规则 4 另一种表示就是从根到叶节点路径上的黑色高度必须相同。

  注意:新插入的节点颜色总是红色的,这是因为插入一个红色节点比插入一个黑色节点违背红-黑规则的可能性更小,原因是插入黑色节点总会改变黑色高度(违背规则4),但是插入红色节点只有一半的机会会违背规则3(因为父节点是黑色的没事,父节点是红色的就违背规则3)。另外违背规则3比违背规则4要更容易修正。当插入一个新的节点时,可能会破坏这种平衡性,那么红-黑树是如何修正的呢?
  参考文档:

[https://blog.csdn.net/carson_ho/article/details/79373026](https://blog.csdn.net/carson_ho/article/details/79373026)

[https://www.jianshu.com/p/8324a34577a0?utm_source=oschina-app](https://www.jianshu.com/p/8324a34577a0?utm_source=oschina-app)

[https://www.cnblogs.com/ysocean/p/8711071.html](https://www.cnblogs.com/ysocean/p/8711071.html)
1.71.8
使用一个Entry数组来存储数据,用key的hashcode取模来决定key会被放到数组里的位置,如果hashcode相同,或者hashcode取模后的结果相同(hash collision),那么这些key会被定位到Entry数组的同一个格子里,这些key会形成一个链表。在hashcode特别差的情况下,比方说所有key的hashcode都相同,这个链表可能会很长,那么put/get操作都可能需要遍历这个链表也就是说时间复杂度在最差情况下会退化到O(n)。使用一个Node数组来存储数据,但这个Node可能是链表结构,也可能是红黑树结构。如果插入的key的hashcode相同,那么这些key也会被定位到Node数组的同一个格子里。如果同一个格子里的key不超过8个,使用链表结构存储。如果超过了8个,那么会调用treeifyBin函数,将链表转换为红黑树。那么即使hashcode完全相同,由于红黑树的特点,查找某个特定元素,也只需要O(log n)的开销。也就是说put/get的操作的时间复杂度最差只有O(log n)
发生hash冲突时,新元素插入到链表头中,即新元素总是添加到数组中,就元素移动到链表中。由于 JDK 1.8 转移数据操作 = 按旧链表的正序遍历链表、在新链表的尾部依次插入,所以不会出现链表 逆序、倒置的情况,故不容易出现环形链表的情况 ,但jdk1.8仍是线程不安全的,因为没有加同步锁保护。
在扩容resize()过程中,采用单链表的头插入方式,在将旧数组上的数据 转移到 新数组上时,转移操作 = 按旧链表的正序遍历链表、在新链表的头部依次插入,即在转移数据、扩容后,容易出现链表逆序的情况 。HashMap线程不安全的一个重要原因就是:多线程下resize()容易出现死循环此时若(多线程)并发执行 put()操作,一旦出现扩容情况,则 容易出现 环形链表,从而在获取数据、遍历链表时 形成死循环(Infinite Loop),即 死锁的状态 。由于 JDK 1.8 转移数据操作 = 按旧链表的正序遍历链表、在新链表的尾部依次插入,所以不会出现链表 逆序、倒置的情况,故不容易出现环形链表的情况 ,但jdk1.8仍是线程不安全的,因为没有加同步锁保护。发生hash冲突后,会优先判断该节点的数据结构式是红黑树还是链表,如果是红黑树,则在红黑树中插入数据,如果是链表,则将数据插入到链表的尾部并判断链表长度是否大于8,如果大于8要转成红黑树,另一还要判断数组长度是否超过阀值
hash值的获取不一样参考 https://blog.csdn.net/carson_ho/article/details/79373026

补充一下map遍历删除异常:ConcurrentModificationException
错误示范:

Map<String, Integer> map=new HashMap<>();
    map.put("张三",22);
    map.put("李四",25);
    map.put("王五",33);
    map.put("赵六",28);
    map.put("田七",25);
    map.put("李思",25);
    map.put("李嘉欣",25);

Set<Entry<String, Integer>> set=map.entrySet();

for (Entry<String, Integer> entry : set) {
    String name=entry.getKey();
    System.out.println(name);
    System.out.println(name.contains("李"));
    if(name.contains("李")){
        map.remove(name);
    }
}

在这里插入图片描述
正确示范为:

package map遍历和删除;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/*map集合遍历删除*/
public class Test2 {
    public static void main(String[] args) {
        //key=name value=age
        Map<String, Integer> map=new HashMap<>();
        map.put("张三",22);
        map.put("李四",25);
        map.put("王五",33);
        map.put("赵六",28);
        map.put("田七",25);
        map.put("李思",25);
        map.put("李嘉欣",25);

        //单个删除删--remove  
        map.remove("张三");
        for(Map.Entry<String, Integer> entry:map.entrySet()){
            System.out.println(entry.getKey()+"="+entry.getValue());
        }

        //需求:删除名字(即key)中包含“李”的元素
        /*分析:
         *  1.此时直接map.remove(key)就不符合要求了,必须进行遍历删除
         *  
         *  2.通常map集合遍历就两种方式,一个foreach和iterator
         * */

        Set<Entry<String, Integer>> set=map.entrySet();

        /*for (Entry<String, Integer> entry : set) {
            String name=entry.getKey();
            System.out.println(name);
            System.out.println(name.contains("李"));
            if(name.contains("李")){
                map.remove(name);
            }
        }*/


        Iterator<Entry<String, Integer>> iterator=set.iterator();
        while(iterator.hasNext()){
            Entry<String, Integer> entry=iterator.next();
//          String name=entry.getKey();
            String name=entry.getKey();
            int value=entry.getValue();
            if(name.contains("李")){
                //特别注意:不能使用map.remove(name)  否则会报同样的错误
                iterator.remove();
            }
        }

        System.out.println();
        for (Entry<String, Integer> entry : set) {
            System.out.println("姓名:"+entry.getKey()+",年龄:"+entry.getValue());
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值