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 + "";
    }

}

 

 

 

 

posted @ 2016-03-16 22:05  maozs  阅读(353)  评论(0编辑  收藏  举报