Java集合常见面试题

Java集合常见面试题

1.常用的集合有哪些?

  • List接口的实现类:
    • ArrayList:基于动态数组实现,支持随机访问,适合频繁读取的场景
      • 优点:随机访问速度快,内存效率高。
      • 缺点:在中间插入或删除元素时需要移动后续元素,性能较低。
    • LinkedList:基于双向链表实现,适合频繁插入和删除的场景。
      • 优点:在头部或尾部插入和删除元素性能优秀。
      • 缺点:随机访问速度慢,因为需要从头或尾遍历到目标位置。
    • Vector:类似于 ArrayList,但线程安全,适合多线程环境(由于其性能开销,通常不推荐使用)。
      • 优点:同步访问,线程安全。
      • 缺点:在多线程环境下开销较大,且通常比 ArrayList 性能低。
  • Set接口的实现类:
    • HashSet:基于哈希表实现,不保证元素的顺序,适合不需要重复元素的场景。
    • LinkedHashSet:维护元素插入的顺序,基于哈希表和链表实现。
    • TreeSet:基于红黑树实现,保证元素的有序性,适合需要排序的场景。
  • Map接口的实现类:
    • HashMap:基于哈希表实现,允许 null 值和 null 键,性能较高。
    • ConcurrentHashMap : 允许多个线程同时读写而无需额外的同步机制,确保线程安全性。
    • LinkedHashMap:维护元素的插入顺序,基于哈希表和链表实现。
    • TreeMap:基于红黑树实现,保证键的有序性。
    • Hashtable:线程安全的哈希表实现,不允许 null 键和 null 值,性能较低,通常不推荐使用。
  • Queue接口的实现类:
    • PriorityQueue:基于堆实现,支持元素的优先级排序。
    • ArrayDeque:基于动态数组实现,作为双端队列使用,性能优于 LinkedList。
  • 其他集合类
    • Stack:线程安全的后进先出(LIFO)栈,继承自 Vector,但不推荐使用。可以使用 ArrayDeque 来代替。
    • CopyOnWriteArrayList:线程安全的 ArrayList 实现,适合读多写少的场景。写操作时会复制数组,导致性能开销。
    • ConcurrentSkipListMap:线程安全的有序 Map,支持高并发场景,基于跳表实现。

2.集合有哪些特点?

  • List:有序集合,能够精确控制每个元素的插入位置,可以包含重复元素。
  • Set:无序不可重复,元素的顺序是不可预测的
  • Map:键值对集合,持有键(唯一)到值的映射,键不可重复,值可以重复。
  • Queue:队列,先进先出
  • Deque:双端队列,允许从两端添加或删除元素

3.Iterator怎么使用?有什么特点

  • Iterator是一个接口,提供了遍历集合的标准方法,使用如下
    1.获取迭代器实例:通过调用集合的 iterator() 方法获取 Iterator 实例。
    2.检查是否有下一个元素:使用 hasNext() 方法检查集合中是否还有元素。
    3.使访问下一个元素:使用 next() 方法访问集合中的下一个元素,如果没有更多元素而调用此方法,会抛出 NoSuchElementException。
    4.可以使用 remove() 方法从集合中删除最后一个返回的元素。使用 remove() 时必须在 next() 方法之后调用,否则会抛出 IllegalStateException
  • 特点:
    • 通用性:为不同类型的集合提供了同一的遍历方式
    • 安全删除:通过迭代器的remove()方法可以安全删除集合中的元素,避免ConcurrentModificationException.(是在多线程环境下由于结构修改而导致的)
    • 单向遍历:迭代器仅支持向前遍历集合,不支持反向遍历
    • 无法访问索引:迭代器不提供获取当前元素索引的方法

4.ArrayList和LinkedList的区别是什么?

  • 内部结构:
    • ArrayList:基于动态数组实现,支持快速随机访问(通过索引访问),可以在 O(1) 时间复杂度内访问任意元素。
    • LinkedList:基于双向链表实现,每个节点包含指向前后元素的引用,访问特定位置的元素需要 O(n) 的时间复杂度,因为需要遍历链表。
  • 性能:
    • ArrayList:对于随机访问操作速度快(O(1)),但在列表中间或开头插入和删除元素时需要移动大量元素,时间复杂度为 O(n)。如果数组容量不足,还需要进行扩容,这会导致性能下降
    • LinkedList:插入和删除操作速度快(O(1)),特别是在链表的头部或尾部进行操作时很高效。然而,随机访问速度较慢(O(n)),需要从头节点或尾节点开始遍历,直到找到目标元素。
  • 内存占用:
    • ArrayList:由于内部使用数组存储元素,当数组容量不足时可能会进行扩容,导致额外的内存占用(可能会浪费内存空间)。此外,动态数组需要连续的内存空间。
    • LinkedList:每个节点除了存储数据外,还需要额外的内存来存储指向前后节点的引用(两个指针),因此对于存储大量小对象时,内存开销相对较大,可能会导致较多的内存碎片。
  • 使用场景:ArrayList适合频繁查找操作,LinkedList适用于需要在列表的两端进行频繁操作的场合。

5.说一下HashSet的实现原理

  • HashSet是基于HashMap实现的,它使用HashMap的键来存储元素,每个元素都映射到一个固定的虚拟值(HashMap的值部分)。HashSet利用HashMap键的唯一性来确保其元素的唯一性,并且通过哈希表实现,因此具有很高的查找和插入效率。当向HashSet添加元素时,实际上是将元素作为HashMap的键添加,而对应的值则是一个默认为Object对象。

6.HashMap与HashTable有什么区别?

  • 线程安全:HashTable是线程安全的,所有方法都是同步的,而HashMap是非线程安全的。
  • 性能:因为HashTable的方法是同步的,所以在单线程环境下比HashMap性能低。
  • 空值:HashMap允许键和值为null(可以有多个null值,但只能有一个null键),HashTable不允许键或值为null。
  • 迭代器:HashMap的迭代器是快速失败的(fail-fast:是指在使用迭代器遍历集合时,如果在遍历过程中检测到集合被修改(例如添加或删除元素),迭代器会立即抛出一个 ConcurrentModificationException,以避免遍历时数据不一致的情况。),而HashTable的枚举器不是。

7.Collection和Collecions有什么区别?

  • Collection:是一个接口,它是各种集合结构的根接口,提供了对集合对象进行基本操作的通用接口方法。
  • Collections:是一个包含有关集合操作的静态方法的工具类,如排序、搜索等。

8.comparable和comparator的区别?

  • Comparable:是一个用于自然排序的接口,通常用在实体类中。
    • Comparable 接口包含一个方法 compareTo(Object o),该方法返回一个整数,表示当前对象与参数对象的比较结果。
      • 返回负数:当前对象小于参数对象。
      • 返回零:当前对象等于参数对象。
      • 返回正数:当前对象大于参数对象
    • 使用场景:当需要为对象定义默认排序规则时,使用 Comparable。例如,实现 Comparable 的 String 类会根据字母顺序进行排序。
public class Person implements Comparable<Person> {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public int compareTo(Person other) {
        return this.age - other.age; // 按照年龄升序排序
    }
}
  • Comparator 接口:是一个用于自定义排序的接口,通常用于创建单独的比较器类。
    • 方法:Comparator 接口包含一个方法 compare(Object o1, Object o2),用于比较两个对象。
      • 返回负数:o1 小于 o2
      • 返回零:o1 等于 o2
      • 返回正数:o1 大于 o2
    • 使用场景:当需要为对象定义多种排序规则时,使用 Comparator。比如一个类可以按年龄排序,也可以按姓名排序,这时就可以使用不同的 Comparator 实现。

下面展示一些 内联代码片

public class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // Getter methods...
}

// 按照姓名排序的 Comparator
class NameComparator implements Comparator<Person> {
    @Override
    public int compare(Person p1, Person p2) {
        return p1.getName().compareTo(p2.getName());
    }
}

// 按照年龄排序的 Comparator
class AgeComparator implements Comparator<Person> {
    @Override
    public int compare(Person p1, Person p2) {
        return p1.getAge() - p2.getAge();
    }
}

9.Collections工具类中的sort()方法如何比较元素?

  • Collections.sort()方法比较元素的方式取决于传入的集合元素类型
    • 如果集合元素实现了Comparable接口,sort()方法会使用这些元素的compareTo()进行自然排序。
    • 可以重载sort()方法,传入一个自定义的Comparator对象,这时候会使用该Comparator的compare()方法来比较元素,实现自定义排序。

10.如何实现数组和List之间的转换?

  • 数组转list,可以使用jdk自动的一个工具类Arrars,里面有一个asList方法
    可以转换为数组
  • List 转数组,可以直接调用list中的toArray方法,需要给一个参数,指定数
    组的类型,需要指定数组的长度

11.HashMap的jdk1.7和jdk1.8有什么区别?

  • JDK1.8之前采用的拉链法,数组+链表
  • JDK1.8之后采用数组+链表+红黑树,当链表的长度超过8,并且当前的数组长度至少为64时,会将链表转换为红黑树

12.HashMap的扩容机制

  • 在添加元素或初始化的时候需要调用resize方法进行扩容,第一次添加数据初始化
    数组长度为16,以后每次扩容都是达到了扩容阈值(数组长度 * 0.75)
  • 每次扩容的时候,都是扩容之前容量的2倍
  • 扩容之后,会新创建一个数组,需要把老数组中的数据挪动到新的数组中

13.为何HashMap的数组长度一定是2的次幂?

  • hashmap这么设计主要有两个原因
    • 计算索引时效率更高:如果是 2 的 n 次幂可以使用位与运算代替取模
    • 扩容时重新计算索引效率更高:在进行扩容是会进行判断 hash值按位与运算旧数组长租是否 == 0,如果等于0,则把元素留在原来位置 ,否则新位置是等于旧位置的下标+旧数
      组长度

14.ArrayList的扩容机制

  • ArrayList 默认的初始容量是 10,当添加新元素时,如果当前的数组长度已经满了(即容量已满),ArrayList 会自动调用 ensureCapacity() 方法进行扩容。新数组的容量为原数组容量的 1.5 倍。具体计算方式是:newCapacity = oldCapacity + (oldCapacity >> 1),这意味着新容量是原容量加上原容量的一半。新数组创建后,ArrayList 会将旧数组中的元素复制到新数组中。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值