忽然发现整理了不少笔记,应该放这里作备用java
主要区别:线程安全性,同步(synchronization),以及速度。算法
HashMap几乎能够等价于Hashtable,除了HashMap是非synchronized的,并能够接受null。Hashtable是线程安全的,多个线程能够共享一个Hashtable。数组
HashMap的同步问题可经过Collections的一个静态方法获得解决,Map Collections.synchronizedMap(Map m)
返回一个同步的Map。安全
HashMap的迭代器(Iterator)是fail-fast迭代器,而Hashtable的enumerator迭代器不是fail-fast的。fail-fast结构上更改时(删除或者插入一个元素),将会抛出ConcurrentModificationException异常。数据结构
HashMap不能保证随着时间的推移Map中的元素次序是不变的。多线程
HashSet实现了Set接口,它不容许集合中有重复的值,当咱们提到HashSet时,第一件事情就是在将对象存储在HashSet以前,要先确保对象重写equals()和hashCode()方法,这样才能比较对象的值是否相等,以确保set中没有储存相等的对象。若是咱们没有重写这两个方法,将会使用这个方法的默认实现。并发
Map中不容许重复的键。Map接口有两个基本的实现,HashMap和TreeMap。TreeMap保存了对象的排列次序,而HashMap则不能。HashMap容许键和值为null。性能
HashSet 和 HashMap 之间有不少类似之处,对于 HashSet 而言,系统采用 Hash 算法决定集合元素的存储位置,这样能够保证能快速存、取集合元素;对于 HashMap 而言,系统 key-value 当成一个总体进行处理,系统老是根据 Hash 算法来计算 key-value 的存储位置,这样能够保证能快速存、取 Map 的 key-value 对。优化
public V put(K key, V value) { // 若是 key 为 null,调用 putForNullKey 方法进行处理 if (key == null) return putForNullKey(value); // 根据 key 的 keyCode 计算 Hash 值 int hash = hash(key.hashCode()); // 搜索指定 hash 值在对应 table 中的索引 int i = indexFor(hash, table.length); // 若是 i 索引处的 Entry 不为 null,经过循环不断遍历 e 元素的下一个元素 for (Entry<K,V> e = table[i]; e != null; e = e.next) { Object k; // 找到指定 key 与须要放入的 key 相等(hash 值相同 // 经过 equals 比较放回 true) if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } // 若是 i 索引处的 Entry 为 null,代表此处尚未 Entry modCount++; // 将 key、value 添加到 i 索引处 addEntry(hash, key, value, i); return null; }
Map.Entry,每一个 Map.Entry 其实就是一个 key-value 对。this
当系统决定存储 HashMap 中的 key-value 对时,彻底没有考虑 Entry 中的 value,仅仅只是根据 key 来计算并决定每一个 Entry 的存储位置。能够把 Map 集合中的 value 当成 key 的附属。
indexFor(int h, int length) 方法来计算该对象应该保存在 table 数组的哪一个索引处。
根据上面 put 方法的源代码能够看出,当程序试图将一个 key-value 对放入 HashMap 中时,程序首先根据该 key 的 hashCode() 返回值决定该 Entry 的存储位置:若是两个 Entry 的 key 的 hashCode() 返回值相同,那它们的存储位置相同。若是这两个 Entry 的 key 经过 equals 比较返回 true,新添加 Entry 的 value 将覆盖集合中原有 Entry 的 value,但 key 不会覆盖。若是这两个 Entry 的 key 经过 equals 比较返回 false,新添加的 Entry 将与集合中原有 Entry 造成 Entry 链,并且新添加的 Entry 位于 Entry 链的头部。
Map提供了一些经常使用方法,如keySet()、entrySet()等方法,keySet()方法返回值是Map中key值的集合;entrySet()的返回值也是返回一个Set集合,此集合的类型为Map.Entry,接口中有getKey()、getValue方法。
HashMap其实是一个“链表散列”的数据结构,即数组和链表的结构,可是在jdk1.8里 ,加入了红黑树的实现,当链表的长度大于8时,转换为红黑树的结构。
少于8个的时候,Java中HashMap采用了链地址法。
经过什么方式来控制map使得Hash碰撞的几率又小,哈希桶数组(Node[] table)占用空间又少呢?答案就是好的Hash算法和扩容机制。即便负载因子和Hash算法设计的再合理,也免不了会出现拉链过长的状况,一旦出现拉链过长,则会严重影响HashMap的性能。
而当链表长度太长(默认超过8)时,链表就转换为红黑树,利用红黑树快速增删改查的特色提升HashMap的性能。
indexFor方法通常想到的是把hash值对数组长度取模运算,但运算较大,所以1.7中使用如下方法,&比%具备更高的效率:
static int indexFor(int h, int length) { return h & (length-1); //第三步 取模运算 }
在JDK1.8的实现中,优化了高位运算的算法:(h = k.hashCode()) ^ (h >>> 16)
红黑树(Red Black Tree) 是一种自平衡二叉查找树。红黑树和AVL树同样都对插入时间、删除时间和查找时间提供了最好可能的最坏状况担保。除了O(log n)的时间以外,红黑树的持久版本对每次插入或删除须要O(log n)的空间。
两个obj,若是equals()相等,hashCode()必定相等。
两个obj,若是hashCode()相等,equals()不必定相等(Hash散列值有冲突的状况,虽然几率很低)。
equals()和hashcode()这两个方法都是从object类中继承过来的。equals()是对两个对象的地址值进行的比较(即比较引用是否相同),hashCode()是一个本地方法,它的实现是根据本地机器相关的。
JVM每new一个Object,它都会将这个Object丢到一个Hash哈希表中去,这样的话,下次作Object的比较或者取这个对象的时候,它会根据对象的hashcode再从Hash表中取这个对象。这样作的目的是提升取对象的效率。
若是不一样的对象确产生了相同的hash值,也就是发生了Hash key相同致使冲突的状况,那么就在这个Hash key的地方产生一个链表,将全部产生相同hashcode的对象放到这个单链表上去,串在一块儿。比较两个对象的时候,首先根据他们的hashcode去hash表中找他的对象,当两个对象的hashcode相同,只能根据Object的equal方法来比较这个对象是否equal。
Object的equal方法默认是两个对象的引用的比较,意思就是指向同一内存。
可是,String对象中equals方法是判断值的,而==是地址判断(由于JDK重写了)。
咱们很大部分时间都是进行两个对象的比较(而不是比较引用),这个时候Object的equals()方法就不能够了,因此才会有String这些类对equals方法的改写,依次类推Double、Integer、Math等等这些类都是重写了equals()方法的,从而进行的是内容的比较。
java.lnag.Object中对hashCode的约定:
只要改写了就会违约,因此要继续改写。
ConcurrentHashMap融合了hashtable和hashmap两者的优点。hashmap在单线程状况下效率较高;hashtable在的多线程状况下,同步操做能保证程序执行的正确性。
hashtable每次同步执行的时候都要锁住整个结构:
ConcurrentHashMap锁的方式是稍微细粒度的。
更使人惊讶的是ConcurrentHashMap的读取并发,由于在读取的大多数时候都没有用到锁定,因此读取操做几乎是彻底的并发操做,而写操做锁定的粒度又很是细,比起以前又更加快速(这一点在桶更多时表现得更明显些)。只有在求size等操做时才须要锁定整个表。
在迭代时,使用弱一致迭代器,再也不是抛出 ConcurrentModificationException,在改变时new新的数据从而不影响原有的数据,iterator完成后再将头指针替换为新的数据。
答:“HashMap是基于hashing的原理,咱们使用put(key, value)存储对象到HashMap中,使用get(key)从HashMap中获取对象。当咱们给put()方法传递键和值时,咱们先对键调用hashCode()方法,返回的hashCode用于找到bucket位置来储存Entry对象。”
关键:HashMap是在bucket中储存键对象和值对象,做为Map.Entry。
由于hashcode相同,因此它们的bucket位置相同,发生冲突,Entry(包含有键值对的Map.Entry对象)会存储在链表中。
遍历链表直到找到Entry对象,找到bucket位置以后,会调用keys.equals()方法去找到链表中正确的节点。
默认的负载因子大小为0.75,将会建立原来HashMap大小的两倍的bucket数组,来从新调整map的大小(rehashing)。当多线程的状况下,可能产生条件竞争(race condition),两个线程都发现HashMap须要从新调整大小了,它们会同时试着调整大小。
先这样吧
如有错误之处请指出,更多地关注煎鱼。