原题贴出自:2015腾讯校园招聘技术类研发笔试题(西安、成都、武汉站)
喝水不忘挖井人,在次感谢IT面论坛的辛勤劳作!
原题描述:
在java中,哪些数据结构可以常量的时间复杂度O(1)添加元素?
HashMap、ArrayList、TreeMap和LinkedList
我给的选择是:HashMap和Linkedlist。
不一定正确,欢迎批评指正!!!共同学习,共同进步……
—————————————————————————分割线————————————————————
首当其冲,讲解HashMap
HashMap原理简介:
HashMap实现的是java.util.Map接口,提供了Map所有可用的方法实现,允许key或者value的值为null,不同于HashTable,HashMap是非线程安全实现,且实现是不保证元素有序排列,也不保证原有元素的顺序随着时间推移保持不变。
这种实现提供了基本操作(get和put),常量时间操作的性能,假定哈希函数将正确之间的桶中的元素。遍历集合的视图,需要的时间与HashMap实例的“容量”(桶的数量),加上它的大小(键 - 值映射关系数)。因此,这是非常重要的是不要设定初始容量太高(或将加载因子设置得太低),如果迭代性能很重要。
这个问题实现是牵扯到的是HashMap的put方法,下面介绍下put方法的实现,先来段源码:
transient Entry[] table = (Entry[]) EMPTY_TABLE;
内部容器实现是基于Entry(俗称的桶)
static class Entry implements Map.Entry {
final K key;
V value;
Entry next;
int hash;
********省略实现代码***********
}
Entry内部属性有next,可以看出桶的实现又是采用的链表形式.
public V put(K key, V value) {
if (table == EMPTY_TABLE) {
inflateTable(threshold);
}
if (key == null)
return putForNullKey(value);
int hash = hash(key);
int i = indexFor(hash, table.length);
for (Entry e = table[i]; e != null; e = e.next) {
Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
V oldValue = e.value;
e.value = value;
e.recordAccess(this);
return oldValue;
}
}
modCount++;
addEntry(hash, key, value, i);
return null;
}
for循环那块代码实现的就是:根据key值计算出hash后,看看是否存在于HashMap的table(元素真实的容器)。由于Hash算法实现是采用的链地址法(看如下分析就知道为什么的),所以得循环遍历桶元素的链表。这个部分不是关心的重点,重点是下面的
addEntry(hash, key, value, i)方法。
void addEntry(int hash, K key, V value, int bucketIndex) {
if ((size >= threshold) && (null != table[bucketIndex])) {
resize(2 * table.length);
hash = (null != key) ? hash(key) : 0;
bucketIndex = indexFor(hash, table.length);
}
createEntry(hash, key, value, bucketIndex);
}
第一个if语句是用来判断是否需要扩容,问题关注的是下面的createEntry(hash, key, value, bucketIndex)方法,继续接着分析。
void createEntry(int hash, K key, V value, int bucketIndex) {
Entry e = table[bucketIndex];
table[bucketIndex] = new Entry<>(hash, key, value, e);
size++;
}
注意啦,问题的核心在这里。为什么之前说是基于链地址法的hash算法。先定位到桶元素,然后更具要插入元素的hash,key和value来新建Entry元素,直接采用的是头插法,把元素添加到桶里。既然是头插入方式,那么可以推测出来插入一个元素的时间复杂度为O(1),也就能够保证常量时间添加,故HashMap也是O(1)时间完成元素添加的。
LinkedList见名知意,实现是基于链表的,因为链表添加动作时间复杂度是O(1),故LinkedList也是正确选项。
ArrayList:内部实现是基于数组,数组元素不能保证O(1)时间内任意添加一个元素。且当ArrayList需要扩容的时候,性能消耗是更加大的,先得重新开辟空间,然后再进行元素的添加。
TreeMap:内部实现是基于红黑树,呵呵~这个也就不是我们想要的答案咯。至于红黑树是什么东西,google下就差不多了,这里不深究。插入和删除操作都设计到树结构的调整。
共同学习,共同进步!