小言_互联网的博客

面试之集合问题的总结

274人阅读  评论(0)

1.说说常见的集合有哪些吧?

      Map接口和Collection接口是所有集合的父接口

  1. Collection接口的子接口有Set 接口和List接口
  2. Map接口的实现类有HashMap,TreeMap,HashTable,ConCurrentHashMap,Properties
  3. Set接口的实现类有HashSet,TreeSet,LinkedHashSet
  4. List接口的实现类有ArrayList,LinkedList,Vector,Stack

2.HashMap与HashTable的区别?

  1. HashMap没有考虑同步,是线程不安全的,而Hashtable里使用了synchronized关键字,是线程安全的
  2. HashMap的key和value值可以是null, 而Hashtable的key和value不能为空
  3. HashMap继承自AbstractMap,而Hashtable继承自Dictionary类

3.HashMap的put方法的具体流程?

                

4.HashMap是怎么解决哈希冲突的?

      1.使用链地址法链接相同hash值的数据

      2.通过两次扰动函数来降低哈希冲突的概率,使得数据分布更均匀

      3.引入红黑树进一步降低遍历的时间复杂度,使得遍历更快

5.HashMap为什么不直接使用hashCode()处理后的哈希值直接作为table的下标?

      hashCode()方法返回值是int类型,范围为-2^31~2^31-1,而HashMap的容量范围为16~2^30,HashMap通常情况下无法取到最大值,且设备上无法提供那么多的内存空间,从而导致通过hashCode()算出的哈希值可能不在数组大小范围内,进而无法匹配存储位置。

      那该如何解决?

           1. HashMap里面实现了hash()方法,通过两次扰动使得高低位进行异或运算,降低了哈希碰撞概率,使得数据分配更均匀。

           2.在保证数组长度为2的幂次方时,通过hash()方法扰动算出的值与数组(长度-1)进行与运算来获取数组下标的方式进行存储,这样一来比取余更有效,而是只有当数组长度为2的幂次方时,h&(length-1)才等价于h%length,三是解决了hash值与数组不匹配的问题。

      为什么数组长度是2的幂次方?

          1.因为只有数组长度是2的幂次方,h&(length-1)才等价于h%length,即实现了key的定位,2的幂次方也可以减少哈希冲突的次数,提高HashMap的查询效率;

          2.如果 length 为 2 的次幂 则 length-1 转化为二进制必定是 11111……的形式,在于 h 的二进制与操作效率会非常的快,而且空间不浪费;如果 length 不是 2 的次幂,比如 length 为 15,则 length - 1 为 14,对应的二进制为 1110,在于 h 与操作,最后一位都为 0 ,而 0001,0011,0101,1001,1011,0111,1101 这几个位置永远都不能存放元素了,空间浪费相当大,更糟的是这种情况中,数组可以使用的位置比数组长度小了很多,这意味着进一步增加了碰撞的几率,减慢了查询的效率!这样就会造成空间的浪费。

         3.如果桶初始化桶数组设置太大,就会浪费内存空间,16是一个折中的大小,既不会像1,2,3那样放几个元素就扩容,也不会像几千几万那样可以只会利用一点点空间从而造成大量的浪费。

         4.加载因子设置为0.75而不是1,是因为设置过大,桶中键值对碰撞的几率就会越大,同一个桶位置可能会存放好几个value值,这样就会增加搜索的时间,性能下降,设置过小也不合适,如果是0.1,那么10个桶,threshold为1,你放两个键值对就要扩容,太浪费空间了。

      为什么是两次扰动?

          加大了哈希值低位的扰动性,使得分布更均匀,从而提高了数组下标位置的随机性和均匀性,最终减少hash冲突,两次就够了,已经达到了高位与低位同时参与与运算的目的。

6.HashMap在JDK1.7和JDK1.8中有哪些不同?

不同 JDK 1.7 JDK 1.8
存储结构 数组 + 链表 数组 + 链表 + 红黑树
初始化方式 单独函数:inflateTable() 直接集成到了扩容函数resize()
hash值计算方式 扰动处理 = 9次扰动 = 4次位运算 + 5次异或运算 扰动处理 = 2次扰动 = 1次位运算 + 1次异或运算
存放数据的规则 无冲突时,存放数组;冲突时,存放链表 无冲突时,存放数组;冲突 & 链表长度 < 8:存放单链表;冲突 & 链表长度 > 8:树化并存放红黑树
插入数据方式 头插法(先讲原位置的数据移到后1位,再插入数据到该位置) 尾插法(直接插入到链表尾部/红黑树)
扩容后存储位置的计算方式 全部按照原来方法进行计算(即hashCode ->> 扰动函数 ->> (h&length-1)) 按照扩容后的规律计算(即扩容后的位置=原位置 or 原位置 + 旧容量)

7.为什么HashMap中String、Integer这样的包装类适合作为Key?

      String,Integer等包装类的特性能够保证hash值的不可更改性和计算准确性,能够有效地减少hash碰撞的几率

      1.都是final类型,不可变性,保证了key不可变性,不会存在获取hash值不一样的情况

      2.内部已经重写了hashCode()和equals()方法,不容易出现hash值计算错误的情况

    如果我想要自己的object方法作为key,如何做?

         重写hashCode()方法和equals()方法

         1.重写hashCode()方法,是因为需要计算存储数据的存储位置

         2.重写equals()方法,目的是为了保证key在哈希表中的唯一性

8.ArrayList 和 Vector 的区别?

      这两个类都实现了 List 接口(List 接口继承了 Collection 接口),他们都是有序集合,即存储在这两个集合中的元素位置都是有顺序的,相当于一种动态的数组,我们以后可以按位置索引来取出某个元素,并且其中的数据是允许重复的,这是与 HashSet 之类的集合的最大不同处,HashSet 之类的集合不可以按索引号去检索其中的元素,也不允许有重复的元素。

ArrayList 与 Vector 的区别主要包括两个方面:

  1. 同步性:
    Vector 是线程安全的,也就是说它的方法之间是线程同步(加了synchronized 关键字)的,而 ArrayList 是线程不安全的,它的方法之间是线程不同步的。如果只有一个线程会访问到集合,那最好是使用 ArrayList,因为它不考虑线程安全的问题,所以效率会高一些;如果有多个线程会访问到集合,那最好是使用 Vector,因为不需要我们自己再去考虑和编写线程安全的代码。

  2. 数据增长:
    ArrayList 与 Vector 都有一个初始的容量大小,当存储进它们里面的元素的个人超过了容量时,就需要增加 ArrayList 和 Vector 的存储空间,每次要增加存储空间时,不是只增加一个存储单元,而是增加多个存储单元,每次增加的存储单元的个数在内存空间利用与程序效率之间要去的一定的平衡。Vector 在数据满时(加载因子1)增长为原来的两倍(扩容增量:原容量的 2 倍),而 ArrayList 在数据量达到容量的一半时(加载因子 0.5)增长为原容量的 (0.5 倍 + 1) 个空间

9.HashSet是如何保证数据不可重复的?

      HashSet底层就是HashMap,只不过HashSet实现了Set接口并且把数据作为key值,而value值一直使用一个相同的虚值来保存。由于HashMap的K值本身就不允许重复,并且在HashMap中如果K/V相同时,会用新的V覆盖掉旧的V,然后返回旧的V,那么在HashSet中执行这一句话始终会返回一个false,导致插入失败,这样就保证了数据的不可重复性。

10.ArrayList和LinkedList的区别?     

  1. LinkedList 实现了 List 和 Deque 接口,一般称为双向链表;ArrayList 实现了 List 接口,动态数组;
  2. LinkedList 在插入和删除数据时效率更高,ArrayList 在查找某个 index 的数据时效率更高;
  3. LinkedList 比 ArrayList 需要更多的内存;

   Array 和 ArrayList 有什么区别?什么时候该应 Array 而不是 ArrayList 呢?

  1. Array 可以包含基本类型和对象类型,ArrayList 只能包含对象类型。
  2. Array 大小是固定的,ArrayList 的大小是动态变化的。
  3. ArrayList 提供了更多的方法和特性,比如:addAll(),removeAll(),iterator() 等等。

11.Java集合的快速失败机制 “fail-fast”?

      是java集合的一种错误检测机制,当多个线程对集合进行结构上的改变的操作时,有可能会产生 fail-fast 机制。

原因:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。

解决办法:

1. 在遍历过程中,所有涉及到改变modCount值得地方全部加上synchronized。

2. 使用CopyOnWriteArrayList来替换ArrayList。


转载:https://blog.csdn.net/weixin_44307391/article/details/100546811
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场