在Java中集合是常用的数据结构,而Map
与Collection
的实现类有很多,我们常见的有HashMap
、HashTable
、TreeMap
、LinkedHashMap
、ArrayList
、Linkedlist
、Vector
、HashSet
、TreeSet
、LinkedHashSet
;
下面从源码的层面简析这些实现类的特性;
Map
先看看Map
接口的继承关系图
Map是顶级接口,声明了通用的抽象方法,如获取所有键值对的方法——entrySet
;以及还有静态的内部接口Entry
;使用entrySet
遍历Map集合的方式效率高于使用keySet
遍历方式;keySet
会遍历两次集合,第一次将集合转为Iterator
对象,第二次重hashMap
中取出key所对应的entrySet
;而entrySet
只是遍历一次就把key和value放入了entry
中。如果是在JDK8,使用Map.forEach
方法更好。
values()
返回的是 V 值集合,是一个 list 集合对象;keySet()
返回的是 K 值集合,是一个 Set 集合对象;entrySet()
返回的是 K-V 值组合集合。
AbstractMap
抽象类AbstractMap
实现了Map
接口的”骨干”方法,目的是减少实现Map
接口的时需要重写的方法。
如其中实现的keySet
方法用于获得Map
中的所有Key值Set集合,内部创建AbstractSet
的匿名子类实例存放key值;实现的values
方法中,创建了AbstractCollection
匿名子类实例,用于存放map
中value值;
HashMap
HashMap
继承自AbstractMap
并实现了Map
等接口,HashMap
中定义了名为Node
的静态内部类,Node
实现了Map.Entry
接口,是一个单向链表,然后使用Node[]
数组,完成了HashMap
的存储结构(数组+链表/红黑树)。
HashMap
中还定义了一个静态内部类,TreeNode
,TreeNode
继承自LinkedHashMap.Entry
,TreeNode
实现了红黑树数据结构。
在计算Key的Hash值时,如果Key为空,则Hash值为0
put()
1 | public V put(K key, V value) { |
方法的入口是put(K,V)
,但核心逻辑是在putVal(int, K k, V , boolean ,boolean)
;核心做了以下工作:
- 计算下标
- 判断Hash表状态,表容量不够则扩容
- 查找要插入的键值对是否存在,不存在则直接放入,键相同时,用链表的方式连接到相应的Hash值后面
- 如果链表长度超过阈值,将链表转换成红黑树
- 如果键存在,则更新旧值
get()
1 | public V get(Object key) { |
get()
方法相比put()
更简单,先计算Hash值,再根据Hash值遍历后面的链表,调用Key.equals()
方法进行匹配。
小结
HashMap
使用Hash表作为数据结构,线程不安全,键值可以为空,自定义对象作为键时,需要重写equals()
和HashCode()
方法;如果需要满足线程安全,可以用 Collections
的 synchronizedMap
方法使HashMap
具有线程安全的能力,或者使用 ConcurrentHashMap
。
LinkedHashMap
LinkedHashMap
继承自HashMap
,并实现了Map
接口,其中定义的静态内部类Entry
继承自HashMap.Node
类,在Entry
中定义了前后节点,实现了Hash表和双向链表数据结构。通过双向链表,保证了数据存取顺序一致。
TreeMap
TreeMap继承自AbstractMap
,直接实现了NavigableMap
接口,NavigableMap
是Map
的间接子接口;TreeMap中定义的静态内部类Entry
实现了Map.Entry
接口,并添加了父、左右还有节点属性,实现了红黑树结构;TreeMap内部自动自然排序,如果要存储自定义对象需要创建比较器(实现Comparator
接口)或自定义类实现Comparable
接口,重写compareTo
方法;
Hashtable
Hashtable
继承自Dictionary
,实现了Map
接口。其中的私有静态内部类Entry
实现了Map.Entry
接口,并增加了增加了hash
、key
、value
、next
成员属性,实现了单向链表结构,再结合数组,实现了Hash
表存储结构。
其中的public
成员方法都使用了synchronized
修饰,所以线程安全但效率相对较低。
键值都不可以为null。
Collection
Collection 层次结构中的根接口。Collection 表示一组对象,这些对象也称为 collection 的元素。一些 collection 允许有重复的元素,而另一些则不允许。一些 collection 是有序的,而另一些则是无序的。JDK 不提供此接口的任何直接实现:它提供更具体的子接口(如
Set
和List
)实现。此接口通常用来传递 collection,并在需要最大普遍性的地方操作这些 collection。
AbstractCollection
AbstractCollection
抽象类提供了Collection
接口的骨干实现,减少子类需要实现的方法,如contains()
;
List
List
接口继承自Collection
接口,该接口下的实现类为有序列表,可以根据索引访问其中的元素,可以有多个null元素。
LinkedList
LinkedList
继承自AbstractCollection
的子类AbstractSequentialList
;LinkedList
中定义了静态内部类Node
,Node
中定义了前驱与后继节点(Node
),这实现了单向链表数据结构,单向链表的特性就是增删快。
get()
先检查索引(下标)是否存在,不存在则抛出索引(下标)越界异常,
下标存在则调用node(inde)
方法,这个方法会先判断下标是在前半还在是后半,在前半就从前往后遍历,在后半就从后往前遍历,这说明如果要找的索引在两头时,效率是很高的。
set()
set()
方法很简单,先根据索引遍历列表,获得Node
后替换成新值。
add()
add()
方法被重载过,当只有一个参数时,直调用linkLast()
将元素链接到链表最后面;
两个参数时,先判断是不是尾节点,是位节点直接调用linkLast()
方法;不是尾节点则调用linkBefore()
方法;
查找到索引值处的节点(node),判断是不是头结点,最后就是链表的插入操作。
LinkedList
线程不安全,查询慢。由于定义了表头与表尾元素,所以也可以当做堆栈、队列和双向队列使用。
ArrayList
ArrayList
继承自AbstractList
并实现了List
等接口。通过ArrayList
提供的构造方法可已看出来,ArrayList
是通过数组实现的;
get()
get()
方法直接是通过数组索引(下标)取值
set()
set()
方法也是通过通过数组索引(下标)直接赋值
add()
两个add()
方法在插入元素前都会对数组容量进行检查,容量不够时进行扩容;扩容完成后对数组进行复制;
扩容后的长度是之前长度的1.5倍+1。
ArrayList
数据查询快,增删慢,线程不安全。
Vector
Vector
继承自AbstractList
并实现了List
等接口;Vector
与ArrayList
基本一致,也是使用数组实现的;但是Vector
的构造方法可以指定扩容参数
在扩容方法grow()
中,默认扩展一倍大小,也可增加指定大小
Vector
与ArrayList
最大的区别是,Vector
的public
方法都使用了synchronized
来实现线程安全,这就降低了Vector
的速度。
Set
Set
接口也继承自Collection
接口,Set的实现类是不包含重复元素的集合,最多只能包含一个null元素;
TreeSet
TreeSet
继承自AbstractSet
实现了Set
的子孙接口(NavigableSet
),通过TreeSet
的构造方法,可以发现,TreeSet
内部是用TreeMap
实现的,那么底层也是使用了二叉树做数据结构;自动内排序;
add()
add()
方法其实调用的是Map
的put()
方法,map
的key是需要存放的元素,而value是一个Object对象。这也是为什么TreeSet
不能存放重复值得原因。
TreeSet
中的其他方法内也是对Map
方法的调用。TreeMap的key不可以为空,value可以为空;
HashSet
HashSet
继承自AbstractSet
并实现了Set
等接口;通过构造方法,可以看出来,HashSet
内部是使用HashMap
实现的,底层也是使用了Hash表作为数据结构;
add()
HashSet
的add()
方法使用的是HashMap
的put()
方法;
HashSet
的其他方法也使用的是HashMap
的响应方法进行封装。
小结
HashSet
不保证存取顺序一致,允许一个null,值唯一。
LinkedHashSet
LinkedHashSet
继承自HashSet
,并实现了Set
等接口;其构造方法直接调用了父类的3个参数的构造方法,在父类的构造方法中创建LinkedHashMap
对象;可以看出来LinkedHashSet
底层使用了链表加Hash表作为数据结构。既保证数据的存取顺序,也保证了值得唯一性;