java基础之集合略解

Java集合:整体结构

HashMap剖析

Java 集合系列10之 HashMap详细介绍(源码解析)和使用示例

集合类结构

Java中的集合包含多种数据结构,如链表、队列、哈希表等。从类的继承结构来说,可以分为两大类,一类是继承自Collection接口,这类集合包含List、Set和Queue等集合类。另一类是继承自Map接口,这主要包含了哈希表相关的集合类。下面我们看一下这两大类的继承结构图:

1、List、Set和Queue


2、Map:

图中的绿色的虚线代表实现,绿色实线代表接口之间的继承,蓝色实线代表类之间的继承。

List

(1)List:我们用的比较多List包括ArrayListLinkedList,这两者的区别也很明显,从其名称上就可以看出。ArrayList的底层的通过数组实现,所以其随机访问的速度比较快,但是对于需要频繁的增删的情况,效率就比较低了。而对于LinkedList,底层通过链表来实现,所以增删操作比较容易完成,但是对于随机访问的效率比较低。

至于Vector,它是ArrayList的线程安全版本,而Stack则对应栈数据结构。

Queue

(2)Queue:一般可以直接使用LinkedList完成,从上述类图也可以看出,LinkedList继承自Deque(双端队列),所以LinkedList具有双端队列的功能。PriorityQueue的特点是为每个元素提供一个优先级,优先级高的元素会优先出队列。

Set

Set与List的主要区别是Set是不允许元素重复的,且存取是无序的(存入和取出不按照顺序),而List则可以允许元素重复的且存取有序。判断元素的重复需要根据对象的hash方法和equals方法来决定。这也是我们通常要为集合中的元素类重写hashCode方法和equals方法的原因。

HashSet:为快速查找设计的Set。存入HashSet的对象必须定义hashCode()。 

TreeSet: 保存次序的Set, 底层为树结构。使用它可以从Set中提取有序的序列。 

LinkedHashSet:具有HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序)。于是在使用迭代器遍历Set时,结果会按元素插入的次序显示。

Map

Map类型的集合最大的优点在于其查找效率比较高,理想情况下可以实现O(1)的时间复杂度。

Map中最常用的是HashMapLinkedHashMap与HashMap的区别在于前者能够保证插入集合的元素顺序与输出顺序一致。

HashMapTreeMap的区别,与之前提到的HashSet与TreeSet的区别是一致的

HashSet和TreeSet本质上分别是通过HashMap和TreeMap来实现的



TreeSet和TreeMap存储原理

TreeSet:按特定顺序存放元素,底层结构是红黑二叉树。

红黑树算法的规则: 左小右大。

既然TreeSet可以自然排序,那么TreeSet必定是有排序规则的。

1:让存入的元素自定义比较规则。

元素自身具备比较性,需要元素实现Comparable接口,重写compareTo方法,也就是让元素自身具备比较性,这种方式叫做元素的自然排序也叫做默认排序。

2:给TreeSet指定排序规则。

当元素自身不具备比较性,或者自身具备的比较性不是所需要的。那么此时可以让容器自身具备。需要定义一个类实现接口Comparator,重写compare方法,并将该接口的子类实例对象作为参数传递给TreeMap集合的构造方法。

注意:当Comparable比较方式和Comparator比较方式同时存在时,以Comparator的比较方式为主;

当compareTo()函数返回值为0时,说明两个对象相等,此时该对象不会添加进来。

为什么使用TreeSet存入字符串,字符串默认输出是按升序排列的?

因为字符串实现了一个接口,叫做Comparable 接口.字符串重写了该接口的compareTo 方法,所以String对象具备了比较性.那么同样道理,我的自定义元素(例如Person类,Book类)想要存入TreeSet集合,就需要实现该接口,也就是要让自定义对象具备比较性.



TreeMap:TreeMap根据集合中的键进行排序

方式一:元素自身具备比较性

和TreeSet一样原理,需要让存储在位置的对象实现Comparable接口,重写compareTo方法,也就是让元素自身具备比较性,这种方式叫做元素的自然排序也叫做默认排序。

方式二:容器具备比较性

当元素自身不具备比较性,或者自身具备的比较性不是所需要的。那么此时可以让容器自身具备。需要定义一个类实现接口Comparator,重写compare方法,并将该接口的子类实例对象作为参数传递给TreeMap集合的构造方法。

当key比较相同时,value会覆盖之前的value。



HashSet和HashMap存储原理

HashSet:元素加入Set之前需要先执行hashCode方法,如果返回的值在集合中已存在,则要继续执行equals方法,如果equals方法返回的结果也为真,则证明该元素已经存在,会将新的元素覆盖老的元素,如果返回hashCode值不同,则直接加入集合。



HashMap:

结构图

1.HashMap是一个数组+链表的结构,数组的下标在HashMap中称为Bucket(水桶)值,每个数组项对应的是一个List(单向链表)

2.每个List中存放的是Entry对象,这个Entry对象包含键和值以及下一个Entry的地址。(其实就是数组中存放的是Entry对象,而Entry对象实际上是一个单向链表)。

存储过程:

1.通过key的hashCode()函数计算key的hash值,再通过hash值得出Bucket值(如何通过hash值的出Bucket值,哈希值 % 数组容量 = bucketIndex )

2.得出Bucket值后,如果该Bucket值对应的list是一个空列表,那么将生成entry对象,插入到list中,做为list的第一个元素

3.如果该Bucket值对应的list中已经有其它对象了(如果两个key的hash值一样就会发生),这个时候就发生了碰撞。

4.发生了碰撞后,新插入的key就会和链表中的其它entry的key进行比较,比较过程需要用到equals()方法

如果两个key的hashcode一样,并且equals方法比较也相同,那么HashMap就判断这两个key完全一样

5.如果两个key比较结果一样,由于HashMap不允许同时存在两个相同的key,那么新插入的entry就会覆盖旧entry

6.如果比较不一样,那么将新的entry插入到list的末尾


那么在这里,Entry是Map的内部接口,HashMap中的内部类Node(JDK1.8以前是Entry)实现了Map.Entry,HashMap内部通过Node数组存储数据。所以若要遍历HashMap,可获取entrySet再遍历。

transient Node[] table;

HashMap为何如此设计

HashMap中的数据结构是数组+单链表的组合,我们希望的是元素存放的更均匀,最理想的效果是,Entry数组中每个位置都只有一个元素,这样,查询的时候效率最高,不需要遍历单链表,也不需要通过equals去比较Key,而且空间利用率最大。那么怎么才能将分布最大的均匀化呢?那就是取余运算%,哈希值 % 数组容量 = bucketIndex

JDK1.8的修改

在JDK1.6中,HashMap采用数组+链表实现,即使用链表处理冲突,同一hash值的节点都存储在一个链表里。但是当位于一个链表中的元素较多时,通过key值依次查找的效率较低。而JDK1.8中,HashMap采用数组+链表+红黑树来实现,当链表长度超过阈值(8)时,将链表转换为红黑树,这样大大减少了查找时间。

你可能感兴趣的:(java基础之集合略解)