面试宝典-【集合】

目录

1.数组存储的特点?

2.常见的集合有哪些?

3.说一下Iterator迭代器?

4. ArrayList、LinkedList 、Vector区别?

5. 简单说一下set?

6.简单说一下map?

7.ArrayList 的扩容机制了解吗?

8.HashMap中元素的特点?

9. HashMap 的添加/修改的过程?

10.hashMap的扩容机制?

11.什么时候扩容?

12.HashMap 的长度为什么是 2 的幂次方? 

13.为什么建议设置HashMap的容量?

14.能说一下 HashMap 的底层数据结构吗? 

15.什么时候会将链表转换为红黑树?

16.什么时候会将红黑树转换为链表?

17.HashMap 的 hash 函数是怎么设计的? 

18.解决哈希冲突有哪些方法呢?

19.为什么 HashMap 链表转红黑树的阈值为 8 呢? 

20.一般用什么作为HashMap的key?

21.HashMap为什么线程不安全?

22.HashMap和HashTable的区别?

23.TreeMap 和 HashMap 的区别?

24.LinkedHashMap底层原理?

25.讲讲 HashSet 的底层实现?


1.数组存储的特点?

  • 1.数组一旦初始化,其长度就是确定的。
  • 2.数组中的多个元素依次紧密排列,有序,可重复
  • 3.元素的类型既可以是基本数据类型,也可以是引用数据类型

2.常见的集合有哪些?

  • 集合类主要由两个接口:CollectionMap派生出来的
  • Collection有三个子接口:List、Set、Queue。

  • collection:存储一个一个的数据
    • lsit:存储有序的、可重复的数据
      • 实现类: ArrayList、LinkedList 、Vector
    • set:存储无序的、不可重复的数据
      • 实现类:HashSet、LinkedHashSet、TreeSet 
    • queue:一个用于保持元素队列的集合。
      • 实现类:PriorityQueue、ArrayDeque 
  • Map:存储一对一对的数据
    • 实现类: HashMap、LinkedHashMap、TreeMap、Hashtable、 Properties

3.说一下Iterator迭代器?

  • 作用:遍历集合的利器
       Iterator iterator = list.iterator();
        //hasNext():判断是否还有下一个元素
        while (iterator.hasNext()){
            //next():①指针下移②将下移以后集合位置上的元素返回
            System.out.println(iterator.next());
        }

4. ArrayList、LinkedList 、Vector区别?

  • ArrayList
    • list的主要实现类,线程不安全的、效率高,底层使用Object[]数组存储.
    • 在添加数据、查找数据时,效率较高;在插入、删除数据时,效率较低
    • 可以通过下标直接获取元素
    • 当添加第11个元素时扩容,默认扩容1.5倍
    • JDK1.7初始化数组大小为10,JDK1.8首次添加元素时,才会初始化数组大小为10
  • LinkedList
    • 线程不安全的底层使用双向链表的方式进行存储
    • 在插入、删除数据时,效率较高;在添加数据、查找数据时,效率较低;
    • 不可以通过下标直接获取元素
    • 不考虑扩容
  • Vector
    • list的古老实现类,线程安全的、效率低,底层使用Object[]数组存储
    • 当添加第11个元素时扩容,默认扩容2倍
    • JDK1.8初始化数组大小为10

5. 简单说一下set?

set:存储无序的、不可重复的数据

主要实现类:HashSet、LinkedHashSet、TreeSet 

  • HashSet:底层使用的是HashMap,即使用数组+单向链表+红黑树结构进行存储。
  • LinkedHashSet:是HashSet的子类,在现有的数组+单向链表+红黑树结构的基础上,又添加了一组双向链表,用于记录添加元素的先后顺序
  • TreeSet :底层使用红黑树存储。可以按照添加的元素的指定的属性的大小顺序进行遍历。

6.简单说一下map?

主要实现类 :HashMap、LinkedHashMap、TreeMap、Hashtable、 Properties

  • HashMap:主要实现类;线程不安全的,效率高;可以添加null的key和value值;jdk1.8底层使用数组+单向链表+红黑树结构,jdk1.7底层使用数组+单向链表
  • TreeMap:古老实现类;线程安全的,效率低;不可以添加null的key或value值;底层使用数组+单向链表
  • LinkedHashMap:是HashMap的子类;在HashMap使用的数据结构的基础上,增加了一对双向链表,用于记录添加的元素的先后顺序,遍历元素时,就可以按照添加的顺序显示。对于频繁的遍历操作,建议使用此类。
  • Hashtable:古老实现类;线程安全的,效率低;不可以添加null的key或value值;底层使用数组+单向链表结构存储
  • Properties:其key和value都是String类型。

7.ArrayList 的扩容机制了解吗?

  • ArrayList 是基于数组的集合,数组的容量是在定义的时候确定的,如果数组满了,再插入,就会数组溢出。所以在插入时候,会先检查是否需要扩容,如果当前容量+1 超过数组长度,就会进行扩容。
  • ArrayList 的扩容是创建一个1.5 倍的新数组,然后把原数组的值拷贝过去。 

8.HashMap中元素的特点?

  • 1.HashMap中的所有的key彼此之间是不可重复的、无序的。 
  • 2.HashMap中的所有的value彼此之间是可重复的、无序的。
  • 3.HashMap中的一个key-value,就构成了一个entry。
  • 4.HashMap中的所有的entry彼此之间是不可重复的、无序的。所有的entry就构成了一个Set集合。

9. HashMap 的添加/修改的过程?

  • jdk1.7:将(key1,value1)添加到map中:首先初始化长度16的数组
    • 1.调用key1所在类的hash()方法,计算key1对应的哈希值1,此哈希值1经过hash()算法之后,得到哈希值2,哈希值2再经过(idexFor()算法之后,就确定了(key1,value1)在数组table中的索引位置i。
      • 1.1.如果此索引位置i的数组上没有元素,则(key1,value1)添加成功。将(key1,value1)存放到数组的索引i的位置
      • 1.2.如果此索引位置i的数组上有元素(kev2.value2),则需要继续比较key1和key2的哈希值2,此过程称为哈希冲突
        • 2.1.如果key1的哈希值2与key2的哈希值2不相同,则(key1,value1)添加成功。
        • 2.2.如果key1的哈希值2与key2的哈希值2相同,则需要继续比较key1和key2的equals()。要调用key1所在类的equals(),将key2作为参数传递进去。
          • 3.1调用equals(),返回false :则(key1.value1)添加成功。
          • 3.2调用equals(),返回true:则认为key1和key2是相同的。默认情况下, value1替换原有的value2.
  • JDK1.8中与JDK1.7的区别:
    • JDK.18中当我们创建了HashMap实例以后,底层并没有初始化table数组。
    • JDK.18中当首次添加(key,value)时,进行判断,如果发现table尚未初始化,则对数组进行初始化,Node类型的数组
    • 在JDK1.7中采用头插法,在JDK1.8中采用尾插法
    • 在JDK1.7中采用数组+单向链表,在JDK1.8中采用数组+单向链表+红黑树

10.hashMap的扩容机制?

  • 当元素的个数达到临界值(->数组的长度*加载因子)时,就考虑扩容。
  • 默认扩容为原来的2倍。

11.什么时候扩容?

  •  HashMap 会在存储的键值对数量超过阈值(即容量 * 加载因子)时进行扩容。
  • 加载因子默认是0.75

加载因子为什么默认是0.75 ?

  • Node[] table的初始化长度length为16,默认的loadFactor是0.75,0.75是对空间和时间效率的一个平衡选择,根据泊松分布,loadFactor 取0.75碰撞最小
  • 如果内存空间很多而又对时间效率要求很高,可以降低负载因子Load factor的值 。

  • 如果内存空间紧张而对时间效率要求不高,可以增加负载因子loadFactor的值,这个值可以大于1。

12.HashMap 的长度为什么是 2 的幂次方? 

  • 将HashMap的长度定为2 的幂次方,这样就可以使用(n -1) & hash位运算代替%取余的操作,提高性能。
  • 数组的长度必须是 2 的整数次幂,这样可以保证 hash & (n-1) 的结果能均匀地分布在数组中。

& 操作的结果就是哈希值的高位全部归零,只保留 n 个低位,用来做数组下标访问。

先判断是否小于16,小于的话,容量就会乘以2

int capacity = 1;
while (capacity < initialCapacity)capacity <<= 1;

13.为什么建议设置HashMap的容量?

  • HashMap有扩容机制,就是当达到扩容条件时会进行扩容。扩容条件就是当HashMap中的元素个数超过临界值时就会自动扩容(threshold = loadFactor * capacity)。 
  • 如果我们没有设置初始容量大小,随着元素的不断增加,HashMap会发生多次扩容。
  • HashMap每次扩容都需要重建hash表,非常影响性能。所以建议开发者在创建HashMap的时候指定初始化容量。

14.能说一下 HashMap 的底层数据结构吗? 

  • JDK 8 中 HashMap的数据结构是数组+链表+红黑树
  • JDK 7中 HashMap的数据结构是数组+链表

15.什么时候会将链表转换为红黑树?

  • 如果数组索引i位置上的元素的个数达到8并且数组的长度达到64,我们就将此索引i位置上的多个元素改为使用红黑树的结构进行存储。 
  • 好处:
    • 红黑树进行put()/get()/remove()操作的时间复杂度为O(logn),比单向链表的时间复杂度O(n)好,性能更高。

16.什么时候会将红黑树转换为链表?

  • 当使用红黑树的索引i位置上的元素的个数低于6的时候,就会将红黑树结构退化为单向链表。

17.HashMap 的 hash 函数是怎么设计的? 

    static final int hash(Object key) {
        int h;
        //key 的hashCode 和key 的hashCode 右移16位做异或运算
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
  • HashMap 的哈希函数是先拿到 key 的 hashcode,是一个 32 位的 int 类型的数值,然后让 hashcode 的高 16 位和低 16 位进行异或操作。有效降低哈希冲突

18.解决哈希冲突有哪些方法呢?

  • 1.再哈希法 :准备两套哈希算法,当发生哈希冲突的时候,使用另外一种哈希算法,直到找到空槽为止。对哈希算法的设计要求比较高。
  • 2.开放地址法:遇到哈希冲突的时候,就去寻找下一个空的槽
  • 3.链地址法当发生哈希冲突的时候,使用链表将冲突的元素串起来。(HashMap采用)

19.为什么 HashMap 链表转红黑树的阈值为 8 呢? 

  • 红黑树节点的大小大概是普通节点大小的两倍,所以转红黑树,牺牲了空间换时间,更多的是一种兜底的策略,保证极端情况下的查找效率。
  • 阈值为什么要选 8 呢?和统计学有关。理想情况下,使用随机哈希码,链表里的节点符合泊松分布,出现节点个数的概率是递减的,节点个数为 8 的情况,发生概率仅为0.00000006

  • 至于红黑树转回链表的阈值为什么是 6,而不是 8?是因为如果这个阈值也设置成 8,假如发生碰撞,节点增减刚好在 8 附近,会发生链表和红黑树的不断转换,导致资源浪费

20.一般用什么作为HashMap的key?

一般用Integer、 String这种不可变类当HashMap当key, String类比较常用。

  • String是不可变的,在它创建的时候 hashcode“就被缓存了,不需要重新计算。这就是HashMap中的key经常使用字符串的原因。
  • 获取对象的时候要用到 equals()hashCode()以及equals()和hashCode() 方法,而Integer、 String这些类都已经重写了方法,不需要自己去重写这两个方法。

21.HashMap为什么线程不安全?

  • 多线程下扩容死循环。JDK1.7中的HashMap使用头插法插入元素,在多线程的环境下,扩容的时候有可能导致环形链表的出现,形成死循环。
  • 在JDK1.8中,在多线程环境下,会发生数据覆盖的情况。 

22.HashMap和HashTable的区别?

HashMap和Hashtable都实现了Map接口。

  • 1.HashMap可以接受为null的key和value, key为null的键值对放在下标为0的头结点的链表中,而Hashtable则不行。 
  • 2.HashMap是非线程安全的, HashTable是线程安全的。Jdk1.5提供了ConcurrentHashMap,它是HashTable的替代。
  • 3.哈希值的使用不同, HashTable直接使用对象的hashCode。而HashMap重新计算hash值。

23.TreeMap 和 HashMap 的区别?

  • 1.HashMap
    • 是基于数组+链表+红黑树实现的
    • put 元素的时候会先计算 key 的哈希值,然后通过哈希值计算出数组的索引,然后将元素插入到数组中,如果发生哈希冲突,会使用链表来解决,如果链表长度大于 8,会转换为红黑树。
    • get 元素的时候同样会先计算 key 的哈希值,然后通过哈希值计算出数组的索引,如果遇到链表或者红黑树,会通过 key 的 equals 方法来判断是否是要找的元素。
    • 在没有发生哈希冲突的情况下,HashMap 的查找效率是 O(1)
  • 2.TreeMap
    • 是基于红黑树实现的
    • put 元素的时候会先判断根节点是否为空,如果为空,直接插入到根节点,如果不为空,会通过 key 的比较器来判断元素应该插入到左子树还是右子树。
    • get 元素的时候会通过 key 的比较器来判断元素的位置,然后递归查找。
    • TreeMap 是基于红黑树实现的,所以 TreeMap 的查找效率是 O(logn)。

24.LinkedHashMap底层原理?

  • LinkedHashMap是HashMap的子类。
  • LinkedHashMap在HashMap使用的数组+单向链表+红黑树的基础上,又增加了一对双向链表,记录添加的(key,value)的先后顺序。便于我们遍历所有的key-value 

25.讲讲 HashSet 的底层实现?

  • HashSet 基于 HashMap 实现。放入HashSet中的元素实际上由HashMap的key来保存,而HashMap的value则存储了一个静态的Object对象。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

会敲代码的小张

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值