Java Collections Framework(2)

一、Map接口

  Map 接口没有继承Collection接口,意味着他不具备迭代器的方法

  Map与List、Set接口不同,它是由一系列键值对组成的集合,提供了key到Value的映射。在Map中它保证了key与value之间的一一对应关系。也就是说一个key对应一个value,所以它不能存在相同的key值,当然value值可以相同。

Map体系结构大致为:

 

二、HashMap

      哈希表数据结构实现,查找对象时通过哈希函数计算其位置,它是为快速查询而设计的,其内部定义了一个hash表数组 Node[ ] table (jdk 1.8 之前是Entry[ ] table)。通过哈希函数将元素的哈希地址转换成数组中的索引,如果有冲突,则使用散列链表的形式将所有相同哈希地址的元素串起来。

数据结构示意图:

 HashMap集合存取数据是怎么实现的?(HashMap的底层实现原理?)

底层存储数据的结构是”哈希表/散列表“,什么是哈希表?其实就是数组+链表的结合体:
     1.数组的数据查询效率极高,增删效率低;
     2.而链表的数据增删效率极高,查询效率低。
所以结合上述两种数据结构的优点,哈希表的随机增删,以及查询效率都很高
---->增删是在链表上完成,而查询只需要部分扫描即可。

map.put(k,v)实现原理:
第一步:先将k,v封装到Node对象中。
       Node对象包含四个属性(Object k;Object v;int hash; Node next)
       Node next:代表链表上下一个节点对象的地址值
第二步:底层调用k的HashCode()方法得出哈希值,然后通过哈希算法将其转换成数组下标(int hash)。
第三步:如果当前数组下标位置上没有元素,就把Node添加到这个位置上了;
      如果说对应下标的位置上有链表,此时会拿着k和链表上每一个节点的k进行equals比较,
      只有所有的比较结果都是false,那么这个新的节点才会被添加到链表末尾,只要有一个
      equals返回了true,那么这个节点的value将会被覆盖掉。

map.get(k)实现原理:
第一步:调用k的HashCode()方法得出哈希值,然后通过哈希算法将其转换成数组下标。
第二步:通过数组下标快速定位到某个位置上
第三步:如果当前数组下标位置上没有元素,返回的value即为null。如果这个位置上有单向链表,
      那么会拿着参数k和单向链表上的每个节点中的k进行equals,如果所有的equals方法返回false,
      那么get方法返回null。只要其中一个节点的k和参数k的比较结果是true,那么此时这个节点的value
      就是我们要找的value,get方法最终返回这个要找的value。

注意:
     同一个单向链表上的所有节点的hash值都相同,因为他们的数组下表都相同。
     但是同一个链表上的k与k的equals方法肯定是返回false,都不相等。

 疑问:hashCode()方法需要重写,在重写的时候返回一个固定值可以吗?会出现什么问题?

 假设一:将所有的hashCode()方法返回值都设定为某个值,那么会导致哈希表变成了纯单向链表。
       这种情况我们称为“散列分布不均匀”
 假设二:将所有的hashCode()方法返回值都设定为不一样的值,这样则会导致哈希表变成了一维数组了,
       没有链表的概念了。也是 “散列分布不均匀”。
 所以,哈希表HashMap在使用不当时会无法发挥其性能!
 --->那什么是“散列分布均匀”呢?
       假设有100个元素,10个单向链表,那么每个单向链表上有10个节点,这是最好的分布,我们则称为“散列分布均匀”。

【重点】放在HashMap集合key部分的元素,以及放在HashSet集合中的元素,需要同时重写hashCode方法和equals方法。
【重点】HashMap集合集合底层数组的初始化容量必须是2的倍数,这也是官方推荐的,这个是因为达到散列均匀,
为了提高HashMap集合的存取效率,所必须的。

HashMap集合的默认初始化容量时16,默认加载因子是0.75(也就是集合底层数组的容量达到75%时,数组开始扩容)
在JDK 1.8之后,如果哈希表单向链表中的元素超过8个,单向链表这种数据结构就会变成红黑树;
当红黑黑树上的节点数量小于6时,会重新把红黑树变成单向链表数据结构。
这种方式也是为了提高检索效率,二叉树的检索会再次缩小扫描范围。

 

JDK8.0相较于JDK7.0在底层实现方面的不同?

注意:

  • 在HashMap中的key允许为null,这样的key有且仅有一个 ; 而一个key或多个key所对应的value 也可以为null ;
  • 底层数组的初始化容量必须是2的倍数 , 如果手动指定的长度不是 2 的 倍数,会给你纠正为最近的一个2 的幂次方的值。

三、LinkedHashMap

     LinkedHashMap是HashMap的一个子类,它保留插入的顺序,如果需要输出的顺序和输入时的相同,那么就选用LinkedHashMap。底层是通过哈希表和链表实现的,它通过维护一个链表来保证对哈希表迭代时的有序性,而这个有序是指键值对插入的顺序。
    根据链表中元素的顺序可以分为:按插入顺序的链表,和按访问顺序(调用get方法)的链表。默认是按插入顺序排序,如果指定按访问顺序排序,那么调用get方法后,会将这次访问的元素移至链表尾部,不断访问可以形成按访问顺序排序的链表。
     由于LinkedHashMap需要维护元素的插入顺序,因此性能略低于HashMap的性能,但在迭代访问Map里的全部元素时将有很好的性能,因为它以链表来维护内部顺序。LinkedHashMap 的大致实现如下图所示,当然链表和哈希表中相同的键值对都是指向同一个对象,这里把它们分开来画只是为了呈现出比较清晰的结构。

四、HashTable

  Hashtable同样是基于哈希表实现的,同样每个元素是一个key-value对,其内部也是通过单链表解决冲突问题,容量不足(超过了阀值)时,同样会自动增长。Hashtable也是JDK1.0引入的类,是线程安全的,能用于多线程环境中。

HashTable和HashMap区别?

  • Hashtable继承自Dictionary类,而HashMap继承自AbstractMap类。但二者都实现了Map接口。
  • Hashtable 中的方法是Synchronize的,而HashMap中的方法在缺省情况下是非Synchronize的。
  • HashMap把Hashtable的contains方法去掉了,改成containsValue和containsKey,因为contains方法容易让人引起误解。 Hashtable则保留了contains,containsValue和containsKey三个方法,其中contains和containsValue功能相同。Hashtable的ContainsKey方法和ContainsValue的源码:
    public boolean containsValue(Object value) {      
         return contains(value);      
     }
  • Hashtable中,key和value都不允许出现null值。但是如果在Hashtable中有类似put(null,null)的操作,编译同样可以通过,因为key和value都是Object类型,但运行时会抛出NullPointerException异常,这是JDK的规范规定的。HashMap中,null可以作为键,当get()方法返回null值时,可能是 HashMap中没有该键,也可能使该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断。
  • 遍历的内部实现不同,  Hashtable、HashMap都使用了 Iterator。而由于历史原因,Hashtable还使用了Enumeration的方式 。
  • 内部实现使用的数组初始化和扩容方式不同,HashTable中hash数组默认大小是11,增加的方式是将容量变为原来的2倍加1。

五、TreeMap

     TreeMap 是一个有序的key-value集合,非同步基于红黑树(Red-Black tree)实现,每一个key-value节点作为红黑树的一个节点。TreeMap存储时会进行排序的,会根据key来对key-value键值对进行排序,其中排序方式也是分为两种,一种是自然排序,一种是定制排序,具体取决于使用的构造方法。

自然排序:TreeMap中所有的key必须实现Comparable接口,并且所有的key都应该是同一个类的对象,否则会报ClassCastException异常。

定制排序:定义TreeMap时,创建一个comparator对象,该对象对所有的treeMap中所有的key值进行排序,采用定制排序的时候不需要TreeMap中所有的key必须实现Comparable接口。

TreeMap判断两个元素相等的标准:两个key通过compareTo()方法返回0,则认为这两个key相等。

如果使用自定义的类来作为TreeMap中的key值,且想让TreeMap能够良好的工作,则必须重写自定义类中的equals()方法,TreeMap中判断相等的标准是:两个key通过equals()方法返回为true,并且通过compareTo()方法比较应该返回为0。

 

posted @ 2021-09-10 17:24  danielzzz  阅读(111)  评论(0编辑  收藏  举报