java.Util.Map接口是jdk的常用接口,Map是一种数据字典,以key,value形式出现,key、value作为数据域出现在内部接口Entry
1.Hashtable
jdk的遗留类。提供了线程安全的添加、删除等接口,通过内部互斥锁Synchronized实现。
public synchronized V put(K key, V value)
public synchronized V get(Object key)
2.HashMap
数据结构是数组,数组元素是单向链表。数据结构如下图:
常用方法:
put(key,value)
1)如果key为null
如果key==null,则执行putForNullKey()。HashMap只允许存在一个key为null的元素,value后值覆盖前值。
2)计算key的hash值
根据jdk的hash算法,计算key的hash值。
3)定位元素在数组中的位置
执行indexFor()方法,得到元素在数组中的index。
4)数组中已存在相同的key
遍历数组,判断数组中是否已经有相同的key,已经存在,则value后值覆盖前值,返回oldValue。
判断条件是两个key的hash值相同,并且两者的”==”或者“equals”比较返回true。如果key是String对象,则内容相同或者是同一个对象都判定为key相同;如果是自定义对象,则需要重写equals方法来规定判断标准。如果以自定义的对象作为key,则需要重写equals()和hashcode()。
5)数组中不存在该key
将元素包装为Entry类型,添加元素到数组,并将新Entry对象添加到链表头。
特点:
1)可以存储null键和null值,Hashtable不允许;
两个关键参数:initial capacity和load factor。Load factor默认是0.75,是offers a good tradeoff between time and space costs。当map元素数量大于集合容量乘以加载因子时,集合容量扩大到当前容量的两倍,并且对元素进行rehash。尽量避免rehash,如果初始容量大于最大元素数除以加载因子时,则不会发生rehash。
2)非线程安全。
典型的问题是多线程并发访问、rehash时,会产生死循环问题。可使用Collections.synchronizedMap 方法来“包装”该映射,实现并发的安全处理。如果Collections.synchronizedMap不能满足性能要求,可试试选择ConcurrentHashMap。
3)迭代的快速失败。
集合的迭代器创建之后,如果集合结构有修改,而且非调用Iterator.remove(),迭代器会抛出 ConcurrentModificationException,但是该fail-fast得不到保证的。参考iterator方法,可知在遍历Iterator期间,元素个数有变化,则出现并发问题,抛出异常。
Step1
private final class KeySet extends AbstractSet {
public Iteratoriterator() {
return new KeyIterator();
}
publicint size() {
returnsize;
}
public boolean contains(Object o) {
return containsKey(o);
}
public boolean remove(Object o) {
return HashMap.this.removeEntryForKey(o) != null;
}
public void clear() {
HashMap.this.clear();
}
}
Step2
Iterator newKeyIterator() {
retur nnew KeyIterator();
}
Step3
private final class KeyIterator extends HashIterator {
public K next() {
return nextEntry().getKey();
}
}
Step4
final Entry nextEntry() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
Entry e = next;
if (e == null)
throw new NoSuchElementException();
if ((next = e.next) == null) {
Entry[] t = table;
while (index< t.length&& (next = t[index++]) == null)
;
}
current = e;
return e;
}
4)线程安全
HashMap为什么是非线程安全的?ConcurrentHashMap为什么是线程安全的?
HashMap数据结构是Entry数组,并发操作时,数组是被竞争的共享资源,如果读时有写操作,就会出现并发问题。
HashMap提供了fast-fail机制,使用Iterator遍历时,会比较元素更新数,如果元素数有变化,则抛出ConcurrentModificationException。
ConcurrentHashMap数据结构是Segment数组,每个数组上通过加显示锁保证了线程安全。同时,由于元素分成了多个segment,提高了并发性能。实现参考ConcurrentHashMap部分。
3.LinkedHashMap
1)数据结构
数据保存在一个数组,数组元素是双向链表。
private transient Entryheader;//数组
private static class Entry extends HashMap.Entry{
Entry before, after;//双向指针
…
}
2)特点
LinkedHashMap保存了数据的插入顺序。在用Iterator遍历LinkedHashMap时,先得到的记录肯定是先插入的;HashMap返回key-value的顺序只与Key的hashCode有关。
LinkedHashMap也可以在构造时用带参数,按照应用次数排序。在遍历的时候会比HashMap慢,不过有种情况例外,当HashMap容量很大,实际数据较少时,遍历起来可能会比LinkedHashMap慢,因为LinkedHashMap的遍历速度只和容量有关,而HashMap的遍历速度和他的碰撞情况相关。
5.ConcurrentHashMap
1)数据结构
2)特点
a)并发安全
通过对segment对象加锁,实现了该segment内读写操作的互斥,保证了并发安全性。
b)并发性能好
ConcurrentHashMap数据保存在名为segments的数组中,锁是分别加在每个segement上的,与Hashtable、Collections.synchronizedMap相比,多个segment之间可以安全的并发操作,提高了并发性能。
代码实现
参考获取操作的实现代码。
Step1
public V get(Object key) {
int hash = hash(key.hashCode());
return segmentFor(hash).get(key, hash);
}
Step2
V get(Object key, int hash) {
if (count != 0) { // read-volatile
HashEntry e = getFirst(hash);
while (e != null) {
if (e.hash == hash && key.equals(e.key)) {
V v = e.value;
if (v != null)
return v;
return readValueUnderLock(e); // recheck
}
e = e.next;
}
}
return null;
}
Step3
V readValueUnderLock(HashEntry e) {
lock();
try {
return e.value;
} finally {
unlock();
}
}
6.TreeMap
TreeMap数据结构是红黑树。实现了SortMap接口,能够把它保存的记录根据键排序,默认是按键值升序排序,也可以指定排序的比较器。(数据结构及分析待补充)