java中的Map集合详情

一、Map结构:

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

二、JDK7与JDK8的HashMap区别

既然讲HashMap,那就不得不说一下JDK7与JDK8(及jdk8以后)的HashMap有什么区别:

HaspMap数组的每一个元素不止是一个Entry对象,也是一个链表的头节点(jdk7),每一个Entry对象通过Next指针指向下一个Entry对象,这样,当新的Entry的hash值与之前的存在冲突时,只需要插入到对应点链表即可(jdk7).

jdk8中添加了红黑树,当某一个数组索引位置上的链表长度 >=8并且当前数组长度大于64,则会把链表变成红黑树 。
链表新节点插入链表的顺序不同(jdk7是插入头结点,jdk8因为要把链表变为红 黑树所以采用插入尾节点)
hash算法简化 ( jdk8 )
resize的逻辑修改(jdk7会出现死循环,jdk8不会)

三、HashMap的容量与扩容机制

HashMap:默认初始容量为16
     (为何是16:16是2^4,可以提高查询效率,另外,32=16<<1 –>至于详细的原因可另行分析,或分析源代码)
     加载因子为0.75:即当 元素个数 超过 容量长度的0.75倍 时,进行扩容
     扩容增量:原容量的 1 倍,新容量为原容量的2倍
      如 HashSet的容量为16,一次扩容后是容量为32

阈值(threshold) = 负载因子(loadFactor) x 容量(capacity)

阈值(threshold) = 负载因子(loadFactor) x 容量(capacity)
当HashMap中table数组(也称为桶)长度 >= 阈值(threshold) 就会自动进行扩容。

扩容的规则是这样的,因为table数组长度必须是2的次方数,扩容其实每次都是按照上一次tableSize位运算得到的就是做一次左移1位运算,
假设当前tableSize是16的话 16转为二进制再向左移一位就得到了32 即 16 << 1 == 32 即扩容后的容量,也就是说扩容后的容量是当前
容量的两倍,但记住HashMap的扩容是采用当前容量向左位移一位(newtableSize = tableSize << 1),得到的扩容后容量,而不是当前容量x2

扫描二维码关注公众号,回复: 12911558 查看本文章
    /**
     * The load factor used when none specified in constructor.
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    /**
     *默认的负载因子是0.75f,也就是75% 负载因子的作用就是计算扩容阈值用,
     比如说使用*无参构造方法创建的HashMap 对象,
     他初始长度默认是16  阈值 = 当前长度 * 0.75= 12  就
     *能算出阈值,当当前长度大于等于阈值的时候HashMap就会进行自动扩容
     */

HashMap实现原理:

在这里插入图片描述
在这里插入图片描述

HashMap存储原理与存储流程

1.HashMap存储原理

获取到传过来的key,调用hash算法获取到hash值

获取到hash值之后调用indexFor方法,通过获取到的hash值以及数组的长度算
出数组的下标 (把哈希值和数组容量转换为二进,再在数组容量范围内与哈希值
进行一次与运算,同为1则1,不然则为0,得出数组的下标值,这样可以保证计算出的数组下标不会大于当前数组容量)

把传过来的key和value存到该数组下标当中。

如该数组下标下以及有值了,则使用链表,jdk7是把新增元素添加到头部节点 jdk8则添加到尾部节点。

2.HashMap存储流程

前面寻址算法都是一样的,根据key的hashcode经过高低位异或之后的值,再按位与 &(table.lingth - 1),得到一个数组下标,然后根据这个数组下标内的状况,状况不同,然后情况也不同,大概分为了4种状态:

( 1.)第一种就是数组下标下内容为空:
这种情况没什么好说的,为空据直接占有这个slot槽位就好了,然后把当前.put方法传进来的key和value包装成一个node对象,放到这个slot中就好了。

( 2.)第二种情况就是数组下标下内容不为空,但它引用的node还没有链化:
这种情况下先要对比一下这个node对象的key与当前put对象的key是否完全.相等,如果完全相等的情况下,就行进行replace操作,把之前的槽位中node.下的value替换成新的value就可以了,否则的话这个put操作就是一个正儿.八经的hash冲突,这种情况在slot槽位后面追加一个node就可以了,用尾插法 ( 前面讲过,jdk7是把新增元素添加到头部节点,而jdk8则添加到尾部节点)。

( 3.)第三种就是该数组下标下内容已经被链化了:
这种情况和第二种情况处理很相似,首先也是迭代查找node,看看链表上中元素的key,与当前传过来的key是否完全一致,如果完全一致的话还是repleace操作,用put过来的新value替换掉之前node中的value,否则的话就是一致迭代到链表尾节点也没有匹配到完全一致的node,就和之前的一样,把put进来数据包装成node追加到链表的尾部,再检查一下当前链表的长度,有没有达到树化阈值,如果达到了阈值就调用一个树化方法,树化操作都是在这个方法里完成的。

( 4.)第四种情况就是冲突很严重的情况下,这个链表已经转化成红黑树了:
红黑树就比较复杂 要将清楚这个红黑树还得从TreeNode说起 TreeNode继承了Node结构,在Node基础上加了几个字段,分别是指向父节点parent字段,指向左子节点left字段,指向右子节点right字段,还有一个表示颜色的red字段,这就是TreeNode的基本结构,然后红黑树的插入操作,首先找到一个合适的插入点,就是找到插入节点的父节点,然后红黑树它又满足二叉树的所有特性,所以找这个父节点的操作和二叉树排序是完全一致的,然后说一下这个二叉树排序,其实就是二分查找算法映射出来的结构,就是一个倒立的二叉树,然后每个节点都可以有自己的子节点,本且左节点小于但前节点,右节点大于当前节点,然后每次向下查找一层就能那个排除掉一半的数据,查找效率非常的高效,当查找的过程中也是分情况的。

首先第一种情况就是一直向下探测,直到查询到左子树或者右子树位null,说明整个树中,并没有发现node链表中的key与当前put key一致的TreeNode,那此时探测节点就是插入父节点的所在了,然后就是判断插入节点的hash值和父节点的hash值大小决定插入到父节点的左子树还是右子树。当然插入会打破平衡,还需要一个红黑树的平衡算法保持平衡。

其次第二种情况就是根节点在向下探测过程中发现TreeNode中key与当前put的key完全一致,然后就也是一次repleace操作,替换value。

总结hashmap存储过程:

在这里插入图片描述

面试题一:为什么HashMap的默认负载因子是0.75,而不是0.5或者是整数1呢?
答案有两种:

理论上来讲,负载因子越大,导致哈希冲突的概率也就越大(即链表的情况比较多),负载因子越小,费的空间也就越大(即数组的利用率比较低,在容量一半的时候就开始扩容),这是一个无法避免的利弊关系,所以通过一个简单的数学推理,可以测算出这个数值在0.75左右是比较合理的。

阈值(threshold) = 负载因子(loadFactor) x 容量(capacity) 根据HashMap的扩容机制,他会保证容量(capacity)的值永远都是2的幂 为了保证负载因子x容量的结果是一个整数,这个值是0.75(4/3)比较合理,因为这个数和任何2的次幂乘积结果都是整数。

四、HashMap的结构

JDK7与JDK8及以后的HashMap结构与存储原理有所不同:

Jdk1.7:数组 + 链表 ( 当数组下标相同,则会在该下标下使用链表)
Jdk1.8:数组 + 链表 + 红黑树 (当某一个数组索引位置上的链表长度 >=8并且当前数组长度大于64,则会把链表变成红黑树 )

Jdk1.7中链表新元素添加到链表的头结点,先加到链表的头节点,再移到数组下标位置
Jdk1.8中链表新元素添加到链表的尾结点(数组通过下标索引查询,所以查询效率非常高,链表只能挨个遍历,效率非常低。jdk1.8及以上版本引入了红黑树,当链表的长度大于或等于8的时候则会把链表变成红黑树,以提高查询效率)。

LinkedHashMap:

在这里插入图片描述
Map常用方法;


//        Hashmap存值:----------------------------------》 .put("key","value"); ----------》无返回值。
//
//        Hashmap取值:----------------------------------》 .get("key");-------------------》 返回Value的类型。
//
//        Hashmap判断map是否为空:-----------------------》 .isEmpty(); -------------------》返回boolean类型。
//
//        Hashmap判断map中是否存在这个key:--------------》.containsKey("key");------------》返回boolean类型。
//
//        Hashmap判断map中是否含有value:----------------》.containsValue("value");-------》返回boolean类型。
//
//        Hashmap删除这个key值下的value:----------------》.remove("key");-----------------》返回Value的类型。
//
//        Hashmap显示所有的value值:---------------------》.values(); --------------------》返回Value的类型。
//
//        Hashmap显示map里的值得数量:-------------------》.size(); ----------------------》返回int类型
//
//        HashMap显示当前已存的key:---------------------》 .keySet();-------------------》返回Key的类型数组。
//
//        Hashmap显示所有的key和value:-----------------》.entrySet());------------------》返回Key=Value类型数组。
//
//        Hashmap添加另一个同一类型的map:--------------》.putAll(map); -----------------》(参数为另一个同一类型的map)无返回值。
//
//        Hashmap删除这个key和value:------------------》.remove("key", "value");-------》(如果该key值下面对应的是该value值则删除)返回boolean类型。
//
//        Hashmap替换这个key对应的value值(JDK8新增):---》.replace("key","value");-------》返回被替换掉的Value值的类型。
//
//        克隆Hashmap:-------------------------------》.clone(); ---------------------》返回object类型。
//
//        清空Hashmap:-------------------------------》.clear(); ---------------------》无返回值。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Map的遍历:

在这里插入图片描述
第一种:遍历所有的key集keySet():

在这里插入图片描述
在这里插入图片描述

第二种:遍历所有的values集values():
在这里插入图片描述
第三种:遍历所有的key-value集entrySet():

在这里插入图片描述
总结:

在这里插入图片描述

HashTable:

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

Collection和Collectons:

1、java.util.Collection 是一个单列集合接口。它提供了对集合对象进行基本操作的通用接口方法。Collection接口在Java 类库中有很多具体的实现。Collection接口的意义是为各种具体的集合提供了最大化的统一操作方式。

List,Set,Queue接口都继承Collection。
直接实现该接口的类只有AbstractCollection类,该类也只是一个抽象类,提供了对集合类操作的一些基本实现。List和Set的具体实现类基本上都直接或间接的继承了该类。

2、java.util.Collections 是一个工具类。它包含有各种有关集合操作的静态方法(对集合的搜索、排序、线程安全化等),大多数方法都是用来处理线性表的。此类不能实例化,就像一个工具类,服务于Java的Collection框架。
Collections常用方法:

1.1) 排序( sort(List list)):按照元素的自然顺序对指定List集合元素按照升序排序。
1.2) 排序(Sort(List,Comparator)):根据指定的Comparator产生的顺序对List参数集合进行排序。
2) 混排(Shuffling(List)):对集合元素进行随机排序。
3) 反转(Reverse(list)):反转List中元素的顺序。
4) swap(List list, int i, int j):将指定list集合中的i处元素和J处元素进行交换。
5)fill(List list, Object obj) :使用指定的对象填充指定列表的所有元素
6)copy(List dest, List src) :是把源列表中的数据覆盖到目标列表
7) 拷贝(Copy(List desc,List src)):将src中的内容赋值到desc集合中。
8) 返回Collections中最小元素(min)
9) 返回Collections中最大元素(max)
10)frequency(List list,Object obj):计算obj在list中重复项出现的次数(Collections.frequency在JDK 1.5版本以后支持).
11) binarySearch(List list, Object key):二分查找 ,使用二分查找法查找指定元素在指定列表的索引位置使用二分搜索查找key对象的索引值,因为使用的二分查找,所以前提是必须有序。

在这里插入图片描述
部分代码演示:frequency(List list,Object obj):计算obj在list中重复项出现的次数
在这里插入图片描述
集合复制代码演示:拷贝(Copy(List desc,List src)):将src中的内容赋值到desc集合中。
在这里插入图片描述
3.将集合转换成线程安全的方法:

在这里插入图片描述
在这里插入图片描述

Map<Object, Object> map = Collections.synchronizedMap(new HashMap<>());

猜你喜欢

转载自blog.csdn.net/weixin_38568503/article/details/113932582