HashMap实现原理(一)--基本定义和概念

1.前提

本系列基于jdk1.8主要介绍HashMap的概念以及源码分析,会对比常见集合与HashMap之间的区别,以及面试遇到的问题。

2.HashMap简介

在Java编程语言中,最基本的结构就是两种,一个是数组,另外一个是模拟指针(引用),所有的数据结构都可以用这两个基本结构来构造的,HashMap也不例外。HashMap实际上是一个“链表散列”的数据结构,即数组和链表的结合体。

  • HashMap 是一个散列表,它存储的内容是键值对(key-value)映射。
  • HashMap 继承于AbstractMap,实现了Map,Cloneable,java.io.Serializable接口。
  • HashMap的实现不是同步的,这意味着它不是线程安全的。它的key、value都可以为null。此外,HashMap中的映射不是有序的。
  • HashMap有两个参数影响其性能:初始容量和加载因子。默认初始容量是16,加载因子是0.75。容量是哈希表中桶(Entry数组)的数量,初始容量只是哈希表在创建时的容量。加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,通过调用 rehash 方法将容量翻倍。

3.Hash实现原理

在介绍hashMap源码之前先讲几个基本概念以及Object类的两个方法hashCode和equals

3.1.Java中==号与equals()方法的区别

==号和equals()方法都是比较是否相等的方法,那它们有什么区别和联系呢?
首先,==号在比较基本数据类型时比较的是值,而用==号比较两个对象时比较的是两个对象的地址值:

package hello;

/**
 * 描述:
 * 作者:袁伟倩
 * 创建日期:2018-05-10 11:47.
 */

public class Test {
    public static void main(String[] args) {
        int x = 10;
        int y = 10;
        String str1 = new String("hello");
        String str2 = new String("hello");
        System.out.println(x == y); // 输出true
        System.out.println(str1 == str2); // 输出false


    }

在比较基本类型的值的时候也会引申一个自动装箱和拆箱的问题,比如下面的例子,具体可以看我写的一篇博客

package hello;

/**
 * 描述:
 * 作者:袁伟倩
 * 创建日期:2018-05-10 11:47.
 */

public class Test {
    public static void main(String[] args) {
        int x = 10;
        int y = 10;
        String str1 = new String("hello");
        String str2 = new String("hello");
        System.out.println(x == y); // 输出true
        System.out.println(str1 == str2); // 输出false



        Integer a = 1444;
        int b = 1444;
        Integer x1 = 10;
        Integer y1 = 10;
        Integer x2 = 1444;
        Integer y2 = 1444;
        System.out.println(a == b); // 输出true
        System.out.println(x1 == y1); // 输出true
        System.out.println(x2 == y2); // 输出false


    }
}

我们可以通过查看源码知道,equals()方法存在于Object类中,因为Object类是所有类的直接或间接父类,也就是说所有的类中的equals()方法都继承自Object类,而通过源码我们发现,Object类中equals()方法底层依赖的是==号,那么,在所有没有重写equals()方法的类中,调用equals()方法其实和使用==号的效果一样,也是比较的地址值,然而,Java提供的所有类中,绝大多数类都重写了equals()方法,重写后的equals()方法一般都是比较两个对象的值:下面看一下String类中重写了equals方法源码:

    public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    // 逐个判断字符是否相等 
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

比如下面的实例:

package hello;

/**
 * 描述:
 * 作者:袁伟倩
 * 创建日期:2018-05-10 11:47.
 */

public class Test {
    public static void main(String[] args) {
        String str1 = new String("abc");
        String str2 = new String("abc");
        System.out.println(str1.equals(str2));// 输出true
        System.out.println(str1 == str2);// 输出false

    }
}

这里又引申一个问题,java 常量池问题,具体介绍详细看java的自动装箱和拆箱这篇文章

package hello;

/**
 * 描述:
 * 作者:袁伟倩
 * 创建日期:2018-05-10 11:47.
 */

public class Test {
    public static void main(String[] args) {
        String str1 ="abc";
        String str2 ="abc";
        System.out.println(str1.equals(str2));// 输出true
        System.out.println(str1 == str2);// 输出true

    }
}

综上所叙,如果想让两个对象相等,需要重写equals方法。

3.2.hashCode方法

hashCode方法,这个方法我们平时通常是用不到的,它是为哈希家族的集合类框架(HashMap、HashSet、HashTable)提供服务,hashCode 的常规协定是:
在 Java 应用程序执行期间,在同一对象上多次调用 hashCode 方法时,必须一致地返回相同的整数,前提是对象上 equals 比较中所用的信息没有被修改。从某一应用程序的一次执行到同一应用程序的另一次执行,该整数无需保持一致。
如果根据 equals(Object) 方法,两个对象是相等的,那么在两个对象中的每个对象上调用 hashCode 方法都必须生成相同的整数结果。
以下情况不 是必需的:如果根据 equals(java.lang.Object) 方法,两个对象不相等,那么在两个对象中的任一对象上调用 hashCode 方法必定会生成不同的整数结果。但是,程序员应该知道,为不相等的对象生成不同整数结果可以提高哈希表的性能。
当我们看到实现这两个方法有这么多要求时,立刻凌乱了,幸好有IDE来帮助我们,Eclipse中可以通过快捷键alt+shift+s调出快捷菜单,选择Generate hashCode() and equals(),根据业务需求,勾选需要生成的属性,确定之后,这两个方法就生成好了,我们通常需要在JavaBean对象中重写这两个方法。
实例:当我比较两个对象时,如果只是比对某几个属性,可重新生成equals和hashcode方法,比如:

package hello;

/**
 * 描述:
 * 作者:袁伟倩
 * 创建日期:2018-05-09 09:50.
 */

public class Model {
    public Integer id;
    public String name;
    public String code;

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Model model = (Model) o;

        if (!id.equals(model.id)) return false;
        return name.equals(model.name);

    }

    @Override
    public int hashCode() {
        int result = id.hashCode();
        result = 31 * result + name.hashCode();
        return result;
    }
}
   即使属性code不一样,而id和name一样,也一样认为两个对象相等。

3.2.HashMap中定义的成员变量

 /**
     * The default initial capacity - MUST be a power of two.
     * 默认初始容量为16,必须为2的幂
     */
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16

    /**
     * The maximum capacity, used if a higher value is implicitly specified
     * by either of the constructors with arguments.
     * MUST be a power of two <= 1<<30.
     * 最大容量为2的30次方
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * The load factor used when none specified in constructor.
     * 默认加载因子0.75 
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * The bin count threshold for using a tree rather than list for a
     * bin.  Bins are converted to trees when adding an element to a
     * bin with at least this many nodes. The value must be greater
     * than 2 and should be at least 8 to mesh with assumptions in
     * tree removal about conversion back to plain bins upon
     * shrinkage.
     * 由链表转换成树的阈值TREEIFY_THRESHOLD
     * 一个桶中bin(箱子)的存储方式由链表转换成树的阈值。即当桶中bin的数量超过TREEIFY_THRESHOLD时使用树来代替链表。默认值是8 
     */
    static final int TREEIFY_THRESHOLD = 8;

    /**
     * The bin count threshold for untreeifying a (split) bin during a
     * resize operation. Should be less than TREEIFY_THRESHOLD, and at
     * most 6 to mesh with shrinkage detection under removal.
     * 由树转换成链表的阈值UNTREEIFY_THRESHOLD
     * 当执行resize操作时,当桶中bin的数量少于UNTREEIFY_THRESHOLD时使用链表来代替树。默认值是6 
     */
    static final int UNTREEIFY_THRESHOLD = 6;

    /**
     * The smallest table capacity for which bins may be treeified.
     * (Otherwise the table is resized if too many nodes in a bin.)
     * Should be at least 4 * TREEIFY_THRESHOLD to avoid conflicts
     * between resizing and treeification thresholds.
     * 当桶中的bin被树化时最小的hash表容量。(如果没有达到这个阈值,即hash表容量小于MIN_TREEIFY_CAPACITY,当桶中bin的数量太多时会执行resize扩容操作)这个MIN_TREEIFY_CAPACITY的值至少是TREEIFY_THRESHOLD的4倍。
     */
    static final int MIN_TREEIFY_CAPACITY = 64;

猜你喜欢

转载自blog.csdn.net/qiyeliuli/article/details/80248778