1.HashMap与HashTable的区别
-
HashTable是线程安全,方法上添加了synchronized同步修饰,HashMap非线程安全
-
HashMap的key和value可以为空,HashTable的key不可以为空
-
HashMap继承AbstractMap,HashTable继承Dictionary,都实现了map接口
-
HashMap的初始容量是16,填充因子默认0.75,扩充时2n,HashTable的初始容量是11,填充因子0.75,扩充时2n+1
-
HashMap对key的hashcode进行了二次hash,然后对数组长度取模,HashTable是对key的hashcode直接取模
-
HashTable是Enumeration遍历,HashMap是Iterator遍历
-
HashMap和HashTable底层是数组+链表结构实现
-
HashMap在1.8以后底层是数组+链表+红黑树(链表长度大于8)
2.HashMap实现原理
-
底层实现是数组+链表结构(HashMap在1.8以后底层是数组+链表+红黑树)
-
HashMap默认初始化时会创建一个默认容量为16的Entry数组,默认加载因子为0.75,同时设置临界值为16*0.75
-
HashMap会对null值key进行特殊处理,总是放到table[0]位置
-
put过程是先计算hash然后通过hash与table.length取摸计算index值,然后将key放到table[index]位置,当table[index]已存在其它元素时,判断key的值是否相等(equals方法),若相等直接覆盖;若不相等,会在table[index]位置形成一个链表,将新添加的元素放在table[index](java8之前使用的是头插法,java8之后改为尾部插入法,防止死循环),原来的元素通过Entry的next进行链接,这样以链表形式解决hash冲突问题,当链表的长度大于8时,会转换为红黑树。当元素数量达到临界值(capactiyfactor)时,则进行扩容,是table数组长度变为table.length*2 注:头插法:就是说新来的值会取代原有的值,原有的值就顺推到链表中去,因为写这个代码的作者认为后来的值被查找的可能性更大一点,提升查找的效率。
-
同样当key为null时会进行特殊处理,在table[0]的链表上查找key为null的元素
-
get的过程是先计算hash,然后通过hash与table.length取摸计算index值,然后遍历table[index]上的链表,直到找到key,然后返回
-
remove方法和put get类似,计算hash,计算index,然后遍历查找,将找到的元素从table[index]链表移除
3.重新调整HashMap大小存在什么问题
-
多线程的情况下,可能产生条件竞争(race condition)
-
当重新调整HashMap大小的时候,确实存在条件竞争,因为如果两个线程都发现HashMap需要重新调整大小了,它们会同时试着调整大小。在调整大小的过程中,存储在链表中的元素的次序会反过来,因为移动到新的bucket位置的时候,HashMap并不会将元素放在链表的尾部,而是放在头部,这是为了避免尾部遍历(tail traversing)。如果条件竞争发生了,那么就死循环了。因此,多线程条件下,Hashmap是线程不安全的。
4.我们可以使用自定义的对象作为Map的键吗
-
可以使用任何对象作为键
-
只要它遵守了equals()和hashCode()方法的定义规则
-
并且当对象插入到Map中之后将不会再改变了。如果这个自定义对象是不可变的,那么它已经满足了作为键的条件,因为当它创建之后就已经不能改变了。
-
如果这个对象是可变的,当属性值改变了后,他的hash相应改变了,get的时候将找不到原对象了
5.如何让HashMap线程安全
-
使用Collections.synchronizedMap()方法来获取一个线程安全的集合(Collections.synchronizedMap()实现原理是Collections定义了一个SynchronizedMap的内部类,这个类实现了Map接口,在调用方法时使用synchronized来保证线程同步,当然了实际上操作的还是我们传入的HashMap实例,简单的说就是Collections.synchronizedMap()方法帮我们在操作HashMap时自动添加了synchronized来实现线程同步,类似的其它Collections.synchronizedXX方法也是类似原理)
-
使用并发类ConcurrentHashMap
6.ArrayList、LinkedList、Vector的区别
-
Arraylist和Vector是采用动态数组的数据结构,LinkedList基于链表的数据结构
-
对于随机访问get和set,ArrayList优于LinkedList,因为ArrayList可以随机定位,而LinkedList要移动指针一步一步的移动到节点处。
-
对于新增和删除操作add和remove,LinedList比较占优势,只需要对指针进行修改即可,而ArrayList要移动数据来填补被删除的对象的空间。
-
Vector使用了synchronized方法-线程安全,性能上比ArrayList差一点
-
ArrayList数组的起始容量是10.当数组需要增长时,新容量=(旧容量*3)/2+1,也就是说每一次容量大概会增长50%。
-
Vector数组的起始容量是10,可以自定义初始容量,新容量=新容量+旧容量
7.Comparable与Comparator的区别
-
Comparable & Comparator 都是用来实现集合中元素的比较、排序的,只是 Comparable 是在集合内部定义的方法实现的排序,Comparator 是在集合外部实现的排序,所以,如想实现排序,就需要在集合外定义 Comparator 接口的方法或在集合内实现 Comparable 接口的方法。
-
Comparator位于包Java.util下,而Comparable位于包 java.lang下
-
Comparator定义了俩个方法,分别是 int compare(T o1, T o2)和 boolean equals(Object obj),用于比较两个Comparator是否相等。有时在实现Comparator接口时,并没有实现equals方法,可程序并没有报错,原因是实现该接口的类也是Object类的子类,而Object类已经实现了equals方法
-
Comparable接口只提供了 int compareTo(T o)方法
8.HashMap 扩充时候是否允许插入?原始长度为什么设置为 16
-
HashMap是线程不安全的,允许扩充的时候插入,不过插入的数据将不存在
-
HashMap存取时,都需要计算当前key应该对应Entry[]数组哪个元素,即计算数组下标;算法如下:
/*
-
Returns index for hash code h.*/
-
static int indexFor(int h, int length) {
// assert Integer.bitCount(length) == 1 : "length must be a non-zero power of 2";
return h & (length-1);
}
假设数组长度分别为15和16,优化后的hash码分别为8和9,那么&运算后的结果如下:
h & (table.length-1) hash table.length-1
8 & (15-1): 0100 & 1110 = 0100
9 & (15-1): 0101 & 1110 = 0100
8 & (16-1): 0100 & 1111 = 0100
9 & (16-1): 0101 & 1111 = 0101
从上面的例子中可以看出:当8、9两个数和(15-1)2=(1110)进行“&运算”的时候,产生了相同的结果,都为0100,也就是说它们会定位到数组中的同一个位置上去,这就产生了碰撞,8和9会被放到数组中的同一个位置上形成链表,那么查询的时候就需要遍历这个链表,得到8或者9,这样就降低了查询的效率。
同时,我们也可以发现,当数组长度为15的时候,hash值会与(15-1)2=(1110)进行“&运算”,那么最后一位永远是0,而0001,0011,0101,1001,1011,0111,1101这几个位置永远都不能存放元素了,空间浪费相当大,数组可以使用的位置比数组长度小了很多,这意味着进一步增加了碰撞的几率,减慢了查询的效率!
而当数组长度为16时,即为2的n次方时,2n-1得到的二进制数的每个位上的值都为1(这是一个奇妙的世界),这使得在低位上&时,得到的和原hash的低位相同,加之hash(int h)方法对key的hashCode的进一步优化,加入了高位计算,就使得只有相同的hash值的两个值才会被放到数组中的同一个位置上形成链表。
所以说,当数组长度为2的n次幂的时候,不同的key算得得index相同的几率较小,那么数据在数组上分布就比较均匀,也就是说碰撞的几率小,相对的,查询的时候就不用遍历某个位置上的链表,这样查询效率也就较高了。
9.ArrayList使用时常遇到的问题以及解决方法
- 集合元素大小为空,get之后没有判断
list.isEmpty() list.size()>0
- ArrayList在迭代的时候不能去改变自身的元素集合,否则会抛异常:java.util.ConcurrentModificationException
List<Integer> list = new ArrayList<Integer>(); list.add(new Random().nextInt(10)); list.add(new Random().nextInt(10)); //开始迭代 Iterator<Integer> iter = list.iterator(); while (iter.hasNext()) { System.out.println("迭代:" + iter.next()); list.add(new Random().nextInt()); }
-
ArrayList在遍历的时候直接删除,出现ConcurrentModificationException
- ArrayList在迭代的时候可以用迭代器删除ArrayList中的元素
List<Integer> list = new ArrayList<Integer>();
list.add(new Random().nextInt(10));
list.add(new Random().nextInt(10));
//开始迭代
Iterator<Integer> iter = list.iterator();
while (iter.hasNext()) {
System.out.println(iter.next()+"元素被删除");
iter.remove();
}
- CopyOnWriteArrayList是线程安全的集合类,该集合在迭代的时候,可以改变自身的元素集合
List<Integer> syncList = new CopyOnWriteArrayList<Integer>(); syncList.add(1); syncList.add(5); Iterator<Integer> iter = syncList.iterator(); int flag; while (iter.hasNext()) { flag = iter.next(); System.out.println("迭代:" + flag); syncList.remove(new Integer(flag)); } System.out.println("集合的大小:" + syncList.size());
- CopyOnWriteArrayList是线程安全的集合类,该集合在迭代的时候,不能用迭代器去删除集合中的元素 ,否则会抛异常:java.lang.UnsupportedOperationException
List<Integer> syncList = new CopyOnWriteArrayList<Integer>(); syncList.add(1); syncList.add(5); Iterator<Integer> iter = syncList.iterator(); while (iter.hasNext()) { System.out.println("迭代:" + iter.next()); iter.remove(); } System.out.println("集合的大小:" + syncList.size());