Map浅析

java.Util.Map接口是jdk的常用接口,Map是一种数据字典,以key,value形式出现,key、value作为数据域出现在内部接口Entry<K,V>中。提供根据key查value,以及keys和values集合并遍历的方法。没什么特殊的地方,只是对于几种Map,如果能知道其数据结构和相对优劣,对于编写性能较好的程序是需要掌握这些的。

 

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<K> {
  public Iterator<K>iterator() {
    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<K> newKeyIterator()   {
  retur nnew KeyIterator();
}
Step3
private final class KeyIterator extends HashIterator<K> {
  public K next() {
    return nextEntry().getKey();
  }
}
Step4
final Entry<K,V> nextEntry() {
  if (modCount != expectedModCount)
    throw new ConcurrentModificationException();

  Entry<K,V> 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 Entry<K,V>header;//数组
 
private static class Entry<K,V> extends HashMap.Entry<K,V>{
Entry<K,V> 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<K,V> 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<K,V> e) {
  lock();
  try {
    return e.value;
  } finally {
    unlock();
  }
}

 

6.TreeMap

TreeMap数据结构是红黑树。实现了SortMap接口,能够把它保存的记录根据键排序,默认是按键值升序排序,也可以指定排序的比较器。(数据结构及分析待补充)

 

猜你喜欢

转载自qify.iteye.com/blog/2310859
Map