Java集合:浅谈 Map集合的原理,底层以及使用

文章目录

    • Map接口:
      • Map底层
      • HashMap:
      • HashMap何时扩容,扩容的算法是什么
      • Map的遍历方式:
      • AbstractMap抽象类
      • SortedMap接口
      • HashMap和HashTable对比
      • Map.Entry:
      • Map里面的为null的情况

Map接口:

首先是基础以及常用方法:
Map是用来存储一组值的一个集合,每一组值都是采用key–>value的形式进行存储,其中key是不可以重复的,每一个key都唯一的指向一个value.

Map接口中提供的常用的方法:
1.public V put(K key, V value):向Map集合中添加数据的方法,其中key指定键的值,value指定值所代表的数据
2.public V get(Object key):根据参数key找到key所对应的value
3.keySet():返回由所有的key组成的Set集合
4.values() :返回由所有的value组成的Collection集合
5.clear():清空Map集合中的全部的数据
6.Boolean containsKey(Object key):判断集合中是否包含参数key
7.Boolean containsValue(Object value) :判断集合中是否包含参数value
8.public V remove(Object key):把指定的键 所对应的键值对元素 在Map集合中删除,可返回被删除元素的值。,不接收就不返回
9.size() :返回集合中的元素的个数
10.entrySet():返回Map集合中由Map.Entry实例化后的对象组成的Set集合

//Set> entrySet(): 获取到Map集合中所有的键值对对象的集合(Set集合)。

11.Map.getOrDefault(Object key, V defaultValue)方法的作用是:
  当Map集合中有这个key时,就使用这个key值;
  如果没有就使用默认值defaultValue。

返回值:V
        存储键值对的时候,key不重复,返回值v是null
        存储键值对的时候,key重复,会使用新的value替换map中重复的value,返回被替换的value值

注意:

Map接口下的集合存储的键值对,而键值对其实是以Entry对象的形式存在Map集合中。即Map中存的是Entry对象

Map常用的子类:

Map底层

Hash Map底层是哈希表。使用HashMap要求添加的键类明确定义了hashCode()和equals()[可以重写hashCode()和equals()],为了优化HashMap空间的使用,您可以调优初始容量和负载因子。

JDK1.8之前,哈希表底层采用数组+链表实现,即使用链表处理冲突,同一hash值的链表都存储在一个链表里。但是当位于一个桶中的元素较多,即hash值相等的元素较多时,通过key值依次查找的效率较低。

JDK1.8中,哈希表存储采用数组+链表+红黑树实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。如下图(其他博客某个大佬的图):

Java集合:浅谈 Map集合的原理,底层以及使用_第1张图片
​ 说明:

1,进行键值对存储时,先通过hashCode()计算出键(K)的哈希值,然后再数组中查询,如果没有则保存。

2,但是如果找到相同的哈希值,那么接着调用equals方法判断它们的值是否相同。只有满足以上两种条件才能认定为相同的数据,因此对于Java中的包装类里面都重写了hashCode()和equals()方法。

JDK1.8引入红黑树大程度优化了HashMap的性能,根据对象的hashCode和equals方法来决定的。如果我们往集合中存放自定义的对象,那么保证其唯一,就必须复写hashCode和equals方法建立属于当前对象的比较方式。

TreeMap:底层是红黑树(一般是用于需要有序的Map,且因为一直是平衡的,所以不要调优)

红黑树其实就是平衡二叉树,其还有五点定义:

(1)节点是黑色或者红色。

(2)根节点是黑色。

(3)每个红色节点的两个子节点都是黑色节点。

(4)每个叶子的节点都是黑色的空节点(NULL)。

(5)从任意节点到其每个叶子节点的所有路径都包含相同数量的黑色节点。

红黑树在增删的时候,需要先进行节点变色,如果变色已经解决不了了,就相应进行旋转,旋转之后如果不成功,再重复上述步骤

Java集合:浅谈 Map集合的原理,底层以及使用_第2张图片

平衡二叉树(Balanced Binary Tree)又被称为AVL树(有别于AVL算法),且具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。这个方案很好的解决了二叉查找树退化成链表的问题,把插入,查找,删除的时间复杂度最好情况和最坏情况都维持在O(logN)。但是频繁旋转会使插入和删除牺牲掉O(logN)左右的时间,不过相对二叉查找树来说,时间上稳定了很多。平衡二叉树(Balanced Binary Tree)又被称为AVL树(有别于AVL算法),且具有以下性质:它是一 棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。这个方案很好的解决了二叉查找树退化成链表的问题,把插入,查找,删除的时间复杂度最好情况和最坏情况都维持在O(logN)。但是频繁旋转会使插入和删除牺牲掉O(logN)左右的时间,不过相对二叉查找树来说,时间上稳定了很多。

HashMap:

基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了非同步和允许使用 null 之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。使用链地址法解决哈希冲突

其常用方法就是Map接口提供的方法

HashMap何时扩容,扩容的算法是什么

存储原理

在java7中叫做Entry,在java8当中叫做Node

img

在插入的时候,他会根据key的hash去计算一个index(下标),然后存储

每个节点(Ebtry、Node),包括了hash值(通过hash计算出来的)、key的值、Value的值以及next(指向下一个节点)

其中链表插入的方式,以及为什么改为尾插法

jdk1.8以前用头插法,1.8之后使用尾插法

第一个是因为我们在java8中,引入了红黑树,我们当链表的长度大于8个时,开始转变红黑树而当红黑树的节点个数小于6个(默认值)以后,又开始使用链表。我们在进行尾插法的时候,还能够遍历一遍size(),来判断要不要进行树的转变

第二个就是:避免多线程死锁(谨慎)就是hashmap线程不安全的原因

hashMap的扩容机制

imgimg

  • Capacity:HashMap当前长度。
  • LoadFactor:负载因子,默认值0.75f

如果当前存储的数量 > 负载因子 * 当前长度,就进行扩容,扩容是怎么样来扩的呢?

分为两步

  • 扩容:创建一个新的Entry空数组,长度是原数组的2倍。
  • ReHash:遍历原Entry数组,把所有的Entry重新Hash到新数组。

为什么要重新哈希

我们来看HashMap的Hash方法,Hash方法是:

Hash的公式—> index = HashCode(Key) & (Length - 1)

扩容之前&8,扩容之后&16,肯定就不一样了

扩容的算法是什么:

扩容(resize)就是重新计算容量,向HashMap对象里不停的添加元素,而HashMap对象内部的数组无法装载更多的元素时,对象就需要扩大数组的长度,以便能装入更多的元素。当然Java里的数组是无法自动扩容的,方法是使用一个新的数组代替已有的容量小的数组

Map的遍历方式:

//未改

        System.out.println("方式一:通过Map.keySet遍历key和value:");
        for (Integer key : map.keySet()) {
            String s = map.get(key);
            System.out.println(key + ": " + s);
        }
        System.out.println();
        System.out.println("方式二:通过Map.entrySet遍历key和value:");
        System.out.println("推荐,尤其是容量大时");
        for (Map.Entry<Integer, String> entry : map.entrySet()) {
            System.out.println(entry.getKey() + ": " + entry.getValue());
        }
        System.out.println();
        System.out.println("方式三:通过Map.entrySet使用iterator遍历key和value:");
        Iterator<Map.Entry<Integer, String>> iterator = map.entrySet().iterator();
        while (iterator.hasNext()) {
        Map.Entry<Integer, String> entry = iterator.next();
        System.out.println(entry.getKey() + ": " + entry.getValue());
        }
        System.out.println();
     System.out.println("方式四:通过Map.values()遍历所有的value,但不能遍历key");
        for (String value : map.values()) {
          System.out.println(value);
        }

AbstractMap抽象类

AbstractMap抽象类:(HashMap继承AbstractMap)覆盖了equals()和hashCode()方法以确保两个相等映射返回相同的哈希码。如果两个映射大小相等、包含同样的键且每个键在这两个映射中对应的值都相同,则这两个映射相等。映射的哈希码是映射元素哈希码的总和,其中每个元素是Map.Entry接口的一个实现。因此,不论映射内部顺序如何,两个相等映射会报告相同的哈希码。

SortedMap接口

SortedMap接口:(TreeMap继承自SortedMap)它用来保持键的有序顺序。SortedMap接口为映像的视图(子集),包括两个端点提供了访问方法。除了排序是作用于映射的键以外,处理SortedMap和处理SortedSet一样。添加到SortedMap实现类的元素必须实现Comparable接口,否则您必须给它的构造函数提供一个Comparator接口的实现。TreeMap类是它的唯一一份实现。

HashMap和HashTable对比

Hashtable

(1)Hashtable 是一个散列表,它存储的内容是键值对(key-value)映射。

(2)Hashtable 的函数都是同步的,这意味着它是线程安全的。它的key、value都不可以为null。

(3)HashTable直接使用对象的hashCode。

HashMap:

(1)由数组+链表组成的,基于哈希表的Map实现,数组是HashMap的主体,链表则是主要为了解决哈希冲突而存在的。

(2)不是线程安全的,HashMap可以接受为null的键(key)和值(value).

(3)HashMap重新计算hash值

Hashtable,HashMap,Properties继承关系如下:

public class Hashtable<K,V> extends Dictionary<K,V>
    implements Map<K,V>, Cloneable, java.io.Serializable

public class HashMap<K,V>extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable  
java.lang.Objecct 
  java.util.Dictionary<K,V> 
    java.util.Hashtable<Object,Object> 
      java.util.Properties 

Map.Entry:

Map.Entry是Map的一个内部接口。

Map提供了一些常用方法,如keySet()、entrySet()等方法,keySet()方法返回值是Map中key值的集合;entrySet()的返回值也是返回一个Set集合,此集合的类型为Map.Entry。

Map.Entry是Map声明的一个内部接口,此接口为泛型,定义为Entry。它表示Map中的一个实体(一个key-value对)。接口中有getKey(),getValue方法。

Map.Entry使用

Map集合中有一个方法entrySet():返回Map集合中由Map.Entry实例化后的对象组成的Set集合,所以我们可以直接遍历该set集合,每个元素都有getKey(),getValue方法

//所以遍历Map集合能更加简便:
	Map<String,String> map = new HashMap<String,String>();
		map.put("1","李四");
		map.put("2","李五");
		map.put("3","李六");
		Set entry = map.entrySet();//此时map的entry对象已经全部存入set
		Iterator iterator = entry.iterator( );//获取set集合里的迭代器
		if(entry!=null){
			while(iterator.hasNext()){//迭代Set集合
				Map.Entry entry1 = (Entry) iterator.next();//创Map.Entry类型对象接收每个Entry对象		    
				System.out.println("key:	"+entry1.getKey()+"	values:	"+entry1.getValue());
			}
		}

Map里面的为null的情况

集合类 Key Value Super 说明
Hashtable 不允许为 null 不允许为 null Dictionary 线程安全
ConcurrentHashMap 不允许为 null 不允许为 null AbstractMap 锁分段技术(JDK8:CAS)
TreeMap 不允许为 null 允许为 null AbstractMap 线程不安全
HashMap 允许为 null 允许为 null AbstractMap 线程不安全

List都可以添加null元素

HashMap可以有1个key为null的元素,TreeMap不能有key为null的元素
Set底层是Map

所以HashSet可以有1个null的元素,TreeSet不能有key为null的元素。

你可能感兴趣的:(JavaSE,java)