Java HashMap整理

来源:互联网 发布:幼儿园美工课图片 编辑:程序博客网 时间:2024/05/14 21:44

HashMap

数组和链表的接合体,先根据key找到数组下标,若该key有了value,则把要插入的value放在链表头
这里写图片描述

Map map = new HashMap();map.put("Rajib Sarma","100");map.put("Rajib Sarma","200");//The value "100" is replaced by "200".map.put("Sazid Ahmed","200");Iterator iter = map.entrySet().iterator();while (iter.hasNext()) {    Map.Entry entry = (Map.Entry) iter.next();    Object key = entry.getKey();    Object val = entry.getValue();}

HashTable和HashMap区别

  • 继承不同
public class Hashtable extends Dictionary implements Mappublic class HashMap  extends AbstractMap implements Map
  • 同步
    HashTable是同步的,在多线程并发状态下可以使用
    HashMap不同步

  • null值
    HashMap允许key、value出现null,HashTable不允许
    HashMap中key为null的值只能有一个,value为null的可以有很多。
    get方法返回null有两种情况:
    1、不存在该建对应的值
    2、该键对应的值为null
    所以需要使用containsKey判断是否存在某个键,不应用get

  • 遍历实现不同
    都使用了iterator,历史原因HashTable还使用了enumeration,而且用与代替求模

  • 哈希值的使用不同
    HashTable使用对象的HashCode,hashMap重新计算对象的hash值

  • 内部实现数组大小和扩容方式
    HashMap:默认16,以2的指数扩容
    HashTable:默认11,以old*2+1扩容

ConcurrentHashMap

实现细节

  • ConcurrentHashMap不是用synchronized关键字实现的

ConcurrentHashMap使用segment来分段和管理锁,segment继承自ReentrantLock,因此ConcurrentHashMap使用ReentrantLock来保证线程安全。

HashMap实现原理

这里写图片描述
HashMap实现原理分析
HashMap工作原理

HashMap是不是所有Object都能作为key?

不是。
1、什么是可变对象

可变对象是指创建后自身状态能改变的对象。换句话说,可变对象是该对象在创建后它的哈希值可能被改变。

在下面的代码中,对象MutableKey的键在创建时变量 i=10 j=20,哈希值是1291。

然后我们改变实例的变量值,该对象的键 i 和 j 从10和20分别改变成30和40。现在Key的哈希值已经变成1931。

显然,这个对象的键在创建后发生了改变。所以类MutableKey是可变的。

public class MutableKey {    private int i;    private int j;    public MutableKey(int i, int j) {        this.i = i;        this.j = j;    }    public final int getI() {        return i;    }    public final void setI(int i) {        this.i = i;    }    public final int getJ() {        return j;    }    public final void setJ(int j) {        this.j = j;    }    @Override    public int hashCode() {        final int prime = 31;        int result = 1;        result = prime * result + i;        result = prime * result + j;        return result;    }    @Override    public boolean equals(Object obj) {        if (this == obj) {            return true;        }        if (obj == null) {            return false;        }        if (!(obj instanceof MutableKey)) {            return false;        }        MutableKey other = (MutableKey) obj;        if (i != other.i) {            return false;        }        if (j != other.j) {            return false;        }        return true;    }}public class MutableDemo {    public static void main(String[] args) {        // Object created        MutableKey key = new MutableKey(10, 20);        System.out.println("Hash code: " + key.hashCode());        // Object State is changed after object creation.        key.setI(30);        key.setJ(40);        System.out.println("Hash code: " + key.hashCode());    }}

2、HashMap如何存储键值对
HashMap用Key的哈希值来存储和查找键值对。

当插入一个Entry时,HashMap会计算Entry Key的哈希值。Map会根据这个哈希值把Entry插入到相应的位置。

查找时,HashMap通过计算Key的哈希值到特定位置查找这个Entry。

所以,可变对象的hashCode会变化,变化之后可能在HashMap中找不到了
自定义类是不能当作Key的。

HashMap中的负载因子

在HashMap的构造函数有以下三种:

  • HashMap():构建一个初始容量为 16,负载因子默认为 0.75 的 HashMap。
  • HashMap(int initialCapacity):构建一个初始容量为 initialCapacity,负载因子为 0.75 的HashMap。
  • HashMap(int initialCapacity, float loadFactor):以指定初始容量、指定的负载因子创建一个HashMap。

而其中的负载因子loadFactor的理解为:HashMap中的数据量/HashMap的总容量(initialCapacity),当loadFactor达到指定值或者0.75时候,HashMap的总容量自动扩展一倍(调用resize),以此类推。

负载因子是为了让查询效率更高,因为在数目过多了时候有可能冲突概率太大,这样很多时候是要遍历链表的。

HashMap中的resize

  • Java 8 中实现
    首先,size超过阈值会调resize函数,resize函数中新建一个散列表数组,容量为旧表的2倍,接着需要把旧表的键值对迁移到新表,这里分三种情况:
  • 表项只有一个键值对时,针对新表计算新的索引位置并插入键值对
  • 表项节点是红黑树节点时(说明这个bin元素较多已经转成红黑树了),split这个bin。
  • 表项节点包含多个键值对组成的链表时(拉链法)。jdk6和7的迁移实现(transfer函数)都是直接算新的索引位置然后头插法往拉链里填坑。

第3种情形的策略:
把链表上的键值对按hash值分成lo和hi两串,lo串的新索引位置与原先相同[原先位置j],hi串的新索引位置为[原先位置j+oldCap];
链表的键值对加入lo还是hi串取决于 判断条件if ((e.hash & oldCap) == 0),因为capacity是2的幂,所以oldCap为10…0的二进制形式,若判断条件为真,意味着oldCap为1的那位对应的hash位为0,对新索引的计算没有影响(新索引=hash&(newCap-1),newCap=oldCap<<2);若判断条件为假,则 oldCap为1的那位对应的hash位为1,
即新索引=hash&( newCap-1 )= hash&( (oldCap<<2) - 1),相当于多了10…0,即oldCap。

else { // preserve order    Node<K,V> loHead = null, loTail = null;    Node<K,V> hiHead = null, hiTail = null;    Node<K,V> next;    do {        next = e.next;        //对于16的大小数组,110000类是影响索引的,100000类不影响索引,索引的计算是对16取余数得来的,所以我们需要关注对32、16取余数不同的hashcode,也就是万位为1的        if ((e.hash & oldCap) == 0) {        //不影响原有索引,00000,还在原有table[index]中            if (loTail == null)                loHead = e;            else                loTail.next = e;            loTail = e;        }        else {        //影响原有索引,10000,应该在table[index+oldCap]中            if (hiTail == null)                hiHead = e;            else                hiTail.next = e;            hiTail = e;        }    } while ((e = next) != null);    if (loTail != null) {        loTail.next = null;        newTab[j] = loHead;    }    if (hiTail != null) {        hiTail.next = null;        //改变索引对应的数组下标        newTab[j + oldCap] = hiHead;    }}

LinkedHashMap

为了保存HashMap中插入的顺序,将HashMap中的Entry作为双向链表串联起来,引入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);    }}

这里写图片描述
加入表头和表尾:

transient LinkedHashMap.Entry<K,V> head;/** * The tail (youngest) of the doubly linked list. */transient LinkedHashMap.Entry<K,V> tail;/** * The iteration ordering method for this linked hash map: <tt>true</tt> * for access-order, <tt>false</tt> for insertion-order. * * @serial */final boolean accessOrder;

accessOrder是标志是否将查询出来的元素放在最后一个,如果需要,应该这样创建:

LinkedHashMap<String, String> map = new LinkedHashMap<String, String>(16, 0.75f, true);

从迭代器可以看出before,after的作用:

  • HashMap(查找当前table[index]的next,如果next==null,则继续查找table[index+1].head):
final Node<K,V> nextNode() {    Node<K,V>[] t;    Node<K,V> e = next;    if (modCount != expectedModCount)        throw new ConcurrentModificationException();    if (e == null)        throw new NoSuchElementException();    if ((next = (current = e).next) == null && (t = table) != null) {        do {} while (index < t.length && (next = t[index++]) == null);    }    return e;}
  • LinkedHashMap(迭代的next一直是用after实现的):
final LinkedHashMap.Entry<K,V> nextNode() {   LinkedHashMap.Entry<K,V> e = next;   if (modCount != expectedModCount)       throw new ConcurrentModificationException();   if (e == null)       throw new NoSuchElementException();   current = e;   next = e.after;   return e;}
1 0
原创粉丝点击