java之Map的使用
Map的实现类有很多,其中较为常见的有HashMap,HashTable,LinkedHashMap,TreeMap,下面分别对这几个类进行简单的分析:
1、HashMap
HashMap的结构数组+链表的组合。
public HashMap(int initialCapacity, float loadFactor) { if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); this.loadFactor = loadFactor; this.threshold = tableSizeFor(initialCapacity); }
在创建HashMap时,可以选择设置容量initialCapacity和加载因子loadFactor。
1)
initialCapacity默认值是16。我们可以根据需求自己设置,不过它会把我们设置的值进行处理,看看源码中是怎么处理的:
static final int tableSizeFor(int cap) { int n = cap - 1; n |= n >>> 1; n |= n >>> 2; n |= n >>> 4; n |= n >>> 8; n |= n >>> 16; return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1; }
在二进制中,除了符号位,最高有效位必然是1,经过一连串位移和或预算,n的值将变成11……11的形式,再加1变成100……00的形式,即最后获取的值将是不小于cap的2的最低次幂。
2)
loadFactor的默认值为0.75,当前最大容量为数组长度*加载因子。所以如果我们在创建HashMap对象时不做任何设置,HashMap对象的最大容量为16*0.75=12个,超过则扩容。
扩容是一个非常耗费资源的过程,最好我们最好根据具体需求设置合适的initialCapacity和loadFactor。
在添加键值对时,HashMap会对Key的hash码做均匀化处理:
static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }
再来看看添加时的操作:
final V putVal(int hash, K key, V value, boolean onlyIfAbsent, boolean evict) { Node<K,V>[] tab; Node<K,V> p; int n, i; if ((tab = table) == null || (n = tab.length) == 0) n = (tab = resize()).length; if ((p = tab[i = (n - 1) & hash]) == null) tab[i] = newNode(hash, key, value, null); else { Node<K,V> e; K k; if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k)))) e = p; else if (p instanceof TreeNode) e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value); else { for (int binCount = 0; ; ++binCount) { if ((e = p.next) == null) { p.next = newNode(hash, key, value, null); if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st treeifyBin(tab, hash); break; } if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) break; p = e; } } if (e != null) { // existing mapping for key V oldValue = e.value; if (!onlyIfAbsent || oldValue == null) e.value = value; afterNodeAccess(e); return oldValue; } } ++modCount; if (++size > threshold) resize(); afterNodeInsertion(evict); return null; }
将key的hash码处理后的值和数组长度做与运算,确定下标,若数组的这个位置为null,则将键值对生成的新对象放在这个位置,否则,添加到此链表的尾部。
到这里就能看出,HashMap将数组长度设为2的次幂,将key的hash码均匀化处理的原因了,因为这样使整个散列均匀化分布,提高插入和查询的效率。
2、HashTable
hashTable也是数组+链表的结构,它也实现了Map接口,但它继承了陈旧的Dictionary类,Dictionary是java早期设计的类,相比HashMap有很多不足之处。
在初始化的时候,默认的数组长度为11,也可以在创建HashTable对象时自己设定,与HashMap不同的是,HashTable数组长度直接就是我们设置的值,不做其它处理。
下面是HashTable添加新元素时的操作:
private void addEntry(int hash, K key, V value, int index) { modCount++; Entry<?,?> tab[] = table; if (count >= threshold) { // Rehash the table if the threshold is exceeded rehash(); tab = table; hash = key.hashCode(); index = (hash & 0x7FFFFFFF) % tab.length; } // Creates the new entry. @SuppressWarnings("unchecked") Entry<K,V> e = (Entry<K,V>) tab[index]; tab[index] = new Entry<>(hash, key, value, e); count++; }
对于索引值它也只是对key的hash码做简单的绝对值取余处理,而且在添加元素时,并不是如同HashMap一样加在链表的末端,而是放在表头。
除此之外,HashTable的key和value都不能是null,HashMap可以。
HashTable是线性安全的,而HashMap非线性安全。
3、LinkedHashMap
LinkedHashMap是HashMap的子类,相比于HashMap它最大的特点是它是有序的。它的顺序可分为两种:插入顺序和访问顺序,因此它常用于Lru算法中。
public LinkedHashMap(int initialCapacity, float loadFactor, boolean accessOrder) { super(initialCapacity, loadFactor); this.accessOrder = accessOrder; }
accessOrder为true则为访问顺序;false则为插入顺序。
相比于HashMap,它的Entry增加了前一个Entry对象before和后一个Entry对象after:
static class Entry<K,V> extends HashMap.Node<K,V> { Entry<K,V> before, after; Entry(int hash, K key, V value, Node<K,V> next) { super(hash, key, value, next); } }
新插入的对象放在末尾:
private void linkNodeLast(LinkedHashMap.Entry<K,V> p) { LinkedHashMap.Entry<K,V> last = tail; tail = p; if (last == null) head = p; else { p.before = last; last.after = p; } }
访问过的对象放在末尾,前后对象互为前后:
public V get(Object key) { Node<K,V> e; if ((e = getNode(hash(key), key)) == null) return null; if (accessOrder) afterNodeAccess(e); return e.value; } void afterNodeAccess(Node<K,V> e) { // move node to last LinkedHashMap.Entry<K,V> last; if (accessOrder && (last = tail) != e) { LinkedHashMap.Entry<K,V> p = (LinkedHashMap.Entry<K,V>)e, b = p.before, a = p.after; p.after = null; if (b == null) head = a; else b.after = a; if (a != null) a.before = b; else last = b; if (last == null) head = p; else { p.before = last; last.after = p; } tail = p; ++modCount; } }
4、TreeMap
TreeMap的结构是红黑树,相比于前面几种Map,要复杂许多。
TreeMap的一大功能就是可以根据设定规则进行排序,下面为一个简单示例:
public class Test { public static void main(String[] args) { Comparator<Person> comparator = new Comparator<Person>() { @Override public int compare(Person o1, Person o2) { return o1.compareTo(o2); } }; Map<Person, String> map = new TreeMap<>(comparator); map.put(new Person(1), "first"); map.put(new Person(2), "second"); map.put(new Person(4), "third"); map.put(new Person(3), "fourth"); iterate(map); /* output: key:1;value:first key:2;value:second key:3;value:fourth key:4;value:third */ } public static <K,V> void iterate(Map<K,V> map){ for(Entry<K, V> entry:map.entrySet()){ System.out.println("key:"+entry.getKey()+";value:"+entry.getValue()); } } } class Person implements Comparable<Person> { private int id; public Person(int id) { this.id = id; } public void setId(int id) { this.id = id; } public int getId() { return id; } @Override public int compareTo(Person person) { if (this.id < person.getId()) { return -1; } else if (this.id == person.id) { return 0; } else { return 1; } } @Override public String toString() { return id + ""; } }