首页技术文章正文

HashMap深入介绍【黑马java培训】

更新时间:2021年03月18日 08时58分23秒 来源:黑马程序员

一、HashMap的内部存储结构

Java中数据存储方式最底层的两种结构,一种是数组,另一种就是链表,数组的特点:连续空间,寻址迅速,但是在删除或者添加元素的时候需要有较大幅度的移动,所以查询速度快,增删较慢。而链表正好相反,由于空间不连续,寻址困难,增删元素只需修改指针,所以查询慢、增删快。有没有一种数据结构来综合一下数组和链表,以便发挥他们各自的优势?答案是肯定的!就是:哈希表。哈希表具有较快(常量级)的查询速度,及相对较快的增删速度,所以很适合在海量数据的环境中使用。一般实现哈希表的方法采用“拉链法”,我们可以理解为“链表的数组”,如下图:

1565576552992_HashMap.jpg

从上图中,我们可以发现哈希表是由数组+链表组成的,一个长度为16的数组中,每个元素存储的是一个链表的头结点。那么这些元素是按照什么样的规则存储到数组中呢。一般情况是通过hash(key)%len获得,也就是元素的key的哈希值对数组长度取模得到。比如上述哈希表中,12%16=12,28%16=12,108%16=12,140%16=12。所以12、28、108以及140都存储在数组下标为12的位置。它的内部其实是用一个Entity数组来实现的,属性有key、value、next。接下来我会从初始化阶段详细的讲解HashMap的内部结构。

1、初始化
首先来看三个常量:

static final int DEFAULT_INITIAL_CAPACITY = 16; 初始容量:16

static final int MAXIMUM_CAPACITY = 1

<< 30; 最大容量:2的30次方:1073741824

static final float DEFAULT_LOAD_FACTOR = 0.75f;

装载因子,后面再说它的作用

public HashMap() {
  this.loadFactor = DEFAULT_LOAD_FACTOR;
  threshold = (int)(DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR);
  table = new Entry[DEFAULT_INITIAL_CAPACITY];
  init();
}

loadFactor、threshold的值在此处没有起到作用,不过他们在后面的扩容方面会用到,此处只需理解table=new Entry[DEFAULT_INITIAL_CAPACITY].说明,默认就是开辟16个大小的空间。另外一个重要的构造方法:

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);
  
        // Find a power of 2 >= initialCapacity
        int capacity = 1;
        while (capacity < initialCapacity)
            capacity <<= 1;
  
        this.loadFactor = loadFactor;
        threshold = (int)(capacity * loadFactor);
        table = new Entry[capacity];
        init();
    }

就是说传入参数的构造方法,我们把重点放在

while (capacity < initialCapacity)
   capacity <<= 1;


上面,该代码的意思是,实际的开辟的空间要大于传入的第一个参数的值。举个例子:

new HashMap(7,0.8),loadFactor为0.8,capacity为7,通过上述代码后,capacity的值为:8.(1 << 2的结果是4,2 << 2的结果为8)。所以,最终capacity的值为8,最后通过new Entry[capacity]来创建大小为capacity的数组,所以,这种方法最红取决于capacity的大小。



2、get(Object key)操作    

   

get(Object key)操作时根据键来获取值,如果了解了put操作,get操作容易理解,先来看看源码的实现:

public V get(Object key) {
        if (key == null)
            return getForNullKey();
        int hash = hash(key.hashCode());
        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.equals(k)))//-------------------1----------------
                return e.value;
        }
        return null;
    }

意思就是:

1、当key为null时,调用getForNullKey(),源码如下:

private V getForNullKey() {
        for (Entry<K,V> e = table[0]; e != null; e = e.next) {
            if (e.key == null)
                return e.value;
        }
        return null;
    }


2、当key不为null时,先根据hash函数得到hash值,在更具indexFor()得到i的值,循环遍历链表,如果有:key值等于已存在的key值,则返回其value。如上述get()代码1处判断。
总结下HashMap新增put和获取get操作:

//存储时:
int hash = key.hashCode();
int i = hash % Entry[].length;
Entry[i] = value;
  
//取值时:
int hash = key.hashCode();
int i = hash % Entry[].length;
return Entry[i];


理解了就比较简单。

此处附一个简单的HashMap小算法应用:

<font style="color:rgb(77, 77, 77)"><font face="&quot"><font style="font-size:16px">package com.xtfggef.hashmap;
  
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
  
/**
 * 打印在数组中出现n/2以上的元素
 * 利用一个HashMap来存放数组元素及出现的次数
 * @author erqing
 *
 */
public class HashMapTest {
     
    public static void main(String[] args) {
         
        int [] a = {2,3,2,2,1,4,2,2,2,7,9,6,2,2,3,1,0};
         
        Map<Integer, Integer> map = new HashMap<Integer,Integer>();
        for(int i=0; i<a.length; i++){
            if(map.containsKey(a[i])){
                int tmp = map.get(a[i]);
                tmp+=1;
                map.put(a[i], tmp);
            }else{
                map.put(a[i], 1);
            }
        }
        Set<Integer> set = map.keySet();//------------1------------
        for (Integer s : set) {
            if(map.get(s)>=a.length/2){
                System.out.println(s);
            }
        }//--------------2---------------
    }</font></font></font>


此处注意两个地方,map.containsKey(),还有就是上述1-2处的代码。理解了HashMap的上面的操作,其它的大多数方法都很容易理解了。搞清楚它的内部存储机制,一切OK!


分享到:
在线咨询 我要报名
和我们在线交谈!