前言:第一次写学习贴,看了很多的帖子,然后也去看了hashMap的源码,想尝试自己去写一下自己对于这个东西的理解,并不一定是对的,可能也就是写出来让自己加深一下基础印象,如果有些的不好的地方请指出。
此博客应该是以JDK1.7为基础的,但是我是以1.8来说的不要混淆了(只因为我懒得打开1.8的源码)
1.HashMap是什么:让我们重新认识一下这个小哥儿
HashMap是java中AbstractMap的子类,是键值对集合java.util.Map的实现类,是作为Map家族中被使用频率最高的小哥哥
HashMap是java面试时基础中被询问频率极高的你不得不懂得面试题1.2.3
2.HashMap基础了解:HashMap家中拥有着好几个兄弟姐妹,但是咱们只是来了解它的,就不多介绍了,先说的感觉最基础的
HashMap是一个无序的键值对集合,是以Key-Value的形式存储数据的,它只允许一个Key为null值,允许多个value为null值
HashMap是一个非线程安全的键值对集合,即当你使用多线程去操作它时,他可能会出现数据不一致,但是如果你需要它达到线程安全的条件时他也是可以做到的,可以用 Collections(肯来克辛斯)的synchronizedMap(神奎耐日德Map
)方法使HashMap具有线程安全的能力,或是选择使用ConcurrentHashMap(肯卡韧特HashMap
)这样一个线程安全的Map集合达到目的。
最开始HashMap底层是由数组加链表来实现的,但是在JDK1.8以后在原有的基础数引入了红黑树这样一个二叉树结构的树来优化HashMap
3.让我们去源码层参观一下HashMap
HashMap的默认长度
/** * The default initial capacity - MUST be a power of two. */ static final int DEFAULT_INITIAL_CAPACITY = 16;
HashMap的默认负载因子
/** * The load factor used when none specified in constructor. */ static final float DEFAULT_LOAD_FACTOR = 0.75f;
HashMap的最大长度
/** * 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;
HashMap中修改map长度和负载因子的构造函数(中文注释是我自己加的,个人理解)
--initialCapacity:长度
--loadFactor :负载因子
public HashMap(int initialCapacity, float loadFactor) { /*如果初始化的长度小于0则抛出异常*/ if (initialCapacity < 0) throw new IllegalArgumentException("Illegal initial capacity: " + initialCapacity); /*如果初始化的长度大于最大值常量那么就以最大值常量为默认长度*/ if (initialCapacity > MAXIMUM_CAPACITY) initialCapacity = MAXIMUM_CAPACITY; /*如果初始化负载因子小于等于0或则不是一个数字那么抛出异常*/ if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException("Illegal load factor: " + loadFactor); // Find a power of 2 >= initialCapacity /*这里的算法我开始没看懂,然后我把代码拿出来测试了一下,我觉得应该是求比你初始化长度更大的一个2的平方根的值 比如我initialCapacity初始化值是15那么capacity的值就是16,如果我的值>16且小于16X2 那么他的值就一定是32(推测的,未经 测试)之所以这么做这么个算法的原因是因为它本身对于Hash冲突的算法的原因,如果后面有聊到这个我们在解释它本身的hash算法 */ int capacity = 1; while (capacity < initialCapacity) capacity <<= 1; /*赋值这个就不需要我多解释了吧*/ this.loadFactor = loadFactor; /*我觉得这里应该是求一个阈值,就是达到这个值以后map就会进行扩容,threshold是一个全局的变量*/ threshold = (int)Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1); /*看到new关键字就不提了吧,初始化一个Entry的素组长度为capacity*/ table = new Entry[capacity]; useAltHashing = sun.misc.VM.isBooted() && (capacity >= Holder.ALTERNATIVE_HASHING_THRESHOLD); init(); }
到此这个构造函数就结束了,去看看其他的
发现一个get方法,让我们来看看
public V get(Object key) { /*这行代码证明HashMap是可以有null的Key的*/ if (key == null) return getForNullKey(); /*对象获取,源码往下*/ Entry<K,V> entry = getEntry(key); /*这个对象不为空就返回这个对象得value值*/ return null == entry ? null : entry.getValue(); }
--获取HashMap中key值为空的value源码
private V getForNullKey() { /*循环取HashMap*/ for (Entry<K,V> e = table[0]; e != null; e = e.next) { /*如果这个key值==null就返回这个key值得value值,最后则返回null*/ if (e.key == null) return e.value; } return null; }
--获取HashMap中key值对应得Value源码
final Entry<K,V> getEntry(Object key) { /*运用三目表达式获取到这个key值得hash码*/ int hash = (key == null) ? 0 : hash(key); /*根据hash码进行运算得到key值对应的对象在数组中的坐标,最后返回对象*/ 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 != null && key.equals(k)))) return e; } return null; }
--hash码获取的源码(这里的算法对我来说太高级了不解释)
final int hash(Object k) { int h = 0; if (useAltHashing) { if (k instanceof String) { return sun.misc.Hashing.stringHash32((String) k); } h = hashSeed; } h ^= k.hashCode(); // 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); }
再看下put方法
public V put(K key, V value) { /*同理于上面得get空值的key,HashMap是允许一个主键为空的*/ if (key == null) return putForNullKey(value); /*获取hash码*/ int hash = hash(key); /*通过算法获取坐标*/ int i = indexFor(hash, table.length); /*这个Entry我觉得有必要解释一波,这是一个实现了Map.Entrty的链表结构对象,上文也说了Hash底层是数组+链表+红黑树实现的(1.8 才有的红黑树,我这里是1.7的源码)*/ for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; /*如果put的key值存在*/ if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { /*进行覆盖,然后返回旧的Value*/ V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } /*如果key值不存在走这里*/ modCount++; addEntry(hash, key, value, i); return null; }
--源码addEntry
void addEntry(int hash, K key, V value, int bucketIndex) { /*判断是否需要扩容*/ if ((size >= threshold) && (null != table[bucketIndex])) { /*每次扩容都会是之前长度X2*/ resize(2 * table.length); /*获取key值hash码*/ hash = (null != key) ? hash(key) : 0; /*对hash码进行算法运算以后得到坐标*/ bucketIndex = indexFor(hash, table.length); } /*新增*/ createEntry(hash, key, value, bucketIndex); }
/*这里的代码就是一个新的对象添加到了HashMap的数组中,然后数组长度++*/
void createEntry(int hash, K key, V value, int bucketIndex) { Entry<K,V> e = table[bucketIndex]; table[bucketIndex] = new Entry<>(hash, key, value, e); size++; }
今天就到这里吧,有什么问题欢迎指出
外贴上我看的两篇对于HashMap写的比较好的(1.8)帖子以供大家共同学习
大家回见