阿里
1.HashMap和HashTable的区别,及其实现原理
HashTable和HashMap都是map接口的实现类,这两个类的实现原理基本是一致的,都是基于数组加链表的数据结构,因此把他们放到一起分析。
一、实现原理:
HashTable和HashMap都实现了map接口,只是HashTable继承了Dictionary抽象类而HashMap继承了AbstractMap类,他们的底层实现基本是一致的,都是基于数组(Entry类型)加链表这种数据结构实现的,称为“拉链法”实现HashTable
1、HashTable和HashMap的构造方法:二者的构造方法都是一样的,有以下4中(以HashMap为例)
HashMap()
构造一个空的 HashMap ,默认初始容量(16)和默认负载系数(0.75)。
HashMap(int initialCapacity)
构造一个空的 HashMap具有指定的初始容量和默认负载因子(0.75)。
HashMap(int initialCapacity, float loadFactor)
构造一个空的 HashMap具有指定的初始容量和负载因子。
HashMap(Map<? extends K,? extends V> m)
构造一个新的 HashMap与指定的相同的映射 Map 。
HashMap的一个实例有两个影响其性能的参数: 初始容量(initialCapacity)和负载因子(loadFactor) 。 容量是哈希表中的桶(bucket)数,初始容量只是创建哈希表时的容量。 负载因子是在容量自动增加之前允许哈希表得到满足的度量。 当在散列表中的条目的数量超过了负载因数和初始容量的乘积,哈希表被重新散列 (即,内部数据结构被重建),使得哈希表具有桶的大约两倍。
作为一般规则,默认负载因子(.75)提供了时间和空间成本之间的良好折中。 更高的值会降低空间开销,但会增加查找成本(反映在HashMap类的大部分操作中,包括get和put )。 在设置其初始容量时,应考虑地图中预期的条目数及其负载因子,以便最小化重新散列操作的次数。 如果初始容量大于最大条目数除以负载因子,则不会发生重新排列操作。
2、HashTable和HashMap的主要方法(基于JDK1.8.0_102):
HashTable和HashMap的API中提供了很多的方法,其中主要两个方法是put()和get():
①put()方法:
思路:
▶根据key计算可以key的hashCode,然后计算出hashCode在数组中对应的bucket的位置,如果该位置的key为null,那么在这个位置创建一个新的Entry类型的Node:
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
▶如果这个位置的key不为null,那么就要判断待添加的元素和该位置的元素是否相同,如果相同的话,就把key对应的旧节点e替换为新的节点:
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
▶如果该节点是一个树节点的实例,那说明HashMap的底层已经转为数组加红黑树的结构,那么把该节点添加到红黑树中:
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
▶如果以上都不是,那么开始对该位置指向的链表遍历,对每一个节点,如果他们的hashCode相同且key相同,那么退出遍历,该节点就是要被替换成新节点的节点e,对于这个节点,做如下处理:将该节点的value换成新的value,并返回旧的value
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
▶如果遍历完该位置的链表都没有找到重复的节点(hashCode一致且key相同),那么在链表尾部添加新节点,并且如果添加新节点后,链表长度大于TREEIFY_THRESHOLD那么将链表转换为红黑树的结构:
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
在JDK1.8.0_102中,TREEIFY_THRESHOLD等于8,也就是说hashmap,当链表长度大于8时,将链表转换为红黑树的结构,这样当该位置(bucket)的节点数很多时,访问效率会提高很多。
②get()方法:
HashMap源码:根据给出的key计算hashCode,然后计算在该HashMap中对应的bucket位置,如果找到fail位置,并且key。equals()返回true,那么返回该可以映射的value,如果没有找到核分条件的key,那么返回null,但是这里要说明,返回null也不一定是没有找到符合条件的null,也可能是这个key映射的value本来就是null,那么这个时候就需要用containsKey()这个方法来区别这两种情况。
public V get(Object key) {
Node<K,V> e;
return (e = getNode(hash(key), key)) == null ? null : e.value;
}
final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
if ((tab = table) != null && (n = tab.length) > 0 &&
(first = tab[(n - 1) & hash]) != null) {
if (first.hash == hash && // always check first node
((k = first.key) == key || (key != null && key.equals(k))))
return first;
//如果first不是符合条件的key,那么对后面的bucket,如果是树,则查询树的节点key是否
//符合条件,如果是链表,则遍历链表中的节点,查看是否有符合条件的key
if ((e = first.next) != null) {
if (first instanceof TreeNode)
return ((TreeNode<K,V>)first).getTreeNode(hash, key);
do {
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
return e;
} while ((e = e.next) != null);
}
}
return null; //如果没有查找到则返回null
}
HashTable源码:因为HashTable的key和value都不允许为null,并且HashTable不会转换为红黑树的结构,因此要简单很多,只要遍历每个bucket的链表,查询是否有符合条件的key即可。
public synchronized V get(Object key) {
Entry<?,?> tab[] = table;
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
if ((e.hash == hash) && e.key.equals(key)) {
return (V)e.value;
}
}
return null;
}
二、HashTable和HashMap的不同点:
1、继承的类不同:HashTable继承Dictionary类,HashMap继承AbstractMap类;
2、线程安全级别不同:HashTable是线程安全的类,每个方法都有Synchronized修饰,HashMap不是线程安全的,如果想要用HashMap创建线程安全的对象,那么可以用Collections接口的SynchrinizedMap()方法封装一个HashMap的对象,使之成为线程安全的对象。由于HashMap非线程安全,在只有一个线程访问的情况下,效率要高于HashTable。
Map m = Collections.synchronizedMap(new HashMap(...));
3、是否允许null的要求不同
HashTable:key不允许为null,value不允许为null
HashMap:key允许为null,value允许为null
4、在JDK1.8以后,HashMap在节点数大于TREEIFY_THRESHOLD(等于8)时,底层实现从数组加链表转换为数组加红黑树,因为防止查找太慢了啊;但是HashTable没有这种操作。(即hashmap=数组+链表/红黑树)(hashtable=数组+链表)
6、两者通过hash值散列到hash表的算法不一样:
HashTbale是古老的除留余数法,直接使用hashcode
int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) % tab.length;
而HashMap是强制容量为2的幂,重新根据hashcode计算hash值,在使用hash 位与 (hash表长度 – 1),也等价取膜,但更加高效,取得的位置更加分散,偶数,奇数保证了都会分散到。前者就不能保证
int hash = hash(k);
int i = indexFor(hash, table.length);
static int hash(Object x) {
int h = x.hashCode();
h += ~(h << 9);
h ^= (h >>> 14);
h += (h << 4);
h ^= (h >>> 10);
return h;
}
static int indexFor(int h, int length) {
return h & (length-1);
2.ArrayList,LinkedList 和Vector的区别和实现原理。
ArrayList是基于数组的可变长数组,因为这个特性,所以它更适合实现get和set;LinkedList是基于双向链表的,所以比较适合实现插入和删除等操作;但以上两个都是非线程安全的,Vector的实现和ArrayList差不多,改进的地方是使用synchronized实现了线程安全。所以所还是hash表比较抗用啊,综合了数组和链表的属性,查的块,插入删除也快呀,哈哈哈,我说的是拉链法实现hash表哦。
3.TreeMap和TreeSet区别和实现原理。
其中 TreeMap 是 Map 接口的常用实现类,而 TreeSet 是 Set 接口的常用实现类。TreeSet 底层是通过 TreeMap 来实现的(如同HashSet底层是是通过HashMap来实现的一样),因此二者的实现方式完全一样。而 TreeMap 的实现就是红黑树算法。
相同点:
TreeMap和TreeSet都是有序的集合,也就是说他们存储的值都是拍好序的。
TreeMap和TreeSet都是非同步集合,因此他们不能在多线程之间共享,不过可以使用方法Collections.synchroinzedMap()来实现同步
运行速度都要比Hash集合慢,他们内部对元素的查询操作时间复杂度为O(logN),而HashMap/HashSet则为O(1)。
不同点:
最主要的区别就是TreeSet和TreeMap非别实现Set和Map接口
TreeMap的底层采用红黑树的实现,完成数据有序的插入,排序
3.java线程阻塞调用wait函数和sleep区别和联系,还有函数yield,notify等的作用。
wait是Object的方法,sleep是Thread类的方法;
wait让出CPU资源的同时会放弃锁,sleep让出CPU资源的同时不会释放锁;
wait需要notify或者notifyall来唤醒才能进入就绪状态,sleep在沉睡指定时间后,会自动进入就绪状态;
5.java中异常机制
Throwable是Error和Exception的父类,Error一般是指JVM抛出的错误,不需要捕获,Exception是程序错误,需要捕获处理;