• 自己动手实现java数据结构(五)哈希表


    1.哈希表介绍

      前面我们已经介绍了许多类型的数据结构。在想要查询容器内特定元素时,有序向量使得我们能使用二分查找法进行精确的查询((O(logN)对数复杂度,很高效)。
      可人类总是不知满足,依然在寻求一种更高效的特定元素查询的数据结构,哈希表/散列表(hash table)就应运而生啦。哈希表在特定元素的插入,删除和查询时都能够达到O(1)常数的时间复杂度,十分高效。

    1.1 哈希算法

      哈希算法的定义:把任意长度的输入通过哈希算法转换映射为固定长度的输出,所得到的输出被称为哈希值(hashCode = hash(input))。哈希映射是一种多对一的关系,即多个不同的输入有可能对应着一个相同的哈希值输出;也意味着,哈希映射是不可逆,无法还原的。

      举个例子:我们有一个好朋友叫熊大,大家都叫他老熊。可以理解为是一个hash算法:对于一个人名,我们一般称呼为"老" + 姓氏(单姓) (hash(熊大) = 老熊)。同时,我们还有一个好朋友叫熊二,我们也叫他老熊(hash(熊二) = 老熊)。当熊大和熊二两个好朋友同时和我们聚会时,都称呼他们为老熊就不太合适啦,因为这时出现了hash冲突。老熊这个称呼同时对应了多个人,多个不同的输入对应了相同的哈希值输出。

      java在Object这一最高层对象中实现了hashCode方法,并允许子类重写更适应自身,冲突概率更低的hashCode方法。

    1.2 哈希表实现的基本思路

      哈希表存储的是key-value键值对结构的数据,其基础是一个数组。

      由于采用hash算法会出现hash冲突,一个数组下标对应了多个元素。常见的解决hash冲突的方法有:开放地址法、重新哈希法、拉链法等等,我们的哈希表实现采用的是拉链法解决hash冲突。

      采用拉链法的哈希表将内部数组的每一个元素视为一个插槽(slot)或者桶(bucket),并将数据存放在键值对节点(EntryNode)中。EntryNode除了存放key和value,还维护着一个next节点的引用。为了解决hash冲突,单个插槽内的多个EntryNode构成一个简单的单向链表,插槽指向链表的头部节点,新的数据将会插入当前链表的尾部。

      key值不同但映射的hash值相同的元素在哈希表的同一个插槽中以链表的形式共存。

      

    1.3 哈希表的负载因子(loadFactor):

      哈希表在查询数据时通过直接计算数据hash值对应的插槽,迅速获取到key值对应的数据,进行非常高效的数据查询。

      但依然存在一个问题:虽然设计良好的hash函数可以尽可能的降低hash冲突的概率,但hash冲突还是不可避免的。当发生频繁的哈希冲突时,对应的插槽内可能会存放较多的元素,导致插槽内的链表数据过多。而链表的查询效率是非常低的,在极端情况下,甚至会出现所有元素都映射存放在同一个插槽内,此时的哈希表退化成了一个链表,查询效率急剧降低。

      一般的,哈希表存储的数据量一定时,内部数组的大小和数组插槽指向的链表长度成反比。换句话说,总数据量一定,内部数组的容量越大(插槽越多),平均下来桶链表的长度也就越小,查询效率越高。

      同等数据量下,哈希表内部数组容量越大,查询效率越高,但同时空间占用也越高,这本质上是一个空间换时间的取舍。

      哈希表允许用户在初始化时指定负载因子(loadFactor):负载因子代表着存储的总数据量内部数组大小比值。插入新数据时,判断哈希表当前的存储量和内部数组的比值是否超过了负载因子。当比值超过了负载因子时,哈希表认为内部过于拥挤,查询效率太低,会触发一次扩容的rehash操作。rehash会对内部数组扩容,将存储的元素重新进行hash映射,使得哈希表始终保持一个合适的查询效率。

      通过指定自定义的负载因子,用户可以控制哈希表在空间和时间上取舍的程度,使哈希表能更有效地适应用户的使用场景。

      指定的负载因子越大,哈希表越拥挤(负载高,紧凑),查询效率越低,空间效率越高。

      指定的负载因子越小,哈希表越稀疏(负载小,松散),查询效率越高,空间效率越低。

    2.哈希表ADT接口

      和之前介绍的链表不同,我们在哈希表的ADT接口中暴露出了哈希表内部实现的EntryNode键值对节点。通过暴露出去的public方法,用户在使用哈希表时,可以获得内部的键值对节点,灵活的访问其中的key、value数据(但没有暴露setKey方法,不允许用户自己设置key值)。

    public interface Map <K,V>{
        /**
         * 存入键值对
         * @param key   key值
         * @param value value
         * @return 被覆盖的的value值
         */
        V put(K key,V value);
    
        /**
         * 移除键值对
         * @param key   key值
         * @return 被删除的value的值
         */
        V remove(K key);
    
        /**
         * 获取key对应的value值
         * @param key   key值
         * @return      对应的value值
         */
        V get(K key);
    
        /**
         * 是否包含当前key值
         * @param key   key值
         * @return      true:包含 false:不包含
         */
        boolean containsKey(K key);
    
        /**
         * 是否包含当前value值
         * @param value   value值
         * @return        true:包含 false:不包含
         */
        boolean containsValue(V value);
    
        /**
         * 获得当前map存储的键值对数量
         * @return 键值对数量
         * */
        int size();
    
        /**
         * 当前map是否为空
         * @return  true:为空 false:不为空
         */
        boolean isEmpty();
    
        /**
         * 清空当前map
         */
        void clear();
    
        /**
         * 获得迭代器
         * @return 迭代器对象
         */
        Iterator<EntryNode<K,V>> iterator();
    
        /**
         * 键值对节点 内部类
         * */
        class EntryNode<K,V>{
            final K key;
            V value;
            EntryNode<K,V> next;
    
            EntryNode(K key, V value) {
                this.key = key;
                this.value = value;
            }
    
            boolean keyIsEquals(K key){
                if(this.key == key){
                    return true;
                }
    
                if(key == null){
                    //:::如果走到这步,this.key不等于null,不匹配
                    return false;
                }else{
                    return key.equals(this.key);
                }
            }
    
            EntryNode<K, V> getNext() {
                return next;
            }
    
            void setNext(EntryNode<K, V> next) {
                this.next = next;
            }
    
            public K getKey() {
                return key;
            }
    
            public V getValue() {
                return value;
            }
    
            public void setValue(V value) {
                this.value = value;
            }
    
            @Override
            public String toString() {
                return key + "=" + value;
            }
        }
    }

    3.哈希表实现细节

    3.1 哈希表基本属性:      

    public class HashMap<K,V> implements Map<K,V>{
    
        /**
         * 内部数组
         * */
        private EntryNode<K,V>[] elements;
    
        /**
         * 当前哈希表的大小
         * */
        private int size;
    
        /**
         * 负载因子
         * */
        private float loadFactor;
    
        /**
         * 默认的哈希表容量
         * */
        private final static int DEFAULT_CAPACITY = 16;
    
        /**
         * 扩容翻倍的基数
         * */
        private final static int REHASH_BASE = 2;
    
        /**
         * 默认的负载因子
         * */
        private final static float DEFAULT_LOAD_FACTOR = 0.75f;
    
        //========================================构造方法===================================================
        /**
         * 默认构造方法
         * */
        @SuppressWarnings("unchecked")
        public HashMap() {
            this.size = 0;
            this.loadFactor = DEFAULT_LOAD_FACTOR;
            elements = new EntryNode[DEFAULT_CAPACITY];
        }
    
        /**
         * 指定初始容量的构造方法
         * @param capacity 指定的初始容量
         * */
        @SuppressWarnings("unchecked")
        public HashMap(int capacity) {
            this.size = 0;
            this.loadFactor = DEFAULT_LOAD_FACTOR;
            elements = new EntryNode[capacity];
        }
    
        /**
         * 指定初始容量和负载因子的构造方法
         * @param capacity 指定的初始容量
         * @param loadFactor 指定的负载因子
         * */
        @SuppressWarnings("unchecked")
        public HashMap(int capacity,int loadFactor) {
            this.size = 0;
            this.loadFactor = loadFactor;
            elements = new EntryNode[capacity];
        }
    }

    3.2 通过hash值获取对应插槽下标:

      获取hash的方法仅和数据自身有关,不受到哈希表存储数据量的影响。

      因此getIndex方法的时间复杂度为O(1)

       /**
         * 通过key的hashCode获得对应的内部数组下标
         * @param key 传入的键值key
         * @return 对应的内部数组下标
         * */
        private int getIndex(K key){
            return getIndex(key,this.elements);
        }
    
        /**
         * 通过key的hashCode获得对应的内部数组插槽slot下标
         * @param key 传入的键值key
         * @param elements 内部数组
         * @return 对应的内部数组下标
         * */
        private int getIndex(K key,EntryNode<K,V>[] elements){
            if(key == null){
                //::: null 默认存储在第0个桶内
                return 0;
            }else{
                int hashCode = key.hashCode();
    
                //:::通过 高位和低位的异或运算,获得最终的hash映射,减少碰撞的几率
                int finalHashCode = hashCode ^ (hashCode >>> 16);
                return (elements.length-1) & finalHashCode;
            }
        }

    3.3 链表查询方法:

      当出现hash冲突时,会在对应插槽处生成一个单链表。我们需要提供一个方便的单链表查询方法,将增删改查接口的部分公用逻辑抽象出来,简化代码的复杂度。

      值得注意的是:在判断Key值是否相等时使用的是EntryNode.keyIsEquals方法,内部最终是通过equals方法进行比较的。也就是说,判断key值是否相等和其它数据结构一样,依然是由equals方法决定的。hashCode方法的作用仅仅是使我们能够更快的定位到所映射的插槽处,加快查询效率。

      思考一下,为什么要求在重写equals方法的同时,也应该重写hashCode方法?

       /**
         * 获得目标节点的前一个节点
         * @param currentNode 当前桶链表节点
         * @param key         对应的key
         * @return  返回当前桶链表中"匹配key的目标节点"的"前一个节点"
         *          注意:当桶链表中不存在匹配节点时,返回桶链表的最后一个节点
         * */
        private EntryNode<K,V> getTargetPreviousEntryNode(EntryNode<K,V> currentNode,K key){
            //:::不匹配
            EntryNode<K,V> nextNode = currentNode.next;
            //:::遍历当前桶后面的所有节点
            while(nextNode != null){
                //:::如果下一个节点的key匹配
                if(nextNode.keyIsEquals(key)){
                    return currentNode;
                }else{
                    //:::不断指向下一个节点
                    currentNode = nextNode;
                    nextNode = nextNode.next;
                }
            }
            //:::到达了桶链表的末尾,返回最后一个节点
            return currentNode;
        }

    3.4 增删改查接口:

      哈希表的增删改查接口都是通过hash值直接计算出对应的插槽下标(getIndex方法),然后遍历插槽内的桶链表进行进一步的精确查询(getTargetPreviousEntryNode方法)。在负载因子位于正常范围内时(一般小于1),桶链表的平均长度非常短,可以认为单个桶链表的遍历查询时间复杂度为(O(1))

      因此哈希表的增删改查接口时间复杂度都是O(1)

        @Override
        public V put(K key, V value) {
            if(needReHash()){
                reHash();
            }
    
            //:::获得对应的内部数组下标
            int index = getIndex(key);
            //:::获得对应桶内的第一个节点
            EntryNode<K,V> firstEntryNode = this.elements[index];
    
            //:::如果当前桶内不存在任何节点
            if(firstEntryNode == null){
                //:::创建一个新的节点
                this.elements[index] = new EntryNode<>(key,value);
                //:::创建了新节点,size加1
                this.size++;
                return null;
            }
    
            if(firstEntryNode.keyIsEquals(key)){
                //:::当前第一个节点的key与之匹配
                V oldValue = firstEntryNode.value;
                firstEntryNode.value = value;
                return oldValue;
            }else{
                //:::不匹配
    
                //:::获得匹配的目标节点的前一个节点
                EntryNode<K,V> targetPreviousNode = getTargetPreviousEntryNode(firstEntryNode,key);
                //:::获得匹配的目标节点
                EntryNode<K,V> targetNode = targetPreviousNode.next;
                if(targetNode != null){
                    //:::更新value的值
                    V oldValue = targetNode.value;
                    targetNode.value = value;
                    return oldValue;
                }else{
                    //:::在桶链表的末尾 新增一个节点
                    targetPreviousNode.next = new EntryNode<>(key,value);
                    //:::创建了新节点,size加1
                    this.size++;
                    return null;
                }
            }
        }
    
        @Override
        public V remove(K key) {
            //:::获得对应的内部数组下标
            int index = getIndex(key);
            //:::获得对应桶内的第一个节点
            EntryNode<K,V> firstEntryNode = this.elements[index];
    
            //:::如果当前桶内不存在任何节点
            if(firstEntryNode == null){
                return null;
            }
            if(firstEntryNode.keyIsEquals(key)){
                //:::当前第一个节点的key与之匹配
    
                //:::将桶链表的第一个节点指向后一个节点(兼容next为null的情况)
                this.elements[index] = firstEntryNode.next;
                //:::移除了一个节点 size减一
                this.size--;
                //:::返回之前的value值
                return firstEntryNode.value;
            }else{
                //:::不匹配
    
                //:::获得匹配的目标节点的前一个节点
                EntryNode<K,V> targetPreviousNode = getTargetPreviousEntryNode(firstEntryNode,key);
                //:::获得匹配的目标节点
                EntryNode<K,V> targetNode = targetPreviousNode.next;
    
                if(targetNode != null){
                    //:::将"前一个节点的next" 指向 "目标节点的next" ---> 相当于将目标节点从桶链表移除
                    targetPreviousNode.next = targetNode.next;
                    //:::移除了一个节点 size减一
                    this.size--;
                    return targetNode.value;
                }else{
                    //:::如果目标节点为空,说明key并不存在于哈希表中
                    return null;
                }
            }
        }
    
        @Override
        public V get(K key) {
            //:::获得对应的内部数组下标
            int index = getIndex(key);
            //:::获得对应桶内的第一个节点
            EntryNode<K,V> firstEntryNode = this.elements[index];
    
            //:::如果当前桶内不存在任何节点
            if(firstEntryNode == null){
                return null;
            }
            if(firstEntryNode.keyIsEquals(key)){
                //:::当前第一个节点的key与之匹配
                return firstEntryNode.value;
            }else{
                //:::获得匹配的目标节点的前一个节点
                EntryNode<K,V> targetPreviousNode = getTargetPreviousEntryNode(firstEntryNode,key);
                //:::获得匹配的目标节点
                EntryNode<K,V> targetNode = targetPreviousNode.next;
    
                if(targetNode != null){
                    return targetNode.value;
                }else{
                    //:::如果目标节点为空,说明key并不存在于哈希表中
                    return null;
                }
            }
        }

    3.5 扩容rehash操作:

      前面提到,当插入数据时发现哈希表过于拥挤,超过了负载因子指定的值时,会触发一次rehash扩容操作。

      扩容时,我们的内部数组扩容了2倍,所以对于每一个插槽内的元素在rehash时存在两种可能:

        1.依然映射到当前下标插槽处

        2.映射到高位下标处(当前下标 + 扩容前内部数组长度大小)

      注意观察0,4,8三个元素节点,在扩容前(对4取模)都位于下标0插槽;扩容后,数组容量翻倍(对8取模),存在两种情况,0,8两个元素哈希值依然映射在下标0插槽(低位插槽),而元素4则被映射到了下标4插槽(高位插槽)(当前下标(0) + 扩容前内部数组长度大小(4))。

      通过遍历每个插槽,将内部元素按顺序进行rehash,得到扩容两倍后的哈希表(数据保留了之前的顺序,即先插入的节点依然位于桶链表靠前的位置)。

      和向量扩容一样,虽然rehash操作的时间复杂度为O(n)。但是由于只在插入时偶尔的被触发,总体上看,rehash操作的时间复杂度为O(1)

    哈希表扩容前:

    哈希表扩容后:

     

      /**
         * 哈希表扩容
         * */
        @SuppressWarnings("unchecked")
        private void reHash(){
            //:::扩容两倍
            EntryNode<K,V>[] newElements = new EntryNode[this.elements.length * REHASH_BASE];
    
            //:::遍历所有的插槽
            for (int i=0; i<this.elements.length; i++) {
                //:::为单个插槽内的元素 rehash
                reHashSlot(i,newElements);
            }
    
            //:::内部数组 ---> 扩容之后的新数组
            this.elements = newElements;
        }
    
        /**
         * 单个插槽内的数据进行rehash
         * */
        private void reHashSlot(int index,EntryNode<K, V>[] newElements){
            //:::获得当前插槽第一个元素
            EntryNode<K, V> currentEntryNode = this.elements[index];
            if(currentEntryNode == null){
                //:::当前插槽为空,直接返回
                return;
            }
    
            //:::低位桶链表 头部节点、尾部节点
            EntryNode<K, V> lowListHead = null;
            EntryNode<K, V> lowListTail = null;
            //:::高位桶链表 头部节点、尾部节点
            EntryNode<K, V> highListHead = null;
            EntryNode<K, V> highListTail = null;
    
            while(currentEntryNode != null){
                //:::获得当前节点 在新数组中映射的插槽下标
                int entryNodeIndex = getIndex(currentEntryNode.key,newElements);
                //:::是否和当前插槽下标相等
                if(entryNodeIndex == index){
                    //:::和当前插槽下标相等
                    if(lowListHead == null){
                        //:::初始化低位链表
                        lowListHead = currentEntryNode;
                        lowListTail = currentEntryNode;
                    }else{
                        //:::在低位链表尾部拓展新的节点
                        lowListTail.next = currentEntryNode;
                        lowListTail = lowListTail.next;
                    }
                }else{
                    //:::和当前插槽下标不相等
                    if(highListHead == null){
                        //:::初始化高位链表
                        highListHead = currentEntryNode;
                        highListTail = currentEntryNode;
                    }else{
                        //:::在高位链表尾部拓展新的节点
                        highListTail.next = currentEntryNode;
                        highListTail = highListTail.next;
                    }
                }
                //:::指向当前插槽的下一个节点
                currentEntryNode = currentEntryNode.next;
            }
    
            //:::新扩容elements(index)插槽 存放lowList
            newElements[index] = lowListHead;
            //:::lowList末尾截断
            if(lowListTail != null){
                lowListTail.next = null;
            }
    
            //:::新扩容elements(index + this.elements.length)插槽 存放highList
            newElements[index + this.elements.length] = highListHead;
            //:::highList末尾截断
            if(highListTail != null){
                highListTail.next = null;
            }
        }
    
        /**
         * 判断是否需要 扩容
         * */
        private boolean needReHash(){
            return ((this.size / this.elements.length) > this.loadFactor);
        }

    3.6 其它接口实现:

        @Override
        public boolean containsKey(K key) {
            V value = get(key);
            return (value != null);
        }
    
        @Override
        public boolean containsValue(V value) {
            //:::遍历全部桶链表
            for (EntryNode<K, V> element : this.elements) {
                //:::获得当前桶链表第一个节点
                EntryNode<K, V> entryNode = element;
    
                //:::遍历当前桶链表
                while (entryNode != null) {
                    //:::如果value匹配
                    if (entryNode.value.equals(value)) {
                        //:::返回true
                        return true;
                    } else {
                        //:::不匹配,指向下一个节点
                        entryNode = entryNode.next;
                    }
                }
            }
            //:::所有的节点都遍历了,没有匹配的value
            return false;
        }
    
        @Override
        public int size() {
            return this.size;
        }
    
        @Override
        public boolean isEmpty() {
            return (this.size == 0);
        }
    
        @Override
        public void clear() {
            //:::遍历内部数组,将所有桶链表全部清空
            for(int i=0; i<this.elements.length; i++){
                this.elements[i] = null;
            }
    
            //:::size设置为0
            this.size = 0;
        }
    
        @Override
        public Iterator<EntryNode<K,V>> iterator() {
            return new Itr();
        }
    
        @Override
        public String toString() {
            Iterator<EntryNode<K,V>> iterator = this.iterator();
    
            //:::空容器
            if(!iterator.hasNext()){
                return "[]";
            }
    
            //:::容器起始使用"["
            StringBuilder s = new StringBuilder("[");
    
            //:::反复迭代
            while(true){
                //:::获得迭代的当前元素
                EntryNode<K,V> data = iterator.next();
    
                //:::判断当前元素是否是最后一个元素
                if(!iterator.hasNext()){
                    //:::是最后一个元素,用"]"收尾
                    s.append(data).append("]");
                    //:::返回 拼接完毕的字符串
                    return s.toString();
                }else{
                    //:::不是最后一个元素
                    //:::使用", "分割,拼接到后面
                    s.append(data).append(", ");
                }
            }
        }

    4.哈希表迭代器

      1. 由于哈希表中数据分布不是连续的,所以在迭代器的初始化过程中必须先跳转到第一个非空数据节点,以避免无效的迭代。

      2. 当迭代器的下标到达当前插槽链表的末尾时,迭代器下标需要跳转到靠后插槽的第一个非空数据节点。

      /**
         * 哈希表 迭代器实现
         */
        private class Itr implements Iterator<EntryNode<K,V>> {
            /**
             * 迭代器 当前节点
             * */
            private EntryNode<K,V> currentNode;
    
            /**
             * 迭代器 下一个节点
             * */
            private EntryNode<K,V> nextNode;
    
            /**
             * 迭代器 当前内部数组的下标
             * */
            private int currentIndex;
    
            /**
             * 默认构造方法
             * */
            private Itr(){
                //:::如果当前哈希表为空,直接返回
                if(HashMap.this.isEmpty()){
                    return;
                }
                //:::在构造方法中,将迭代器下标移动到第一个有效的节点上
    
                //:::遍历内部数组,找到第一个不为空的数组插槽slot
                for(int i=0; i<HashMap.this.elements.length; i++){
                    //:::设置当前index
                    this.currentIndex = i;
    
                    EntryNode<K,V> firstEntryNode = HashMap.this.elements[i];
                    //:::找到了第一个不为空的插槽slot
                    if(firstEntryNode != null){
                        //:::nextNode = 当前插槽第一个节点
                        this.nextNode = firstEntryNode;
    
                        //:::构造方法立即结束
                        return;
                    }
                }
            }
    
            @Override
            public boolean hasNext() {
                return (this.nextNode != null);
            }
    
            @Override
            public EntryNode<K,V> next() {
                this.currentNode = this.nextNode;
                //:::暂存需要返回的节点
                EntryNode<K,V> needReturn = this.nextNode;
    
                //:::nextNode指向自己的next
                this.nextNode = this.nextNode.next;
                //:::判断当前nextNode是否为null
                if(this.nextNode == null){
                    //:::说明当前所在的桶链表已经遍历完毕
    
                    //:::寻找下一个非空的插槽
                    for(int i=this.currentIndex+1; i<HashMap.this.elements.length; i++){
                        //:::设置当前index
                        this.currentIndex = i;
    
                        EntryNode<K,V> firstEntryNode = HashMap.this.elements[i];
                        //:::找到了后续不为空的插槽slot
                        if(firstEntryNode != null){
                            //:::nextNode = 当前插槽第一个节点
                            this.nextNode = firstEntryNode;
                            //:::跳出循环
                            break;
                        }
                    }
                }
                return needReturn;
            }
    
            @Override
            public void remove() {
                if(this.currentNode == null){
                    throw new IteratorStateErrorException("迭代器状态异常: 可能在一次迭代中进行了多次remove操作");
                }
    
                //:::获得需要被移除的节点的key
                K currentKey = this.currentNode.key;
                //:::将其从哈希表中移除
                HashMap.this.remove(currentKey);
    
                //:::currentNode设置为null,防止反复调用remove方法
                this.currentNode = null;
            }
        }

    5.哈希表性能

    5.1 空间效率:

      哈希表的空间效率很大程度上取决于负载因子。通常,为了保证哈希表查询的高效性,负载因子都设置的比较小(小于1),因而可能会出现许多空的插槽,浪费空间。

      总体而言,哈希表的空间效率低于向量和链表。

    5.2 时间效率:

      一般的,哈希表增删改查接口的时间复杂度都是O(1)。但是出现较多的hash冲突时,冲突范围内的key的增删改查效率较低,时间效率会有一定的波动。

      总体而言,哈希表的时间效率高于向量和链表。

      哈希表的时间效率很高,可天下没有免费的午餐,据统计,哈希表的空间利用率通常情况下还不到50%。

      哈希表是一个使用空间来换取时间的数据结构,对查询性能有较高要求的场合,可以考虑使用哈希表。

    6.哈希表总结

    6.1 当前版本缺陷

      至此,我们已经实现了一个基础的哈希表,但还存在许多明显缺陷:   

      1.当hash冲突比较频繁时,查询效率急剧降低。

      jdk在1.8版本的哈希表实现(java.util.HashMap)中,对这一场景进行了优化。当内部桶链表的节点个数超过一定数量(默认为8)时,会将插槽中的桶链表转换成一个红黑树(查询效率为O(logN))。

      2.不支持多线程

      在多线程的环境,并发的访问一个哈希表会导致诸如:扩容时内部节点死循环、丢失插入数据等异常情况。

    6.2 查询特定元素的方法

      我们目前查询特定元素有几种不同的方法:

      1.顺序查找

      在无序向量或者链表中,查找一个特定元素是通过从头到尾遍历容器内元素的方式实现的,执行速度正比于数据量的大小,顺序查找的时间复杂度为O(n),效率较低

      2.二分查找

      在有序向量以及后面要介绍的二叉搜索树中,由于容器内部的元素是有序的,因此可以通过二分查找比较的方式查询特定的元素,二分查找的时间复杂度为O(logN),效率较高。 

      3.哈希查找

      在哈希表中,通过直接计算出数据hash值对应的插槽(slot)(时间复杂度O(1)),查找出对应的数据,哈希查找的时间复杂度为O(1),效率极高。

    特定元素的查找方式和排序算法的关系

      1.顺序查找对应冒泡排序选择排序等,效率较低,时间复杂度(O(n²))。

      2.二分查找对应快速排序归并排序等,效率较高,时间复杂度(O(nLogn))。

      3.哈希查找对应基排序,效率极高,时间复杂度(O(n))。

      在大牛刘未鹏的博客中有更为详细的说明,http://mindhacks.cn/2008/06/13/why-is-quicksort-so-quick

    6.3 完整代码

    哈希表ADT接口:

      1 public interface Map <K,V>{
      2     /**
      3      * 存入键值对
      4      * @param key   key值
      5      * @param value value
      6      * @return 被覆盖的的value值
      7      */
      8     V put(K key,V value);
      9 
     10     /**
     11      * 移除键值对
     12      * @param key   key值
     13      * @return 被删除的value的值
     14      */
     15     V remove(K key);
     16 
     17     /**
     18      * 获取key对应的value值
     19      * @param key   key值
     20      * @return      对应的value值
     21      */
     22     V get(K key);
     23 
     24     /**
     25      * 是否包含当前key值
     26      * @param key   key值
     27      * @return      true:包含 false:不包含
     28      */
     29     boolean containsKey(K key);
     30 
     31     /**
     32      * 是否包含当前value值
     33      * @param value   value值
     34      * @return        true:包含 false:不包含
     35      */
     36     boolean containsValue(V value);
     37 
     38     /**
     39      * 获得当前map存储的键值对数量
     40      * @return 键值对数量
     41      * */
     42     int size();
     43 
     44     /**
     45      * 当前map是否为空
     46      * @return  true:为空 false:不为空
     47      */
     48     boolean isEmpty();
     49 
     50     /**
     51      * 清空当前map
     52      */
     53     void clear();
     54 
     55     /**
     56      * 获得迭代器
     57      * @return 迭代器对象
     58      */
     59     Iterator<EntryNode<K,V>> iterator();
     60 
     61     /**
     62      * 键值对节点 内部类
     63      * */
     64     class EntryNode<K,V>{
     65         final K key;
     66         V value;
     67         EntryNode<K,V> next;
     68 
     69         EntryNode(K key, V value) {
     70             this.key = key;
     71             this.value = value;
     72         }
     73 
     74         boolean keyIsEquals(K key){
     75             if(this.key == key){
     76                 return true;
     77             }
     78 
     79             if(key == null){
     80                 //:::如果走到这步,this.key不等于null,不匹配
     81                 return false;
     82             }else{
     83                 return key.equals(this.key);
     84             }
     85         }
     86 
     87         EntryNode<K, V> getNext() {
     88             return next;
     89         }
     90 
     91         void setNext(EntryNode<K, V> next) {
     92             this.next = next;
     93         }
     94 
     95         public K getKey() {
     96             return key;
     97         }
     98 
     99         public V getValue() {
    100             return value;
    101         }
    102 
    103         public void setValue(V value) {
    104             this.value = value;
    105         }
    106 
    107         @Override
    108         public String toString() {
    109             return key + "=" + value;
    110         }
    111     }
    112 }
    View Code

    哈希表实现:

      1 public class HashMap<K,V> implements Map<K,V>{
      2 
      3     //===========================================成员属性================================================
      4     /**
      5      * 内部数组
      6      * */
      7     private EntryNode<K,V>[] elements;
      8 
      9     /**
     10      * 当前哈希表的大小
     11      * */
     12     private int size;
     13 
     14     /**
     15      * 负载因子
     16      * */
     17     private float loadFactor;
     18 
     19     /**
     20      * 默认的哈希表容量
     21      * */
     22     private final static int DEFAULT_CAPACITY = 16;
     23 
     24     /**
     25      * 扩容翻倍的基数 两倍
     26      * */
     27     private final static int REHASH_BASE = 2;
     28 
     29     /**
     30      * 默认的负载因子
     31      * */
     32     private final static float DEFAULT_LOAD_FACTOR = 0.75f;
     33 
     34     //========================================构造方法===================================================
     35     /**
     36      * 默认构造方法
     37      * */
     38     @SuppressWarnings("unchecked")
     39     public HashMap() {
     40         this.size = 0;
     41         this.loadFactor = DEFAULT_LOAD_FACTOR;
     42         elements = new EntryNode[DEFAULT_CAPACITY];
     43     }
     44 
     45     /**
     46      * 指定初始容量的构造方法
     47      * @param capacity 指定的初始容量
     48      * */
     49     @SuppressWarnings("unchecked")
     50     public HashMap(int capacity) {
     51         this.size = 0;
     52         this.loadFactor = DEFAULT_LOAD_FACTOR;
     53         elements = new EntryNode[capacity];
     54     }
     55 
     56     /**
     57      * 指定初始容量和负载因子的构造方法
     58      * @param capacity 指定的初始容量
     59      * @param loadFactor 指定的负载因子
     60      * */
     61     @SuppressWarnings("unchecked")
     62     public HashMap(int capacity,int loadFactor) {
     63         this.size = 0;
     64         this.loadFactor = loadFactor;
     65         elements = new EntryNode[capacity];
     66     }
     67 
     68     //==========================================内部辅助方法=============================================
     69     /**
     70      * 通过key的hashCode获得对应的内部数组下标
     71      * @param key 传入的键值key
     72      * @return 对应的内部数组下标
     73      * */
     74     private int getIndex(K key){
     75         return getIndex(key,this.elements);
     76     }
     77 
     78     /**
     79      * 通过key的hashCode获得对应的内部数组插槽slot下标
     80      * @param key 传入的键值key
     81      * @param elements 内部数组
     82      * @return 对应的内部数组下标
     83      * */
     84     private int getIndex(K key,EntryNode<K,V>[] elements){
     85         if(key == null){
     86             //::: null 默认存储在第0个桶内
     87             return 0;
     88         }else{
     89             int hashCode = key.hashCode();
     90 
     91             //:::通过 高位和低位的异或运算,获得最终的hash映射,减少碰撞的几率
     92             int finalHashCode = hashCode ^ (hashCode >>> 16);
     93             return (elements.length-1) & finalHashCode;
     94         }
     95     }
     96 
     97     /**
     98      * 获得目标节点的前一个节点
     99      * @param currentNode 当前桶链表节点
    100      * @param key         对应的key
    101      * @return 返回当前桶链表中"匹配key的目标节点"的"前一个节点"
    102      *          注意:当桶链表中不存在匹配节点时,返回桶链表的最后一个节点
    103      * */
    104     private EntryNode<K,V> getTargetPreviousEntryNode(EntryNode<K,V> currentNode,K key){
    105         //:::不匹配
    106         EntryNode<K,V> nextNode = currentNode.next;
    107         //:::遍历当前桶后面的所有节点
    108         while(nextNode != null){
    109             //:::如果下一个节点的key匹配
    110             if(nextNode.keyIsEquals(key)){
    111                 return currentNode;
    112             }else{
    113                 //:::不断指向下一个节点
    114                 currentNode = nextNode;
    115                 nextNode = nextNode.next;
    116             }
    117         }
    118 
    119         //:::到达了桶链表的末尾,返回最后一个节点
    120         return currentNode;
    121     }
    122 
    123     /**
    124      * 哈希表扩容
    125      * */
    126     @SuppressWarnings("unchecked")
    127     private void reHash(){
    128         //:::扩容两倍
    129         EntryNode<K,V>[] newElements = new EntryNode[this.elements.length * REHASH_BASE];
    130 
    131         //:::遍历所有的插槽
    132         for (int i=0; i<this.elements.length; i++) {
    133             //:::为单个插槽内的元素 rehash
    134             reHashSlot(i,newElements);
    135         }
    136 
    137         //:::内部数组 ---> 扩容之后的新数组
    138         this.elements = newElements;
    139     }
    140 
    141     /**
    142      * 单个插槽内的数据进行rehash
    143      * */
    144     private void reHashSlot(int index,EntryNode<K, V>[] newElements){
    145         //:::获得当前插槽第一个元素
    146         EntryNode<K, V> currentEntryNode = this.elements[index];
    147         if(currentEntryNode == null){
    148             //:::当前插槽为空,直接返回
    149             return;
    150         }
    151 
    152         //:::低位桶链表 头部节点、尾部节点
    153         EntryNode<K, V> lowListHead = null;
    154         EntryNode<K, V> lowListTail = null;
    155         //:::高位桶链表 头部节点、尾部节点
    156         EntryNode<K, V> highListHead = null;
    157         EntryNode<K, V> highListTail = null;
    158 
    159         while(currentEntryNode != null){
    160             //:::获得当前节点 在新数组中映射的插槽下标
    161             int entryNodeIndex = getIndex(currentEntryNode.key,newElements);
    162             //:::是否和当前插槽下标相等
    163             if(entryNodeIndex == index){
    164                 //:::和当前插槽下标相等
    165                 if(lowListHead == null){
    166                     //:::初始化低位链表
    167                     lowListHead = currentEntryNode;
    168                     lowListTail = currentEntryNode;
    169                 }else{
    170                     //:::在低位链表尾部拓展新的节点
    171                     lowListTail.next = currentEntryNode;
    172                     lowListTail = lowListTail.next;
    173                 }
    174             }else{
    175                 //:::和当前插槽下标不相等
    176                 if(highListHead == null){
    177                     //:::初始化高位链表
    178                     highListHead = currentEntryNode;
    179                     highListTail = currentEntryNode;
    180                 }else{
    181                     //:::在高位链表尾部拓展新的节点
    182                     highListTail.next = currentEntryNode;
    183                     highListTail = highListTail.next;
    184                 }
    185             }
    186             //:::指向当前插槽的下一个节点
    187             currentEntryNode = currentEntryNode.next;
    188         }
    189 
    190         //:::新扩容elements(index)插槽 存放lowList
    191         newElements[index] = lowListHead;
    192         //:::lowList末尾截断
    193         if(lowListTail != null){
    194             lowListTail.next = null;
    195         }
    196 
    197         //:::新扩容elements(index + this.elements.length)插槽 存放highList
    198         newElements[index + this.elements.length] = highListHead;
    199         //:::highList末尾截断
    200         if(highListTail != null){
    201             highListTail.next = null;
    202         }
    203     }
    204 
    205     /**
    206      * 判断是否需要 扩容
    207      * */
    208     private boolean needReHash(){
    209         return ((this.size / this.elements.length) > this.loadFactor);
    210     }
    211 
    212     //============================================外部接口================================================
    213 
    214     @Override
    215     public V put(K key, V value) {
    216         if(needReHash()){
    217             reHash();
    218         }
    219 
    220         //:::获得对应的内部数组下标
    221         int index = getIndex(key);
    222         //:::获得对应桶内的第一个节点
    223         EntryNode<K,V> firstEntryNode = this.elements[index];
    224 
    225         //:::如果当前桶内不存在任何节点
    226         if(firstEntryNode == null){
    227             //:::创建一个新的节点
    228             this.elements[index] = new EntryNode<>(key,value);
    229             //:::创建了新节点,size加1
    230             this.size++;
    231             return null;
    232         }
    233 
    234         if(firstEntryNode.keyIsEquals(key)){
    235             //:::当前第一个节点的key与之匹配
    236             V oldValue = firstEntryNode.value;
    237             firstEntryNode.value = value;
    238             return oldValue;
    239         }else{
    240             //:::不匹配
    241 
    242             //:::获得匹配的目标节点的前一个节点
    243             EntryNode<K,V> targetPreviousNode = getTargetPreviousEntryNode(firstEntryNode,key);
    244             //:::获得匹配的目标节点
    245             EntryNode<K,V> targetNode = targetPreviousNode.next;
    246             if(targetNode != null){
    247                 //:::更新value的值
    248                 V oldValue = targetNode.value;
    249                 targetNode.value = value;
    250                 return oldValue;
    251             }else{
    252                 //:::在桶链表的末尾 新增一个节点
    253                 targetPreviousNode.next = new EntryNode<>(key,value);
    254                 //:::创建了新节点,size加1
    255                 this.size++;
    256                 return null;
    257             }
    258         }
    259     }
    260 
    261     @Override
    262     public V remove(K key) {
    263         //:::获得对应的内部数组下标
    264         int index = getIndex(key);
    265         //:::获得对应桶内的第一个节点
    266         EntryNode<K,V> firstEntryNode = this.elements[index];
    267 
    268         //:::如果当前桶内不存在任何节点
    269         if(firstEntryNode == null){
    270             return null;
    271         }
    272 
    273         if(firstEntryNode.keyIsEquals(key)){
    274             //:::当前第一个节点的key与之匹配
    275 
    276             //:::将桶链表的第一个节点指向后一个节点(兼容next为null的情况)
    277             this.elements[index] = firstEntryNode.next;
    278             //:::移除了一个节点 size减一
    279             this.size--;
    280             //:::返回之前的value值
    281             return firstEntryNode.value;
    282         }else{
    283             //:::不匹配
    284 
    285             //:::获得匹配的目标节点的前一个节点
    286             EntryNode<K,V> targetPreviousNode = getTargetPreviousEntryNode(firstEntryNode,key);
    287             //:::获得匹配的目标节点
    288             EntryNode<K,V> targetNode = targetPreviousNode.next;
    289 
    290             if(targetNode != null){
    291                 //:::将"前一个节点的next" 指向 "目标节点的next" ---> 相当于将目标节点从桶链表移除
    292                 targetPreviousNode.next = targetNode.next;
    293                 //:::移除了一个节点 size减一
    294                 this.size--;
    295                 return targetNode.value;
    296             }else{
    297                 //:::如果目标节点为空,说明key并不存在于哈希表中
    298                 return null;
    299             }
    300         }
    301     }
    302 
    303     @Override
    304     public V get(K key) {
    305         //:::获得对应的内部数组下标
    306         int index = getIndex(key);
    307         //:::获得对应桶内的第一个节点
    308         EntryNode<K,V> firstEntryNode = this.elements[index];
    309 
    310         //:::如果当前桶内不存在任何节点
    311         if(firstEntryNode == null){
    312             return null;
    313         }
    314 
    315         if(firstEntryNode.keyIsEquals(key)){
    316             //:::当前第一个节点的key与之匹配
    317             return firstEntryNode.value;
    318         }else{
    319             //:::获得匹配的目标节点的前一个节点
    320             EntryNode<K,V> targetPreviousNode = getTargetPreviousEntryNode(firstEntryNode,key);
    321             //:::获得匹配的目标节点
    322             EntryNode<K,V> targetNode = targetPreviousNode.next;
    323 
    324             if(targetNode != null){
    325                 return targetNode.value;
    326             }else{
    327                 //:::如果目标节点为空,说明key并不存在于哈希表中
    328                 return null;
    329             }
    330         }
    331     }
    332 
    333     @Override
    334     public boolean containsKey(K key) {
    335         V value = get(key);
    336         return (value != null);
    337     }
    338 
    339     @Override
    340     public boolean containsValue(V value) {
    341         //:::遍历全部桶链表
    342         for (EntryNode<K, V> element : this.elements) {
    343             //:::获得当前桶链表第一个节点
    344             EntryNode<K, V> entryNode = element;
    345 
    346             //:::遍历当前桶链表
    347             while (entryNode != null) {
    348                 //:::如果value匹配
    349                 if (entryNode.value.equals(value)) {
    350                     //:::返回true
    351                     return true;
    352                 } else {
    353                     //:::不匹配,指向下一个节点
    354                     entryNode = entryNode.next;
    355                 }
    356             }
    357         }
    358 
    359         //:::所有的节点都遍历了,没有匹配的value
    360         return false;
    361     }
    362 
    363     @Override
    364     public int size() {
    365         return this.size;
    366     }
    367 
    368     @Override
    369     public boolean isEmpty() {
    370         return (this.size == 0);
    371     }
    372 
    373     @Override
    374     public void clear() {
    375         //:::遍历内部数组,将所有桶链表全部清空
    376         for(int i=0; i<this.elements.length; i++){
    377             this.elements[i] = null;
    378         }
    379 
    380         //:::size设置为0
    381         this.size = 0;
    382     }
    383 
    384     @Override
    385     public Iterator<EntryNode<K,V>> iterator() {
    386         return new Itr();
    387     }
    388 
    389     @Override
    390     public String toString() {
    391         Iterator<EntryNode<K,V>> iterator = this.iterator();
    392 
    393         //:::空容器
    394         if(!iterator.hasNext()){
    395             return "[]";
    396         }
    397 
    398         //:::容器起始使用"["
    399         StringBuilder s = new StringBuilder("[");
    400 
    401         //:::反复迭代
    402         while(true){
    403             //:::获得迭代的当前元素
    404             EntryNode<K,V> data = iterator.next();
    405 
    406             //:::判断当前元素是否是最后一个元素
    407             if(!iterator.hasNext()){
    408                 //:::是最后一个元素,用"]"收尾
    409                 s.append(data).append("]");
    410                 //:::返回 拼接完毕的字符串
    411                 return s.toString();
    412             }else{
    413                 //:::不是最后一个元素
    414                 //:::使用", "分割,拼接到后面
    415                 s.append(data).append(", ");
    416             }
    417         }
    418     }
    419 
    420     /**
    421      * 哈希表 迭代器实现
    422      */
    423     private class Itr implements Iterator<EntryNode<K,V>> {
    424         /**
    425          * 迭代器 当前节点
    426          * */
    427         private EntryNode<K,V> currentNode;
    428 
    429         /**
    430          * 迭代器 下一个节点
    431          * */
    432         private EntryNode<K,V> nextNode;
    433 
    434         /**
    435          * 迭代器 当前内部数组的下标
    436          * */
    437         private int currentIndex;
    438 
    439         /**
    440          * 默认构造方法
    441          * */
    442         private Itr(){
    443             //:::如果当前哈希表为空,直接返回
    444             if(HashMap.this.isEmpty()){
    445                 return;
    446             }
    447             //:::在构造方法中,将迭代器下标移动到第一个有效的节点上
    448 
    449             //:::遍历内部数组,找到第一个不为空的数组插槽slot
    450             for(int i=0; i<HashMap.this.elements.length; i++){
    451                 //:::设置当前index
    452                 this.currentIndex = i;
    453 
    454                 EntryNode<K,V> firstEntryNode = HashMap.this.elements[i];
    455                 //:::找到了第一个不为空的插槽slot
    456                 if(firstEntryNode != null){
    457                     //:::nextNode = 当前插槽第一个节点
    458                     this.nextNode = firstEntryNode;
    459 
    460                     //:::构造方法立即结束
    461                     return;
    462                 }
    463             }
    464         }
    465 
    466         @Override
    467         public boolean hasNext() {
    468             return (this.nextNode != null);
    469         }
    470 
    471         @Override
    472         public EntryNode<K,V> next() {
    473             this.currentNode = this.nextNode;
    474             //:::暂存需要返回的节点
    475             EntryNode<K,V> needReturn = this.nextNode;
    476 
    477             //:::nextNode指向自己的next
    478             this.nextNode = this.nextNode.next;
    479             //:::判断当前nextNode是否为null
    480             if(this.nextNode == null){
    481                 //:::说明当前所在的桶链表已经遍历完毕
    482 
    483                 //:::寻找下一个非空的插槽
    484                 for(int i=this.currentIndex+1; i<HashMap.this.elements.length; i++){
    485                     //:::设置当前index
    486                     this.currentIndex = i;
    487 
    488                     EntryNode<K,V> firstEntryNode = HashMap.this.elements[i];
    489                     //:::找到了后续不为空的插槽slot
    490                     if(firstEntryNode != null){
    491                         //:::nextNode = 当前插槽第一个节点
    492                         this.nextNode = firstEntryNode;
    493                         //:::跳出循环
    494                         break;
    495                     }
    496                 }
    497             }
    498             return needReturn;
    499         }
    500 
    501         @Override
    502         public void remove() {
    503             if(this.currentNode == null){
    504                 throw new IteratorStateErrorException("迭代器状态异常: 可能在一次迭代中进行了多次remove操作");
    505             }
    506 
    507             //:::获得需要被移除的节点的key
    508             K currentKey = this.currentNode.key;
    509             //:::将其从哈希表中移除
    510             HashMap.this.remove(currentKey);
    511 
    512             //:::currentNode设置为null,防止反复调用remove方法
    513             this.currentNode = null;
    514         }
    515     }
    516 }
    View Code

    哈希表简单的测试代码:

     1 public class MapTest {
     2     public static void main(String[] args){
     3         testJDKHashMap();
     4 
     5         System.out.println("=================================================");
     6 
     7         testMyHashMap();
     8     }
     9 
    10     private static void testJDKHashMap(){
    11         java.util.Map<Integer,String> map1 = new java.util.HashMap<>(1,2);
    12         System.out.println(map1.put(1,"aaa"));
    13         System.out.println(map1.put(2,"bbb"));
    14         System.out.println(map1.put(3,"ccc"));
    15         System.out.println(map1.put(1,"aaa"));
    16         System.out.println(map1.put(2,"bbb"));
    17         System.out.println(map1.put(3,"ccc"));
    18         System.out.println(map1.put(1,"111"));
    19         System.out.println(map1.put(3,"aaa"));
    20         System.out.println(map1.put(4,"ddd"));
    21         System.out.println(map1.put(5,"eee"));
    22         System.out.println(map1.put(6,"fff"));
    23         System.out.println(map1.put(8,"ggg"));
    24         System.out.println(map1.put(11,"bbb"));
    25         System.out.println(map1.put(22,"ccc"));
    26         System.out.println(map1.put(33,"111"));
    27         System.out.println(map1.put(9,"111"));
    28         System.out.println(map1.put(10,"111"));
    29         System.out.println(map1.put(12,"111"));
    30         System.out.println(map1.put(13,"111"));
    31         System.out.println(map1.put(14,"111"));
    32 
    33         System.out.println(map1.toString());
    34         System.out.println(map1.containsKey(1));
    35         System.out.println(map1.containsKey(11));
    36         System.out.println(map1.containsValue("bbb"));
    37         System.out.println(map1.containsValue("aaa"));
    38         System.out.println(map1.size());
    39         System.out.println(map1.get(1));
    40         System.out.println(map1.get(2));
    41         System.out.println(map1.get(3));
    42         System.out.println(map1.remove(1));
    43         System.out.println(map1.remove(2));
    44         System.out.println(map1.size());
    45 
    46     }
    47 
    48     private static void testMyHashMap(){
    49         com.xiongyx.datastructures.map.Map<Integer,String> map2 = new com.xiongyx.datastructures.map.HashMap<>(1,2);
    50         System.out.println(map2.put(1,"aaa"));
    51         System.out.println(map2.put(2,"bbb"));
    52         System.out.println(map2.put(3,"ccc"));
    53         System.out.println(map2.put(1,"aaa"));
    54         System.out.println(map2.put(2,"bbb"));
    55         System.out.println(map2.put(3,"ccc"));
    56         System.out.println(map2.put(1,"111"));
    57         System.out.println(map2.put(3,"aaa"));
    58         System.out.println(map2.put(4,"ddd"));
    59         System.out.println(map2.put(5,"eee"));
    60         System.out.println(map2.put(6,"fff"));
    61         System.out.println(map2.put(8,"ggg"));
    62         System.out.println(map2.put(11,"bbb"));
    63         System.out.println(map2.put(22,"ccc"));
    64         System.out.println(map2.put(33,"111"));
    65         System.out.println(map2.put(9,"111"));
    66         System.out.println(map2.put(10,"111"));
    67         System.out.println(map2.put(12,"111"));
    68         System.out.println(map2.put(13,"111"));
    69         System.out.println(map2.put(14,"111"));
    70 
    71         System.out.println(map2.toString());
    72         System.out.println(map2.containsKey(1));
    73         System.out.println(map2.containsKey(11));
    74         System.out.println(map2.containsValue("bbb"));
    75         System.out.println(map2.containsValue("aaa"));
    76         System.out.println(map2.size());
    77         System.out.println(map2.get(1));
    78         System.out.println(map2.get(2));
    79         System.out.println(map2.get(3));
    80         System.out.println(map2.remove(1));
    81         System.out.println(map2.remove(2));
    82         System.out.println(map2.size());
    83     }
    84 }
    View Code

      我们的哈希表实现是demo级别的,功能简单,也比较好理解,希望这能够成为大家理解更加复杂的产品级哈希表实现的一个跳板。在理解了demo级别代码的基础之上,去阅读更加复杂的产品级实现代码,更好的理解哈希表,更好的理解自己所使用的数据结构,写出更高效,易维护的程序。

      本系列博客的代码在我的 github上:https://github.com/1399852153/DataStructures ,存在许多不足之处,请多多指教。

  • 相关阅读:
    巩固基础前台
    super 、static、final关键字加深记忆哦!还有父子类构造函数调用问题
    java异常了解
    spring粗略整体认识
    java枚举新认识
    泛型集合注意事项
    java反射基础
    对java集合类的认识——基础很重要
    多线程(C++ And POSIX)
    v2代理原理,应用
  • 原文地址:https://www.cnblogs.com/xiaoxiongcanguan/p/10190861.html
Copyright © 2020-2023  润新知