1、HashMap 和 Hashtable 的区别?
HashMap 和 Hashtable是Map接口的实现类,它们大体有一下几个区别:
1. 继承的父类不同。HashMap是继承自AbstractMap类,而HashTable是继承自Dictionary类。
2. 线程安全性不同。Hashtable 中的方法是Synchronize的,而HashMap中的方法在缺省情况下是非Synchronize的。Hashtable 是现成安全的,HashMap是非线程安全的。
3. key和value是否允许null值。Hashtable中,key和value都不允许出现null值。但是如果在Hashtable中有类似put(null,null)的操作,编译同样可以通过,因为key和value都是Object类型,但运行时会抛出NullPointerException异常,这是JDK的规范规定的。HashMap中,null可以作为键,这样的键只有一个;可以有一个或多个键所对应的值为null。当get()方法返回null值时,可能是 HashMap中没有该键,也可能使该键所对应的值为null。因此,在HashMap中不能由get()方法来判断HashMap中是否存在某个键, 而应该用containsKey()方法来判断。
2、Map 集合有哪些实现类,分别具有什么特征?
实现类 | 特征 |
HashMap | 线程不安全的键值对集合,允许 null 值,key 和 value 都可以 |
HashTable | 线程安全的键值对集合,不允许 null 值,key 和 value 都不可以 |
TreeMap | 能够把它保存的记录根据键排序,默认是按升序排序 |
3、解决 hashmap 线程不安全问题?
1. Collections.synchronizedMap() 方法
2. java.util.concurrent.ConcurrentHashMap 类
4、Hashmap 的底层实现原理?
在JDK1.6,JDK1.7中,HashMap采用位桶+链表实现,即使用链表处理冲突,同一hash值的键值对会被放在同一个位桶里,当桶中元素较多时,通过key值查找的效率较低。
而JDK1.8中,HashMap采用位桶+链表+红黑树实现,当链表长度超过阈值(8),时,将链表转换为红黑树,这样大大减少了查找时间。
当我们创建 hashmap 时 会先创建一个数组,当我们用 put 方法存数据时,先根据 key 的 hashcode 值计算出 hash 值,然后用这个哈希值确定在数组中的位置,再把 value 值放进去,如果这个位置本来没放 东西,就会直接放进去,如果之前就有,就会生成一个链表,把新放入的值放在头部,当用 get 方法取值时,会先根据 key 的 hashcode 值计算出 hash 值,确定位置,再根据 equals 方法从该位置上的链表中取出该 value 值。
5、hash 碰撞怎么产生,怎么解决?
对象Hash的前提是实现equals()和hashCode()两个方法,那么HashCode()的作用就是保证对象返回唯一hash值,但当两个对象计算值一样时,这就发生了碰撞冲突。如下将介绍如何处理冲突,当然其前提是一致性hash。
解决hash碰撞有以下几种方法:
● 开放地址法
开放地执法有一个公式:Hi=(H(key)+di) MOD m i=1,2,…,k(k<=m-1) 其中,m为哈希表的表长。di 是产生冲突的时候的增量序列。如果di值可能为1,2,3,…m-1,称线性探测再散列。如果di取1,则每次冲突之后,向后移动1个位置.如果di取值可能为1,-1,2,-2,4,-4,9,-9,16,-16,…kk,-kk(k<=m/2),称二次探测再散列。如果di取值可能为伪随机数列。称伪随机探测再散列。
● 再哈希法
当发生冲突时,使用第二个、第三个、哈希函数计算地址,直到无冲突时。缺点:计算时间增加。比如上面第一次按照姓首字母进行哈希,如果产生冲突可以按照姓字母首字母第二位进行哈希,再冲突,第三位,直到不冲突为止。
● 链地址法(拉链法)
将所有关键字为同义词的记录存储在同一线性链表中。如下:
6、HashMap 为什么需要扩容?
当hashmap中的元素个数超过数组大小的loadFactor倍时,就会进行数组扩容,loadFactor的默认值为 0.75,也就是说,默认情况下,数组大小为 16,那么当hashmap 中元素个数超过 16*0.75=12 的时候,就把数组的大小扩展为2*16=32,即扩大一倍。然后重新计算每个元素在数组中的位置,而这是一个非常消耗性能的操作,所以如果我们已经预知 hashmap 中元素的个数,那么预设元素的个数能够有效的提高 hashmap 的性能。比如说,我们有 1000 个元素new HashMap(1000), 但是理论上来讲 new HashMap(1024)更合适,不过上面annegu 已经说过,即使是 1000,hashmap 也自动会将其设置为 1024。 但是newHashMap(1024)还不是更合适的,因为 0.75*1000<1000,也就是说为了让0.75*size>1000, 我们必须这样 newHashMap(2048)才最合适,避免了resize 的问题。
7、如何遍历 Map 集合?
1. 先获取 Map 中的 key 的 set 集合 map.keySet(); 通过遍历 key 集合,获取 value 值。
2. Map.get(key)先获取 Entry 集合 map.entrySet(); 遍历 entrySet 分别获取 key value。
8、ArrayList 与 LinkedList 区别?
ArrayList 使用数组方式存储数据,所以根据索引查询数据速度快,而新增或者 删除元素时需要设计到位移操作,所以比较慢。
LinkedList 使用双向链接方式存储数据,每个元素都记录前后元素的指针, 所以插入、删除数据时只是更改前后元素的指针指向即可,速度非常快,然后通过下标查询元素时需要从头开始索引,所以比较慢,但是如果查询前几个元素或 后几个元素速度比较快。
ArrayList 与 LinkedList 都是线程不安全的。
9、Java中的ArrayList的初始容量和容量分配?
ArrayList是经常会被用到的,一般情况下,使用的时候会像这样进行声明:
List arrayList = new ArrayList();
如果像上面这样使用默认的构造方法,初始容量被设置为10。当ArrayList中的元素超过10个以后,会重新分配内存空间,使数组的大小增长到16。
可以通过调试看到动态增长的数量变化:10->16->25->38->58->88->...
也可以使用下面的方式进行声明:
List arrayList = new ArrayList(4);
将ArrayList的默认容量设置为4。当ArrayList中的元素超过4个以后,会重新分配内存空间,使数组的大小增长到7。
可以通过调试看到动态增长的数量变化:4->7->11->17->26->...
那么容量变化的规则是什么呢?请看下面的公式:
((旧容量 * 3) / 2) + 1
10、如何使用的 List 集合来保证线程安全?
1. 使用 Vector
2. 使用 Collections 中的方法 synchronizedList 将 ArrayList 转换为线程安全的 List
3. 使用 java.util.current 包下的 CopyOnWriteArrayList(推荐)