HashMap源代码学习笔记

HashMap的底层主要是基于数组和链表来实现的,它之所以有相当快的查询速度主要是由于它是通过计算散列码来决定存储的位置。

HashMap中主要是通过key的hashCode来计算hash值的。仅仅要hashCode同样。计算出来的hash值就一样。假设存储的对象对多了,就有可能不同的对象所算出来的hash值是同样的,这就出现了所谓的hash冲突。

学过数据结构的同学都知道。解决hash冲突的方法有非常多,HashMap底层是通过链表来解决hash冲突的。

HashMap事实上就是一个Entry数组。Entry对象中包括了键和值,当中next也是一个Entry对象。它就是用来处理hash冲突的。形成一个链表。数组的每一个元素都是一个单链表的头节点,链表是用来解决冲突的。假设不同的key映射到了数组的同一位置处,就将其放入单链表中。

Hashtable 与 HashMap类似,可是主要有6点不同。

1.HashTable的方法是同步的,HashMap未经同步,所以在多线程场合要手动同步HashMap这个差别就像Vector和ArrayList一样。

2.HashTable不同意null值,key和value都不能够,HashMap同意null值,key和value都能够。HashMap同意key值仅仅能由一个null值,由于hashmap假设key值同样。新的key,
value将替代旧的。

3.HashTable有一个contains(Object value)功能和containsValue(Object value)功能一样。

4.HashTable使用Enumeration,HashMap使用Iterator。

5.HashTable中hash数组默认大小是11,添加的方式是 old*2+1。HashMap中hash数组的默认大小是16,并且一定是2的指数。

1、HashTable的方法是同步的,HashMap未经同步

Hashtable 提供的几个主要方法,包含 get(), put(), remove() 等。每一个方法本身都是 synchronized 的,会对整个对象进行加锁操作,不会出现两个线程同一时候对数据进行操作的情况。因此保证了线程安全性,可是也大大的减少了运行效率。

2、HashMap中hash数组的默认大小是16,并且一定是2的指数

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

将key的二次hash值。与长度减一进行与操作,这一步可谓经典,通常我们会用取模的方式来定位数组中的某个位置,我们首先想到的就是把hashcode对数组长度取模运算,这样一来,元素的分布相对来说是比較均匀的。

可是,“模”运算的消耗还是比較大的。能不能找一种更高速,消耗更小的方式那?hashMap用这样的方法,并且length即capacity的值。面capacity又是2的倍数,减1之后。表示成二进制就所有是1了。那么与所有为1的一个数进行与操作,速度会大大提升了。这就是为什么"capacity的值是2的倍数"

3、HashMap解决冲突的办法

1)再哈希法

2)拉链发

  1. public V put(K key, V value) {
  2. if (key == null)
  3. return putForNullKey(value);
  4. int hash = hash(key.hashCode());
  5. int i = indexFor(hash, table.length);
  6. for (Entry<K,V> e = table[i]; e != null; e = e.next) {
  7. Object k;
  8. if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
  9. V oldValue = e.value;
  10. e.value = value;
  11. e.recordAccess(this);
  12. return oldValue;
  13. }
  14. }
  15. modCount++;
  16. addEntry(hash, key, value, i);
  17. return null;
  18. }

依据key的hash值得到这个元素在数组中的位置(即下标),然后就能够把这个元素放到相应的位置中了。假设这个元素所在的位子上已经存放有其它元素了,那么在同一个位子上的元素将以链表的形式存放,新增加的放在链头。最先增加的放在链尾。从hashmap中get元素时。首先计算key的hashcode,找到数组中相应位置的某一元素,然后通过key的equals方法在相应位置的链表中找到须要的元素。

调用int hash = hash(key.hashCode());这是hashmap的一个自己定义的hash,在key.hashCode()基础上进行二次hash。再哈希法可以解决开放地址法的聚集现象,可是却添加了时间复杂度。

  1. static int hash(int h) {
  2. h ^= (h >>> 20) ^ (h >>> 12);
  3. return h ^ (h >>> 7) ^ (h >>> 4);
  4. }

改进传统的hash方法,并且尽量保证key的每一位都会影响到最后的hash值。以达到降低hash冲突的目的.

4、HashMap的构造函数

  1. if (initialCapacity < 0)
  2. throw new IllegalArgumentException("Illegal initial capacity: " +
  3. initialCapacity);
  4. if (initialCapacity > MAXIMUM_CAPACITY)
  5. initialCapacity = MAXIMUM_CAPACITY;
  6. if (loadFactor <= 0 || Float.isNaN(loadFactor))
  7. throw new IllegalArgumentException("Illegal load factor: " +
  8. loadFactor);
  9. // Find a power of 2 >= initialCapacity
  10. int capacity = 1;
  11. while (capacity < initialCapacity)
  12. capacity <<= 1;
  13. this.loadFactor = loadFactor;
  14. threshold = (int)(capacity * loadFactor);
  15. table = new Entry[capacity];
  16. init();
  17. }

loadFactor :载入因子,载入因子与HashMap resize有关。

默觉得0.75

capacity:容器大小。默认值为16 其大小为上面所说的数据结构中数组的长度。

table:即为上面数据结构图中。X方向的数组(transient Entry[] table;)

threshold :resize的临界值,即当HashMap中无素个数达到该值时,HashMap就会调用其resize方法。又一次扩充大小。

while (capacity < initialCapacity)

capacity <<= 1;

这段代码说明什么:capacity的值是2的倍数,即使设置初始值是1000,hashmap也自己主动会将其设置为1024。

5、哈希表解决冲突的经常用法

1)开放地址法

2)拉链法

3)再哈希法

拉链法的长处 ,与开放定址法相比,拉链法有例如以下几个长处:

①拉链法处理冲突简单。且无堆积现象。即非同义词决不会发生冲突。因此平均查找长度较短。

②因为拉链法中各链表上的结点空间是动态申请的,故它更适合于造表前无法确定表长的情况;

③开放定址法为降低冲突,要求装填因子α较小。故当结点规模较大时会浪费非常多空间。

而拉链法中可取α≥1,且结点较大时,拉链法中添加的指针域可忽略不计,因此节省空间;

④在用拉链法构造的散列表中,删除结点的操作易于实现。仅仅要简单地删去链表上对应的结点就可以。而对开放地址法构造的散列表,删除结点不能简单地将被删结 点的空间置为空,否则将截断在它之后填人散列表的同义词结点的查找路径。这是由于各种开放地址法中,空地址单元(即开放地址)都是查找失败的条件。因此在
用开放地址法处理冲突的散列表上运行删除操作,仅仅能在被删结点上做删除标记。而不能真正删除结点。

拉链法的缺点

拉链法的缺点是:指针须要额外的空间。故当结点规模较小时,开放定址法较为节省空间,而若将节省的指

时间: 2024-11-08 23:43:08

HashMap源代码学习笔记的相关文章

[Java] Map / HashMap - 源代码学习笔记

Map 1. 用于关联 key 和 value 的对象,其中 key 与 key 之间不能重复. 2. 是一个接口,用来代替 Java 早期版本中的 Dictionary 抽象类. 3. 提供三种不同的视图用于观察内部数据,key 的 Set 视图.value 的 Collection 视图,key-value 关联对象的 Set 视图. 4. 有些实现会保证元素的顺序,例如 TreeMap.有些则不会保证,例如 HashMap 5. 如果 key 是可变对象,需要小心处理 6. key 值指向

[Java] LinkedList / Queue - 源代码学习笔记

简单地画了下 LinkedList 的继承关系,如下图.只是画了关注的部分,并不是完整的关系图.本博文涉及的是 Queue, Deque, LinkedList 的源代码阅读笔记.关于 List 接口的笔记,可以参考上一篇博文 List / ArrayList - 源代码学习笔记 Queue 1. 继承 Collection 接口,并提供了额外的插入.提取和查看元素的方法.新增的方法都有两种形式:当操作失败时,抛出异常或者返回一个特殊值.特殊值可以是 null 或者 false ,这取决于方法本

[Java] List / ArrayList 源代码学习笔记

在阅读 List / ArrayList 源代码过程中,做了下面的笔记. LinkedList 待更新. List List 是一个接口,继承自 Collection 接口.接口是对功能的定义,没有具体实现.List 接口有以下几个特点 1. 可以存在重复的元素.这点和 Set 是不一样的,Set 接口不允许重复的元素出现. 2. 有四个根据下标访问的方法 : get(int), set(int, E), add(int, E), remove(int). 3. 提供特殊的 Iterator —

jQuery源代码学习笔记:jQuery.fn.init(selector,context,rootjQuery)代码具体解释

3.1 源代码 init: function( selector, context, rootjQuery ) { var match, elem, ret, doc; // Handle $(""), $(null), or $(undefined) //假设selector为空格.!selector为false if (!selector) { //此时this为空jQuery对象 return this; } // Handle $(DOMElement) //nodeType节

Mono源代码学习笔记:Console类(四)

NullStream 类 (internal class) 下面就是 mcs/class/corlib/System.IO/NullStream.cs: 01: namespace System.IO 02: { 03: class NullStream : Stream 04: { 05: public override bool CanRead { get { return true; } } 06: public override bool CanSeek { get { return t

[Java] TreeMap - 源代码学习笔记

TreeMap 实现了 SortedMap 和 NavigableMap 接口,所有本文还会记录 SortedMap 和 NavigableMap 的阅读笔记. SortedMap 1. 排序的比较应该和 equals(Object) 保持一致 2. 应该提供四种“标准”的构造器 1). 无参构造器 2). 带一个 Comparator 为参数的构造器 3). 带一个 Map 为参数的构造器 4). 带一个 SortedMap 为参数的构造器 3.  subMap ,  headMap ,  t

Android源代码学习笔记:android-Ultra-Pull-To-Refresh-master

学习要点:下拉刷新 这个小应用包含了在使用到GridView,FrameLayout,TextView,ListView等等控件时的所有下拉刷新效果下拉刷新具有极为强大的使用空间,几乎所有的应用都会用到. 源码解析文档:http://a.codekk.com/detail/Android/Grumoon/android-Ultra-Pull-To-Refresh%20%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90 public interface PtrHandler

Mono源代码学习笔记:Console类(一)

前言 我们知道,Mono 是 .NET Framework 跨平台的开源实现.Mono 的源代码就是金矿,等待我们去挖掘. 目前 Mono 的最新版本是 Mono 2.8.2,可以到 http://ftp.novell.com/pub/mono/sources/mono/ 下载 mono-2.8.2.tar.bz2,文件大小是30MB.可以参阅"在 Ubuntu 10.10 操作系统安装 Mono 2.8.2"一文. 现在,让我们来看看 Mono 是如何实现 .NET Framewor

Mono源代码学习笔记:Console类(六)

Unix 终端的基础知识 许多 Unix 系统使用终端.但是在今天的许多情况下,终端也许是一个运行终端程序的 PC 机.从历史上来说,不同的生产商提供了大量的硬件终端.Linux 操作系统包含一个环境变量 TERM,用来表示我们正在使用的终端的类型,如下所示: [email protected]:~$ w 16:35:13 up 6 days, 7:36, 2 users, load average: 0.62, 0.34, 0.25 USER TTY FROM [email protected