Java集合5——Map的实现类HashMap

以下源码基于jdk1.7.8.0

在学HashMap之前我们应该先了解一种数据结构——哈希表

数据结构->哈希表:哈希表是一种根据关键码去寻找值的数据映射结构,
                 该结构通过把关键码映射的位置去寻找存放值的地方.

一般的线性表中,树中,记录在结构中的相对位置是随机的,即和记录的关键字之间不存在确定的关系,因此,在结构中查找记录时需进行一系列和关键字的比较。这一类查找方法建立在“比较“的基础上,查找的效率依赖于查找过程中所进行的比较次数。理想的情况是能直接找到需要的记录,因此必须在记录的存储位置和它的关键字之间建立一个确定的对应关系f,使每个关键字和结构中一个唯一的存储位置相对应。

当我们对某个元素进行哈希运算,得到一个存储地址,然后要进行插入的时候,发现已经被其他元素占用了,其实这就是所谓的哈希冲突,也叫哈希碰撞。数组是一块连续的固定长度的内存空间,再好的哈希函数也不能保证得到的存储地址绝对不发生冲突。那么哈希冲突如何解决呢?哈希冲突的解决方案有多种:开放定址法(发生冲突,继续寻找下一块未被占用的存储地址),再散列函数法,链地址法,而HashMap即是采用了链地址法,也就是数组+链表的方式,

哈希表的构造方法
           直接寻址法:f(key) = key


           除留余数法:f(p) = p%n

我们常用的解决哈希冲突的方法
         1,链地址法(数组+单项链表)


         2,探测法  pos(n) = f(n)+p(n)
             a,线性探测:p(n) = 1-->p(n) = 2-->p(n) = 3直到下一个卡槽有位置时放下 
             b,随机探测:p(n) = random();随机找一个卡槽看是否有位置

HashMap简介

  • HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。
  • HashMap 继承于AbstractMap,实现了Map、Cloneable、java.io.Serializable接口。

1,成员变量

 
     // 默认的初始容量是16,必须是2的幂。
    static final int DEFAULT_INITIAL_CAPACITY = 16;

     // 最大容量(必须是2的幂且小于2的30次方,传入容量过大将被这个值替换)
	
    static final int MAXIMUM_CAPACITY = 1 << 30;

      // 默认加载因子
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

     // 存储数据的Entry数组,长度是2的幂。
     // HashMap是采用拉链法实现的,每一个Entry本质上是一个单向链表
    transient Entry[] table;

      // HashMap的大小,它是HashMap保存的键值对的数量
    transient int size;

     // HashMap的阈值,用于判断是否需要调整HashMap的容量(threshold = 容量*加载因子)
    int threshold;

  
     // 加载因子实际大小
    final float loadFactor;

      // HashMap被改变的次数,由于HashMap非线程安全,在对HashMap进行迭代时,如果期间其他线程参与导致HashMap的结构发生变化了(比如put,remove等操作),需要抛出异常
    transient volatile int modCount;

HashMap的主干是一个Entry数组。Entry是HashMap的基本组成单元,每一个Entry包含一个key-value键值对。

2,构造方法

//指定“容量大小”和“加载因子”的构造函数
    public HashMap(int initialCapacity, float loadFactor) {
       //容量小于0则抛异常
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity: " +
                                               initialCapacity);
		//指定最大容量只能是MAXIMUM_CAPACITY
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new IllegalArgumentException("Illegal load factor: " +
                                               loadFactor);

        // Find a power of 2 >= initialCapacity
        //找出“大于”initialCapacity的最小的2的幂
        int capacity = 1;
        while (capacity < initialCapacity)
            capacity <<= 1;
		//设置加载因子
        this.loadFactor = loadFactor;
		//设置阈值,阈值=容量乘以加载因子,当HashMap中存储数据的数量达到threshold时,
		  //就需要将HashMap的容量增加
        threshold = (int)(capacity * loadFactor);
		//创建Entry数组,用来保存数据
        table = new Entry[capacity];
        init();
    }

  
     //指定容量大小的构造函数
    public HashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

  
     //默认构造函数
    public HashMap() {
        //默认加载因子
        this.loadFactor = DEFAULT_LOAD_FACTOR;
		//默认阈值
        threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
		//默认Entry数组的大小
        table = new Entry[DEFAULT_INITIAL_CAPACITY];
        init();
    }

   
     //包含子Map的构造函数
    public HashMap(Map<? extends K, ? extends V> m) {
        this(Math.max((int) (m.size() / DEFAULT_LOAD_FACTOR) + 1,
                      DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR);
	    //将m中的全部元素逐个添加到HashMap中
        putAllForCreate(m);
    }
  • HashMap 的实例有两个参数影响其性能:“初始容量” 和 “加载因子” 。初始容量 只是哈希表在创建时的容量。加载因子 是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积(也就是阈值)时,则要对该哈希表进行 rehash 操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。
  • 通常,默认加载因子是 0.75, 这是在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查询成本(在大多数 HashMap 类的操作中,包括 get 和 put 操作,都反映了这一点)。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少 rehash 操作次数。如果初始容量大于最大条目数除以加载因子,则不会发生 rehash 操作。

3,常用方法

1,put():向集合中添加键值对

 //将key-value添加到HashMap中
    public V put(K key, V value) {
        //若key为null,则将该键值对添加到table[0]中
        if (key == null)
            return putForNullKey(value);
		//不为null,则计算该key值得哈希值,然后将其添加到该哈希值对应的链表中
        int hash = hash(key.hashCode());//对key的hashcode进一步计算,确保散列均匀
        int i = indexFor(hash, table.length);//获取在table中的实际位置
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
			//如果key值存在,则用新的value替代旧的value
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
	//该key对应的键值对不存在,将“key-value”添加到table中能够
        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

h&(length-1)保证获取的index一定在数组范围内

HashMap 的实现不是同步的,这意味着它不是线程安全的。它的key、value都可以为null。此外,HashMap中的映射不是有序的。

扩容方式为2倍扩容

扩容之后调用transfer进行重新hash

  2,get():通过key值获取value的值

3,boolean containsKey(Object key):如果集合中已经有这个键,则返回true

4,boolean containsValue(Object value):如果集合中已经有这个值,则返回true

5.remove(key):删除键为key的元素

猜你喜欢

转载自blog.csdn.net/qq_37937537/article/details/81141498