`

从map开始说起一(仔细学习Hashmap)

    博客分类:
  • java
 
阅读更多

   提起map,这个java中collection家族中的典范,特别是hashmap更是大家耳熟能详的工具类,下面就细细的看看

public class HashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable
{

    /**
     * The default initial capacity - MUST be a power of two.
     */
    static final int DEFAULT_INITIAL_CAPACITY = 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.
     */
    static final int MAXIMUM_CAPACITY = 1 << 30;

    /**
     * The load factor used when none specified in constructor.
     */
    static final float DEFAULT_LOAD_FACTOR = 0.75f;

    /**
     * The table, resized as necessary. Length MUST Always be a power of two.
     */
    transient Entry[] table;

    /**
     * The number of key-value mappings contained in this map.
     */
   transient int size;
    /**
     * The next size value at which to resize (capacity * load factor).
     * @serial
     */
    int threshold;
    /**
     * The load factor for the hash table.
     *
     * @serial
     */
    final float loadFactor;
    /**
     * The number of times this HashMap has been structurally modified
     * Structural modifications are those that change the number of mappings in
     * the HashMap or otherwise modify its internal structure (e.g.,
     * rehash).  This field is used to make iterators on Collection-views of
     * the HashMap fail-fast.  (See ConcurrentModificationException).
     */
    transient volatile int modCount;

 

 

这是Hashmap中的实例变量(jdk1.6),其中大家可以看到hashmap的实质其实是一个transient Entry[] table数组,另外像DEFAULT_INITIAL_CAPACITY(默认容量),MAXIMUM_CAPACITY最大容量(2^30),DEFAULT_LOAD_FACTOR默认装载因子(0.75),这些都是staic final的,用来当做默认配置和检查边界的,另外可以设置的3个也是我们一般传进去的参数,size(这个是记录map内存了多少对数据的),threshold,loadFactor,分别是边界值,加载因子,关系就是threshold=a*loadFactor,意思就是你的table初始化为a大小,当你不断添加内容到了threshold大小时,table就要自动加倍了。

 

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();
    }

 这个就是我们常用的构造函数,基本上就是对初始容量大小,loadFactor的检查,以及最关键的table = new Entry[capacity];,其中capacity并不是我们制定多少,他就是多少,实际上他选择了刚好小于输入initialCapacity的2的倍数作为大小,

 

 

 public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }

 这就是著名的put函数了,注意这里可以明显看到hashmap不是同步的,以及他可以接受空值为键,此外大家也可以明显看到一个良好的key的hashcode()还是很必要的,如果设置了一个垃圾的hashcode()函数,那么

    static int hash(int h) {
        // This function ensures that hashCodes that differ only by
        // constant multiples at each bit position have a bounded
        // number of collisions (approximately 8 at default load factor).
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }

 即便HashMap的hash函数也无能为力了,当得到处理过的32位hash码后,还要继续处理得到table[]的index

    static int indexFor(int h, int length) {
        return h & (length-1);
    }

 很简单的一个函数,利用table的长度很好的截出了适当的大小,然后就是利用index在table中开始找key了,很明显这个就是直到找到为空或者找到"相同的"key,然后覆盖,如果没有调用addEntry

  for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }

        modCount++;
        addEntry(hash, key, value, i);
        return null;

 

    void addEntry(int hash, K key, V value, int bucketIndex) {
	Entry<K,V> e = table[bucketIndex];
        table[bucketIndex] = new Entry<K,V>(hash, key, value, e);
        if (size++ >= threshold)
            resize(2 * table.length);
    }

 这里很明显,就看到了每当新加一个Entry时,size++,并且如果size>threshold(就是前面两者相乘的结果),则table长度翻倍。

 

get基本上类似,就不细讲了,下来看看hashmap中很实用的keySet(),

    public Set<K> keySet() {
        Set<K> ks = keySet;
        return (ks != null ? ks : (keySet = new KeySet()));
    }

    private final class KeySet extends AbstractSet<K> {
        public Iterator<K> iterator() {
            return newKeyIterator();
        }
        public int size() {
            return size;
        }
        public boolean contains(Object o) {
            return containsKey(o);
        }
        public boolean remove(Object o) {
            return HashMap.this.removeEntryForKey(o) != null;
        }
        public void clear() {
            HashMap.this.clear();
        }
    }

 明显keyset()返回的是一个内部类实现了AbstractSet,而这个内部类中实际上的每一个set操作,都直接影响着Haspmap中的数据。

 

  在这个内部类中我们还能看到一个非常多见的方法public Iterator<K> iterator(),这个方法伴随在collection的每一个角落

 

 private abstract class HashIterator<E> implements Iterator<E> {
        Entry<K,V> next;	// next entry to return
        int expectedModCount;	// For fast-fail
        int index;		// current slot
        Entry<K,V> current;	// current entry

        HashIterator() {
            expectedModCount = modCount;
            if (size > 0) { // advance to first entry
                Entry[] t = table;
                while (index < t.length && (next = t[index++]) == null)
                    ;
            }
        }

        public final boolean hasNext() {
            return next != null;
        }

        final Entry<K,V> nextEntry() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            Entry<K,V> e = next;
            if (e == null)
                throw new NoSuchElementException();

            if ((next = e.next) == null) {
                Entry[] t = table;
                while (index < t.length && (next = t[index++]) == null)
                    ;
            }
	    current = e;
            return e;
        }

        public void remove() {
            if (current == null)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            Object k = current.key;
            current = null;
            HashMap.this.removeEntryForKey(k);
            expectedModCount = modCount;
        }

    }

 

这同样是Hashmap中的一个内部类,而且这还是一个实现Iterator的抽象类,在hashmap中有3个用来对keyset,value,和entry返回iterator的内部类均是继承HashIterator,而且很明显的可以看到对iterator的任何改变都会带来Hashmap的改变,特别要注意的是 expectedModCount = modCount;

               if (modCount != expectedModCount)
                throw new ConcurrentModificationException();

这里实际上是保证了在iterator遍历Hashmap过程中对Hashmap的改变(增加和删除均会带来modCount的增加,可以看前面的put函数),均会导致iterator扔出异常,但仔细看put函数,我们又会发现如果只是value的更替,而不是新加,modCount 不会发生变化。

    Iterator<K> newKeyIterator()   {
        return new KeyIterator();
    }
    Iterator<V> newValueIterator()   {
        return new ValueIterator();
    }
    Iterator<Map.Entry<K,V>> newEntryIterator()   {
        return new EntryIterator();
    }

 


因为HashIterator实现的很好了,故每个自己的iterator就实现的很简单了

    private final class KeyIterator extends HashIterator<K> {
        public K next() {
            return nextEntry().getKey();
        }
    }

 
好了Hashmap就大概讲这么多,明天再从Hashmap铺开比较更多的map

分享到:
评论

相关推荐

    易语言HashMap类

    易语言HashMap类是一种在易语言编程环境中实现的高效数据结构,它主要用于存储键值对(key-value pairs),提供快速的数据存取。HashMap类基于哈希表(Hash Table)原理,通过计算键的散列值来确定数据在内存中的...

    Java里多个Map的性能比较(TreeMap、HashMap、ConcurrentSkipListMap)

    在Java编程中,Map接口是用于存储键值对的数据结构,而Java提供了多种Map的实现,包括TreeMap、HashMap和ConcurrentSkipListMap。本文主要比较了这三种Map的性能,尤其是在插入和查找操作上的效率。 1. **TreeMap**...

    自定义map实现java的hashmap

    在Java编程中,HashMap是一个非常重要的数据结构,它实现了Map接口,提供了键值对的存储功能,具有快速存取和高效查找的特点。HashMap基于哈希表(也称为散列表)原理,通过键对象的哈希码来定位元素,进而实现O(1)...

    Map与HashMap

    《java编程思想》,Map结合HashMap获取键相关联的值

    List、ArrayList、Vector及map、HashTable、HashMap分别的区别

    Hashtable和HashMap的区别在于,Hashtable是一个同步的Map实现类,而HashMap是一个非同步的Map实现类。因为同步需要花费机器时间,所以Hashtable的执行效率要低于HashMap。 Collection框架是Java容器类的基础,它...

    Map,HashMap,TreeMap的使用

    Java 中的 Map、HashMap、TreeMap 使用详解 Map 是 Java 集合框架中的一个接口,用于存储键值对,根据键可以获取值。Map 中的键不允许重复,但值可以重复。在 Java 中,HashMap、LinkedHashMap、TreeMap 都实现了 ...

    基于共享内存的hashMap及STL

    从一个公司的项目中提取的一个基于共享内存的hashMap,vector,list等的相关实现,应用与游戏服务器的数据保存与访问

    用HashMap模拟一个网上购物车

    3. 编写主程序`Hash_Map`,从键盘接收五本书的信息,并将这些信息存储到`HashMap`中。 4. 实现`getSum`方法,遍历`HashMap`,计算所有书籍的总价。 5. 在主程序中调用`getSum`方法,并打印结果。 #### 四、代码实现...

    Hashmap 通过对VALUE排序 源代码

    HashMap是Java编程语言中最常用的集合类之一,它提供了一种基于键值对(key-value pair)的数据存储方式,允许我们通过键快速查找对应的值。在Java的HashMap中,元素是无序的,也就是说,它们在内存中的存储位置并...

    java Pojo转Map

    Map接口则是Java集合框架的一部分,它提供了键值对的数据存储方式,方便数据的存取。将Pojo对象转换为Map,可以简化数据处理过程,尤其是在JSP页面上展示数据时,Map的灵活性更加突出。本文将详细介绍如何实现Java中...

    HashMap通过VALUE反向求KEY的方法

    首先,创建一个名为`Map_ValueGetKey`的类,并实例化一个HashMap对象`map`。然后定义一个`getKey`方法,该方法接受一个值作为参数,其目的是找到与该值相匹配的所有键。 方法的核心在于调用`entrySet()`方法,它...

    马士兵老师HashMap学习笔记

    马士兵老师的HashMap学习笔记深入剖析了这一核心组件的工作原理,旨在帮助开发者更深入地理解其内部机制。本文将结合马士兵老师的讲解,详细阐述HashMap在不同JDK版本中的put操作流程,以及可能遇到的死循环问题。 ...

    怎样遍历一个HashMap?

    可以通过2种方法遍历HashMap &lt;br&gt;Map map = new HashMap(); &lt;br&gt;for (Iterator iter = map.entrySet().iterator(); iter.hasNext();) { &lt;br&gt; Map.Entry entry = (Map.Entry) iter.next(); &lt;br&gt; Object ...

    一个基于js的HashMap

    在JavaScript中,HashMap是一种数据结构,它允许我们通过键(key)来存储和检索值(value),类似于对象,但提供了一种更高效的方式来处理大量数据。JavaScript原生并不支持HashMap,但开发者可以通过自定义类来实现...

    JNI处理hashmap,string等对象的操作

    JNI,全称Java Native Interface,是Java平台标准的一部分,它允许Java代码和其他语言写的代码进行交互。JNI在很多场景下都是必要的,比如调用操作系统本地库、加速性能关键的代码或者实现与硬件设备的直接通信。在...

    HashMap排序

    在Java开发中,`HashMap`是一种非常常见的数据结构,它通过键值对的形式存储数据。然而,由于`HashMap`是基于哈希表实现的,所以它并不能保证元素的顺序。这就意味着如果需要按照某种特定顺序来处理`HashMap`中的...

    hashmap面试题_hashmap_

    本篇将围绕HashMap的相关面试题,从基础概念到高级应用进行详尽解析。 一、HashMap概述 HashMap是一个基于哈希表实现的键值对存储结构,它提供了快速的插入、删除和查找操作,平均时间复杂度为O(1)。HashMap非线程...

    HashMap与HashTable和HashSet的区别

    `HashMap`也是基于哈希表实现的一个`Map`容器,但它是非线程安全的。与`HashTable`相比,`HashMap`最大的不同之处在于它允许`key`和`value`为`null`。 **特点:** - **线程安全性**:`HashMap`不是线程安全的,...

    Hash Map for geeks_hashmap_Geeks_源码

    标题中的“Hash Map for geeks_hashmap_Geeks_源码”显然指的是一个关于哈希映射(HashMap)的学习资源,特别针对极客和普通程序员。哈希映射是一种数据结构,它允许我们以O(1)的时间复杂度进行插入、删除和查找操作...

    深入Java集合学习系列:HashMap的实现原理

    HashMap是一个基于哈希表的数据结构,它实现了Map接口,提供了快速的存取功能。本文将深入探讨HashMap的内部机制,包括其构造、工作原理、哈希函数、冲突解决策略以及扩容机制。 首先,HashMap的基本结构是由数组...

Global site tag (gtag.js) - Google Analytics