Java集合框架——HashMap

Map接口下常见的实现类有:HashMap、LinkedHashMap、HashTable、TreeMap,今天我们主要看一下HashMap的内容。

一、HashMap的特点

1)数据是以键值对的形式存储

2)key不能重复,value可以重复

3)key只能有一个为null,value可以有多个为null

4)存储数据不能保证有序

5)底层是由数组和链表实现的

二、HashMap的源码解读

1)继承关系

public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable

继承于AbstractMap<K,V>类,实现了Map<K,V>, Cloneable, Serializable接口

2)基本属性

static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //默认初始容量,将1向左移动4位,表示数组默认大小为16

static final int MAXIMUM_CAPACITY = 1 << 30;//1向左移动30位,限制数组最大值

static final float DEFAULT_LOAD_FACTOR = 0.75f;//默认加载因子,加载因子是在扩容时使用,默认值为0.75f

static final Entry<?,?>[] EMPTY_TABLE = {};//默认空数组

transient Entry<K,V>[] table = (Entry<K,V>[]) EMPTY_TABLE;//数组,用来存储entry数据

transient int size;//存储数据量

int threshold;//扩容阈值,在添加元素过程put中,capacity * loadFactor(容量*加载因子)

final float loadFactor;//加载因子

transient int modCount;// 版本号

static class Entry<K,V> implements Map.Entry<K,V> {//数组中存储元素的类型

        final K key;

        V value;

        Entry<K,V> next;

        int hash;

}

3)构造函数

1、有参构造(初始容量,加载因子)

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;
        threshold = initialCapacity;
        init();
    }

2、有参构造(初始容量)

 public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

3、有参构造(通过集合构造新集合)

public HashMap(Map<? extends K, ? extends V> m) {
        this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
                      DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
        inflateTable(threshold);

        putAllForCreate(m);
    }

4、无参构造

public HashMap() {
        this(DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR);
    }

4)hash

final int hash(Object k) {
        int h = hashSeed;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }

        h ^= k.hashCode();
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

计算hash值的方法,通过key的hashCode来计算

5)indexFor

static int indexFor(int h, int length) {
        // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
        return h & (length-1);
    }

通过hash码去计算节点应该存储在数组中的索引

6)得到数组的有效个数

 public int size() {
        return size;
    }

7)判断数组是否为空

public boolean isEmpty() {
        return size == 0;
    }

8)通过key值获得value

public V get(Object key) {
        if (key == null)
            return getForNullKey();
        Entry<K,V> entry = getEntry(key);

        return null == entry ? null : entry.getValue();
    }

get中的方法:

1、getForNullKey(key为null时,调用该方法)

 private V getForNullKey() {
        if (size == 0) {//如果size=0,说明数组中没有数据,直接返回
            return null;
        }
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {

//判断数组0索引位置是否存在元素,存在返回该节点的值,不存在返回null
            if (e.key == null)
                return e.value;
        }
        return null;
    }

2、getEntry(根据key找到该节点)

final Entry<K,V> getEntry(Object key) {
        if (size == 0) {
            return null;
        }

        int hash = (key == null) ? 0 : hash(key);
        for (Entry<K,V> e = table[indexFor(hash, table.length)];
             e != null;
             e = e.next) {
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k))))
                return e;
        }
        return null;
    }

9)添加元素put

public V put(K key, V value) {
        if (table == EMPTY_TABLE) {//若数组为null时,进入
            inflateTable(threshold);   //1、对容量、扩容阈值做更改,并且创新大小为capacity的数组
        }
        if (key == null)
            return putForNullKey(value);//2、key为null时的特殊处理
        int hash = hash(key);//3、通过key进行hash
        int i = indexFor(hash, table.length);//4、hash完成后找到在数组中的合适位置
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);//5、添加节点
        return null;
    }

1、inflateTable

private void inflateTable(int toSize) {
        // Find a power of 2 >= toSize
        int capacity = roundUpToPowerOf2(toSize);

        threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
        table = new Entry[capacity];
        initHashSeedAsNeeded(capacity);
    }

2、putForNullKey

private V putForNullKey(V value) {
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
        modCount++;
        addEntry(0, null, value, 0);
        return null;
    }

key为null时特殊处理,key-value键值对存储在table位置为0的位置

若key为null数据已经存在,则对原value做覆盖处理,负责添加新entry节点

3、hash

final int hash(Object k) {
        int h = hashSeed;
        if (0 != h && k instanceof String) {
            return sun.misc.Hashing.stringHash32((String) k);
        }

        h ^= k.hashCode();

        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

4、indexFor

static int indexFor(int h, int length) {
        // assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
        return h & (length-1);
    }

5、addEntry

void addEntry(int hash, K key, V value, int bucketIndex) {

    if ((size >= threshold) && (null != table[bucketIndex])) {//若size大于阈值,进行2倍扩容

        resize(2 * table.length);

        hash = (null != key) ? hash(key) : 0;

        bucketIndex = indexFor(hash, table.length);

    }

    createEntry(hash, key, value, bucketIndex);

}

综上所述,HashMap的put过程可以进行如下的概括:

1、table数组为空时,创建数组阈值(阈值,容量进行更新,数组初始化)

2、key为null时做特殊处理,放在table索引为0的位置上,若此位置有的话,就覆盖,否则创建新节点

3、若key不为null,则进行如下操作:根据key进行hash过程

4、找到数组中对应的位置

5、对应table位置的链表进行遍历,判断该key是否存在

6、已存在用新的value值替代旧值,同时返回旧值;存在的话创建节点

扩容时机:size>阈值时,进行2倍扩容

插入节点的方式是头插

10)删除元素remove

  public V remove(Object key) {//根据key删除键值对,返回此键值对的value值
        Entry<K,V> e = removeEntryForKey(key);
        return (e == null ? null : e.value);
    }

里面调用了removeEntryForKey方法

    final Entry<K,V> removeEntryForKey(Object key) {
        if (size == 0) {
            return null;
        }
        int hash = (key == null) ? 0 : hash(key);
        int i = indexFor(hash, table.length);
        Entry<K,V> prev = table[i];
        Entry<K,V> e = prev;

        while (e != null) {
            Entry<K,V> next = e.next;
            Object k;
            if (e.hash == hash &&
                ((k = e.key) == key || (key != null && key.equals(k)))) {
                modCount++;
                size--;
                if (prev == e)
                    table[i] = next;
                else
                    prev.next = next;
                e.recordRemoval(this);
                return e;
            }
            prev = e;
            e = next;
        }

        return e;
    }

删除key-value时要注意进行链接操作。

11)扩容操作resize

void resize(int newCapacity) {
        Entry[] oldTable = table;
        int oldCapacity = oldTable.length;
        if (oldCapacity == MAXIMUM_CAPACITY) {//旧数组的容量大于最大的存储量时,对阈值进行修改
            threshold = Integer.MAX_VALUE;
            return;
        }

        Entry[] newTable = new Entry[newCapacity];
        transfer(newTable, initHashSeedAsNeeded(newCapacity));
        table = newTable;
        threshold = (int)Math.min(newCapacity * loadFactor, MAXIMUM_CAPACITY + 1);
    }

里面用到了transfer这个方法

void transfer(Entry[] newTable, boolean rehash) {
        int newCapacity = newTable.length;
        for (Entry<K,V> e : table) {//对数组中的数据进行链表遍历
            while(null != e) {
                Entry<K,V> next = e.next;
                if (rehash) {
                    e.hash = null == e.key ? 0 : hash(e.key);
                }
                int i = indexFor(e.hash, newCapacity);
                e.next = newTable[i];
                newTable[i] = e;
                e = next;
            }
        }
    }
将老旧数组中的数据逐个进行遍历,重新计算旧数组中的数据应该存放的位置后,将其放入到新扩容后的数组中;一般情况下,数组的长度都保持为2的次幂。

三、HashMap的应用场景

一般在统计大量数据中数据的重复次数时使用。

四、遍历HashMap的三种方式

HashMap<String,String> hashmap=new HashMap<String,String>();

1)以键值对的方式进行遍历

System.out.println("以键值对的形式遍历:");

Iteractor<Map.Entry<String,String>> iteractor=hashmap.entrySet().iteractor();

while(iteractor.hasNext()){

Map.Entry<String,String> entry=iteractor.next();

String key=entry.getKey();

String value=entry.getValue();

System.out.print("  " + key + "=" + value);

}

2)以键的形式遍历

System.out.println("以键的形式遍历:");

Iteractor<String> iteractor=hashmap.keySet().iteractor();

while(iteractor.hasNext()){

String key=iteractor.next();

System.out.print(key + " ");

}

3)以值的形式遍历

System.out.println("以值的形式遍历:");

Iteractor<String> iteractor=hashmap.values().iteractor();

while(iteractor.hasNext()){

String value=iteractor.next();

System.out.print(value + " ");

}

猜你喜欢

转载自blog.csdn.net/ZQ_313/article/details/84304840