List、Map、Set三个接口存取元素时,各有什么特点
List接口以特定索引来存取元素,可以有重复元素
Set接口不可以存放重复元素(使用equals方法区分是否重复)
Map接口保存的是键值对(key-value-pair)映射,映射关系可以是一对一或者多对一(key唯一)
Set和Map容器都有基于哈希存储和排序树的两种实现版本。基于哈希存储的版本的实现理论存取时间复杂度是O(1),而基于排序树版本的的实现在插入或者删除元素时会按照元素或者元素的key构成排序树从而达到去重和排序的效果
哈希存储的版本的实现理论存取时间复杂度是O(1)
HashMap源码:
put方法
1 public V put(K key, V value) { 2 // HashMap允许存放null键和null值。 3 // 当key为null时,调用putForNullKey方法,将value放置在数组第一个位置。 4 if (key == null) 5 return putForNullKey(value); 6 // 根据key的keyCode重新计算hash值。 7 int hash = hash(key.hashCode()); 8 // 搜索指定hash值在对应table中的索引。 9 int i = indexFor(hash, table.length); 10 // 如果 i 索引处的 Entry 不为 null,通过循环不断遍历 e 元素的下一个元素。 11 for (Entry<K,V> e = table[i]; e != null; e = e.next) { 12 Object k; 13 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { 14 V oldValue = e.value; 15 e.value = value; 16 e.recordAccess(this); 17 return oldValue; 18 } 19 } 20 // 如果i索引处的Entry为null,表明此处还没有Entry。 21 modCount++; 22 // 将key、value添加到i索引处。 23 addEntry(hash, key, value, i); 24 return null; 25 }
get方法
1 public V get(Object key) { 2 if (key == null) 3 return getForNullKey(); 4 int hash = hash(key.hashCode()); 5 for (Entry<K,V> e = table[indexFor(hash, table.length)]; 6 e != null; 7 e = e.next) { 8 Object k; 9 if (e.hash == hash && ((k = e.key) == key || key.equals(k))) 10 return e.value; 11 } 12 return null; 13 }
该过程一共分四步:
- 根据key的keyCode重新计算hash值
- 搜索指定hash值在对应table中的索引
- 遍历键值对的链表,根据key找到对应的键值对
- 从键值对中获取value值
这四步中,1/2/4每一步的时间复杂度都是O(1),但是第3步是for循环,时间复杂度是O(n),想要保证存取时间复杂度是O(1),那么只有键值对链表长度是1,也就是只有那个hash算法尽量减少冲突,才能使链表长度尽可能短,理想状态为1所以HashMap的查找时间复杂度只有在最理想的情况下才会为O(1)