Handwritten HashMap core source

Handwritten Java HashMap core source

The last chapter handwriting LinkedList core source code, in this chapter we handwriting Java HashMap core source code. Let's first look at the principles of the HashMap. HashMap literally hash + map, map is a mapping means, mapping means HashMap is performed by hash. do not understand? It's ok. We specifically explain the principles of the HashMap.

HashMap use analysis

//1 存
HashMap<String,String> map = new HashMap<>();
map.put("name","tom");

//2 取
System.out.println(map.get("name"));//输出 tom
复制代码

Use is that simple.

HashMap Principle Analysis

We know that, Object class has a hashCode () method returns an object hashCode value can be understood as a return memory address of the object, no matter the time being is to return the memory address or any other Ye Hao, the first matter, as hashCode () method returns the number is counted how? We also matter

First we only need to remember: This function returns a number on the line. The second internal HashMap is an array to store data

1 HashMap is how the name, tom storage? Here we use a diagram to demonstrate

As can be seen from the chart: Note: The image above the array size is 7, it is how many will do, but here we drew seven elements, we have an array of size 7 to illustrate the principles of the HashMap.

  1. 7 is the size of the array, the array index range is [0, 6]
  2. Obtain key is the "name" of the hashCode, this is a number, no matter how much this number, take the remainder of the 7, then the scope is certainly [0, 6], and the index of the array is exactly the same.
  3. Value "name" .hashCode ()% 7, if a is 2, then the value is the location "tom" should be stored is 2
  4. data [2] = "tom", stored in the array. It is not very clever.

2 The following look at how to take? Also used a map to illustrate the underlying principles, as follows

It is seen from the figure:

  1. First and obtain key is the "name" value of hashCode
  2. HashCode value performed with an array of size 7 to take the remainder, and run time deposit of the same, and certainly 2
  3. Value to a second position removed from the array, namely: String value = data [2]

Note: There are several points to note

  1. hashCode () method returns a value, called at any time, the return value of an object is the same
  2. For a number n take the remainder, the range [0, n - 1]

Note: There are several problems to be solved

  1. Save time, if a different key for an array of hashCode take the remainder are exactly the same, that is, are mapped in the same location of the array, how do? This is the hash issues such as conflict 9 % 7 == 2 , 16 % 7 == 2are equal to 2 A: The array is a data structure stored in a node, the node has a next attribute, if the hash conflict, singly linked list for storage, the time taken is the same, traverse the list

  2. If the array is already full with how to do? A: Like ArrayList and, for expansion, remapping

  3. Directly hashCode () value mapping, produce a great introduction to hash conflicts, how to do? A: The reference in HashMap JDK implementation, there is a hash () function, and then the value hashCode () are run, another map

From the result: HashMap is an array to store data, if they are above the location map already has a value, then the current stored in a linked list previously. + Arrays list structure, if the underlying structure of the HashMap our array element is stored inside QEntry, as shown below:

594516-20181128085125518-59591486.png

Handwritten HashMap core source

The principle of the above, then we use a minimum of code to prompt the principle of HashMap. We called QHashMap class, while inside the array elements need also need to define a class, we defined inside QHashMap class. Called QEntry

QEntry defined as follows:

    //底层数组中存放的元素类
   public static class QEntry<K, V> {
        K key;      //存放key
        V value;    //存放value
        int hash;   //key对应的hash值
        
        //hash冲突时,也就是映射的位置上已经有一个元素了
        //那么新加的元素作为链表头,已经存放的放在后面
        //即保存在next中,一句话:添加新元素时,添加在表头
        QEntry<K, V> next;  

        public QEntry(K key, V value, int hash, QEntry<K, V> next) {
            this.key = key;
            this.value = value;
            this.hash = hash;
            this.next = next;
        }
    }
复制代码

With the definition of QEntry class, which attributes the following look QHashMap class need? FIG QHashMap class defined as follows:

public class QHashMap<K, V> {
    //默认的数组的大小
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    //默认的扩容因子,当数据中元素的个数越多时,hash冲突也容易发生
    //所以,需要在数组还没有用完的情况下就开始扩容
    //这个 0.75 就是元素的个数达到了数组大小的75%的时候就开始扩容
    //比如数组的大小是100,当里面的元素增加到75的时候,就开始扩容
    private static final float DEFAULT_LOAD_FACTOR = 0.75f;

    //存放元素的数组
    private QEntry[] table;

    //数组中元素的个数
    private int size;
 
    ......
}    
复制代码

Only two constants and two variables enough. Here we look at the constructor QHashMap the sake of simplicity, only implement a default constructor

  public QHashMap() {
        //创建一个数组,默认大小为16
        table = new QEntry[DEFAULT_INITIAL_CAPACITY];
        
        //此时元素个数是0
        size = 0;
    }
复制代码

QHashMap we look at how the storage of data map.put("name","tom")put () function to achieve the following:

    /**
     * 1 参数key,value很容易理解
     * 2 返回V,我们知道,HashMap有一个特点,
     * 如果调用了多次 map.put("name","tom"); map.put("name","lilei");
     * 后面的值会把前面的覆盖,如果出现这种情况,返回旧值,在这里返回"tom"
     */
    public V put(K key, V value) {
        //1 为了简单,key不支持null
        if (key == null) {
            throw new RuntimeException("key is null");
        }

        //不直接用key.hashCode(),我们对key.hashCode()再作一次运算作为hash值
        //这个hash()的方法我是直接从HashMap源码拷贝过来的。可以不用关心hash()算法本身
        //只需要知道hash()输入一个数,返回一个数就行了。
        int hash = hash(key.hashCode());

        //用key的hash值和数组的大小,作一次映射,得到应该存放的位置
        int index = indexFor(hash, table.length);

        //看看数组中,有没有已存在的元素的key和参数中的key是相等的
        //相等则把老的值替换成新的,然后返回旧值
        QEntry<K, V> e = table[index];
        while (e != null) {
            //先比较hash是否相等,再比较对象是否相等,或者比较equals方法
            //如果相等了,说明有一样的key,这时要更新旧值为新的value,同时返回旧的值
            if (e.hash == hash && (key == e.key || key.equals(e.key))) {
                V oldValue = e.value;
                e.value = value;
                return oldValue;
            }
            e = e.next;
        }

        //如果数组中没有元素的key与传的key相等的话
        //把当前位置的元素保存下来
        QEntry<K, V> next = table[index];

        //next有可能为null,也有可能不为null,不管是否为null
        //next都要作为新元素的下一个节点(next传给了QEntry的构造函数)
        //然后新的元素保存在了index这个位置
        table[index] = new QEntry<>(key, value, hash, next);

        //如果需要扩容,元素的个数大于 table.length * 0.75 (别问为什么是0.75,经验)
        if (size++ >= (table.length * DEFAULT_LOAD_FACTOR)) {
            resize();
        }

        return null;
    }
复制代码

Notes in great detail, here are a few function hash () function is copied directly from the source code HashMap, do not tangle the algorithm. indexFor (), passing hash and size of the array, so they know what position we should go to the source to find or save these two functions are as follows:

   //对hashCode进行运算,JDK中HashMap的实现,直接拷贝过来了
    static int hash(int h) {
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

    //根据 h 求key落在数组的哪个位置
    static int indexFor(int h, int length) {
        //或者  return h & (length-1) 性能更好
        //这里我们用最容易理解的方式,对length取余数,范围就是[0,length - 1]
        //正好是table数组的所有的索引的范围

        h = h > 0 ? h : -h; //防止负数

        return h % length;
    }
复制代码

There is also a function expansion. When the number of elements is greater than table.length * 0.75, we started a resize expansion () of source code as follows:

  //扩容,元素的个数大于 table.length * 0.75
    //数组扩容到原来大小的2倍
    private void resize() {
        //新建一个数组,大小为原来数组大小的2倍
        int newCapacity = table.length * 2;
        QEntry[] newTable = new QEntry[newCapacity];

        QEntry[] src = table;

        //遍历旧数组,重新映射到新的数组中
        for (int j = 0; j < src.length; j++) {
            //获取旧数组元素
            QEntry<K, V> e = src[j];

            //释放旧数组
            src[j] = null;

            //因为e是一个链表,有可能有多个节点,循环遍历进行映射
            while (e != null) {
                //把e的下一个节点保存下来
                QEntry<K, V> next = e.next;

                //e这个当前节点进行在新的数组中映射
                int i = indexFor(e.hash, newCapacity);

                //newTable[i] 位置上有可能是null,也有可能不为null
                //不管是否为null,都作为e这个节点的下一个节点
                e.next = newTable[i];

                //把e保存在新数组的 i 的位置
                newTable[i] = e;

                //继续e的下一个节点的同样的处理
                e = next;
            }
        }

        //所有的节点都映射到了新数组上,别忘了把新数组的赋值给table
        table = newTable;
    }
复制代码

Compared put () function is, get () much easier. Just need to find a position corresponding to the hash through the array, and then traverse the list to find a key key and pass inside the element is equal to the line. Source put () method is as follows:

    //根据key获取value
    public V get(K key) {

        //同样为了简单,key不支持null
        if (key == null) {
            throw new RuntimeException("key is null");
        }

        //对key进行求hash值
        int hash = hash(key.hashCode());

        //用hash值进行映射,得到应该去数组的哪个位置上取数据
        int index = indexFor(hash, table.length);

        //把index位置的元素保存下来进行遍历
        //因为e是一个链表,我们要对链表进行遍历
        //找到和key相等的那个QEntry,并返回value
        QEntry<K, V> e = table[index];
        while (e != null) {

            //比较 hash值是否相等
            if (hash == e.hash && (key == e.key || key.equals(e.key))) {
                return e.value;
            }
                
            //如果不相等,继续找下一个    
            e = e.next;
        }

        return null;
    }
复制代码

QHashMap above is the core source code, we did not achieve deleted. Here the source is emitted to the entire class QHashMap

QHashMap complete source code as follows:

public class QHashMap<K, V> {
    //默认的数组的大小
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    //默认的扩容因子,当数组的大小大于或者等于当前容量 * 0.75的时候,就开始扩容
    private static final float DEFAULT_LOAD_FACTOR = 0.75f;

    //底层用一个数组来存放数据
    private QEntry[] table;

    //数组大小
    private int size;

    //一个点节,数组中存放的单位
    public static class QEntry<K, V> {
        K key;
        V value;
        int hash;
        QEntry<K, V> next;

        public QEntry(K key, V value, int hash, QEntry<K, V> next) {
            this.key = key;
            this.value = value;
            this.hash = hash;
            this.next = next;
        }
    }

    public QHashMap() {
        table = new QEntry[DEFAULT_INITIAL_CAPACITY];
        size = 0;
    }

    //根据key获取value
    public V get(K key) {

        //同样为了简单,key不支持null
        if (key == null) {
            throw new RuntimeException("key is null");
        }

        //对key进行求hash值
        int hash = hash(key.hashCode());

        //用hash值进行映射,得到应该去数组的哪个位置上取数据
        int index = indexFor(hash, table.length);

        //把index位置的元素保存下来进行遍历
        //因为e是一个链表,我们要对链表进行遍历
        //找到和key相等的那个QEntry,并返回value
        QEntry<K, V> e = table[index];
        while (e != null) {

            //比较 hash值是否相等
            if (hash == e.hash && (key == e.key || key.equals(e.key))) {
                return e.value;
            }
            
            //如果不相等,继续找下一个    
            e = e.next;
        }

        return null;
    }

    /**
     * 1 参数key,value很容易理解
     * 2 返回V,我们知道,HashMap有一个特点,
     * 如果调用了多次 map.put("name","tom"); map.put("name","lilei");
     * 后面的值会把前面的覆盖,如果出现这种情况,返回旧值,在这里返回"tom"
     */
    public V put(K key, V value) {
        //1 为了简单,key不支持null
        if (key == null) {
            throw new RuntimeException("key is null");
        }

        //不直接用key.hashCode(),我们对key.hashCode()再作一次运算作为hash值
        //这个hash()的方法我是直接从HashMap源码拷贝过来的。可以不用关心hash()算法本身
        //只需要知道hash()输入一个数,返回一个数就行了。
        int hash = hash(key.hashCode());

        //用key的hash值和数组的大小,作一次映射,得到应该存放的位置
        int index = indexFor(hash, table.length);

        //看看数组中,有没有已存在的元素的key和参数中的key是相等的
        //相等则把老的值替换成新的,然后返回旧值
        QEntry<K, V> e = table[index];
        while (e != null) {
            //先比较hash是否相等,再比较对象是否相等,或者比较equals方法
            //如果相等了,说明有一样的key,这时要更新旧值为新的value,同时返回旧的值
            if (e.hash == hash && (key == e.key || key.equals(e.key))) {
                V oldValue = e.value;
                e.value = value;
                return oldValue;
            }
            e = e.next;
        }

        //如果数组中没有元素的key与传的key相等的话
        //把当前位置的元素保存下来
        QEntry<K, V> next = table[index];

        //next有可能为null,也有可能不为null,不管是否为null
        //next都要作为新元素的下一个节点(next传给了QEntry的构造函数)
        //然后新的元素保存在了index这个位置
        table[index] = new QEntry<>(key, value, hash, next);

        //如果需要扩容,元素的个数大于 table.length * 0.75 (别问为什么是0.75,经验)
        if (size++ >= (table.length * DEFAULT_LOAD_FACTOR)) {
            resize();
        }

        return null;
    }

    //扩容,元素的个数大于 table.length * 0.75
    //数组扩容到原来大小的2倍
    private void resize() {
        //新建一个数组,大小为原来数组大小的2倍
        int newCapacity = table.length * 2;
        QEntry[] newTable = new QEntry[newCapacity];

        QEntry[] src = table;

        //遍历旧数组,重新映射到新的数组中
        for (int j = 0; j < src.length; j++) {
            //获取旧数组元素
            QEntry<K, V> e = src[j];

            //释放旧数组
            src[j] = null;

            //因为e是一个链表,有可能有多个节点,循环遍历进行映射
            while (e != null) {
                //把e的下一个节点保存下来
                QEntry<K, V> next = e.next;

                //e这个当前节点进行在新的数组中映射
                int i = indexFor(e.hash, newCapacity);

                //newTable[i] 位置上有可能是null,也有可能不为null
                //不管是否为null,都作为e这个节点的下一个节点
                e.next = newTable[i];

                //把e保存在新数组的 i 的位置
                newTable[i] = e;

                //继续e的下一个节点的同样的处理
                e = next;
            }
        }

        //所有的节点都映射到了新数组上,别忘了把新数组的赋值给table
        table = newTable;
    }

    //对hashCode进行运算,JDK中HashMap的实现,直接拷贝过来了
    static int hash(int h) {
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

    //根据 h 求key落在数组的哪个位置
    static int indexFor(int h, int length) {
        //或者  return h & (length-1) 性能更好
        //这里我们用最容易理解的方式,对length取余数,范围就是[0,length - 1]
        //正好是table数组的所有的索引的范围

        h = h > 0 ? h : -h; //防止负数

        return h % length;
    }

}
复制代码

Above is QHashMap principle. Here we write test code and look at our QHashMap can not function properly. Test code is as follows:

 public static void main(String[] args) {
        QHashMap<String, String> map = new QHashMap<>();
        map.put("name", "tom");
        map.put("age", "23");
        map.put("address", "beijing");
        String oldValue = map.put("address", "shanghai"); //key一样,返回旧值,保存新值

        System.out.println(map.get("name"));
        System.out.println(map.get("age"));

        System.out.println("旧值=" + oldValue);
        System.out.println("新值=" + map.get("address"));
    }
复制代码

Output is as follows:

tom
23
旧值=beijing
新值=shanghai
复制代码

The above simple realization QHashMap through, there is a good multi-function not implemented, relatively remove, clear, containsKey (), etc., as well as traversing relevant, interested readers can achieve their own

Guess you like

Origin juejin.im/post/5d5cf60af265da03ec2e681a