这篇笔记对几个常用的集合实现,从效率,线程安全和应用场景进行综合比较。
1.ArrayList、LinkedList与Vector的对比
(1)相同和不同
都实现了List接口,使用类似。
Vector和ArrayList的底层实现都是数组,这一点与LinkedList的双向链表不同。
Vector和ArrayList在更多元素添加进来时会请求更大的空间。Vector每次请求其大小的双倍空间,而ArrayList每次对size增长50%。
(2)线程安全
ArrayList、LinkedList都没有进行同步,可以使用 Collections的同步方法进行包装。
(3)性能比较
在ArrayList和Vector中,从一个指定的位置(通过索引)查找数据或是在集合的末尾增加、移除一个元素所花费的时间是一样的,这个时间我们用O(1)表示。但是,如果在集合的其他位置增加或移除元素那么花费的时间会呈线形增长:O(n-i),其中n代表集合中元素的个数,i代表元素增加或移除元素的索引位置,即时间复杂度O(N)。为什么会这样呢?因为数组在内存上是连续的,进行上述操作的时候集合中第i和第i个元素之后的所有元素都要执行位移的操作。
如果你只是查找特定位置的元素或只在集合的末端增加、移除元素,那么使用Vector或ArrayList都可以。如果是其他操作,你最好选择其他的集合操作类。比如,LinkList集合类在增加或移除集合中任何位置的元素所花费的时间都是一样的O(1),但它在索引一个元素的是有比较慢,O(i),其中i是索引的位置。
2.HashMap与HashTable比较
(1)是否允许空值
HashMap允许 null 值(key和value都可以),Hashtable不允许 null 值(key 和 value 都不可以),
这一点在之前学习源码时也注意到了,看一下HashTable的源码:
1
2
3
4
5
6
7
|
public synchronized V put(K key, V value) { // Make sure the value is not null
if (value == null) {
throw new NullPointerException();
}
......
}
|
(2)哈希值的使用不同
Hashtable直接使用对象的hashCode,代码是这样的:
1
2
|
int hash = key.hashCode(); int index = (hash & 0x7FFFFFFF) % tab.length; |
而HashMap重新计算hash值,这一点在之前的分析中也看到了:
1
2
|
int hash = hash(k); int i = indexFor(hash, table.length); |
另外还有一些如内部链表数组使用方式不同等差异。
(3)线程安全
HashMap和HashTable在线程安全性上就如同ArrayList和Vector类似,
HashTable的操作都使用synchronized做了同步。需要注意这是一种性能较低的线程安全。
>>HashSet和HashMap的区别
HashSet 实现了 Set 接口,它不允许集合中有重复的值,在将对象存储在 HashSet 之前,要先确保对象重写 equals()和 hashCode()方法,这样才能比较对象的值是否相等,以确保set中没有储存相等的对象。
HashMap 实现了 Map 接口,Map 接口对键值对进行映射。Map 中不允许重复的键。Map 接口有两个基本的实现,HashMap 和 TreeMap。TreeMap 保存了对象的排列次序,而 HashMap 则不能。HashMap 允许键和值为 null。
(1)不同之处
HashMap实现了Map接口 HashSet实现了Set接口
HashMap储存键值对 HashSet仅仅存储对象
使用put()方法将元素放入map中 使用add()方法将元素放入set中
HashMap中使用键对象来计算hashcode值 HashSet使用成员对象来计算hashcode值,对于两个对象来说hashcode可能相同,所以equals()方法用来判断对象的相等性,如果两个对象不同的话,那么返回false
HashMap比较快,因为是使用唯一的键来获取对象 HashSet较HashMap来说比较慢
(2)线程安全
HashMap 是非 synchronized 的。