【程序员快速复习系列】JAVA集合总结

0. 前言:

程序员面试本是一件再平常不过的事情,记得刚毕业的时候面试题背的滚瓜烂熟。但是在职程序员面试却是另一回事了,我们往往没有太多时间复习,特别是大龄程序员,工作日忙于工作,周末还要照顾家庭,一旦面临被优化的风险就很被动,难以在短时间内复习并找到工作。不要问我是怎么知道的,都是切身体会,在复习的过程中我也走了不少弯路,所幸最终结果令自己满意。
为了不让和我一样的程序员遇到同样的问题,我打算写这一系列的文章,这些文章不会像其他面经一般大而全,这些文章仅记录我在复习过程中认为重要的知识点,如果能帮助到你就太好了。

1. Java有哪些顶层集合接口

一句话回答:Java集合框架主要包含四个顶层接口:CollectionListSetMap

细节解释:

  • Collection:是最基本的集合接口,它是一个扩展自Iterable接口的根接口。它定义了集合的基本操作,如添加、删除和遍历元素。
  • List:继承自Collection接口,是一个有序的集合,允许存储重复元素。List接口提供了一些额外的操作,如按索引访问元素、添加元素到特定位置等。
  • Set:同样继承自Collection接口,是一个不允许存储重复元素的集合。Set接口主要用于存储唯一元素,并且没有定义元素的顺序。
  • Map:是一个与Collection接口并列的接口,用于存储键值对。它不是集合的一部分,但通常与集合一起使用,因为它可以作为集合的属性存储键值对。Map接口提供了键值对的存储和检索功能。

以下是上述接口常见的实现类:

  • List接口下的实现类(继承自Collection):

    • ArrayList:基于动态数组实现,提供快速随机访问。适合随机访问频繁的场景。
    • LinkedList:基于链表实现,提供快速的插入和删除操作。适合插入和删除操作频繁的场景。
    • Vector:类似于ArrayList,但它是线程安全的。
    • Stack:继承自Vector,实现了栈的功能,LIFO(后进先出)。
  • Set接口下的实现类(继承自Collection):

    • HashSet:基于HashMap实现,提供快速查找和插入操作,但不允许重复元素。
    • LinkedHashSet:类似于HashSet,但维护元素的插入顺序。
    • TreeSet:基于NavigableMap实现,可以按照自然顺序或自定义顺序对元素进行排序。
  • Map接口下的实现类

    • HashMap:基于哈希表实现,提供快速的键值对查找和插入操作。键值对无序。
    • LinkedHashMap:类似于HashMap,但维护插入顺序或访问顺序。
    • TreeMap:基于红黑树实现,可以按照键的自然顺序或自定义顺序对键值对进行排序。
    • Hashtable:类似于HashMap,但它是线程安全的,不允许空键和空值。

这些实现类提供了不同的性能特性和功能,开发者可以根据具体需求选择合适的集合类。
下文将挑选几个个人认为常考,但是平时容易疏于学习的类介绍。

2. Vector 和Hashtable如何保证线程安全

一句话回答:VectorHashtable 都是通过同步机制来实现线程安全的集合类。

细节解释:

  • Vector

    • Vector 类似于 ArrayList,但它是同步的。这意味着它的方法被 synchronized 关键字修饰,从而在多线程环境中,每次只有一个线程可以访问这些方法。
    • 这种同步机制虽然确保了线程安全,但也带来了性能开销,特别是在高并发的情况下。因此,在单线程或低并发的场景下,使用 ArrayList 可能更合适。
    • 示例代码:
      Vector<String> vector = new Vector<>();
      vector.add("Java");
      vector.add("Python");
      
  • Hashtable

    • Hashtable 类似于 HashMap,但它是线程安全的。Hashtable 通过在构造函数中传入一个 synchronized 参数来实现同步。
    • 所有公共方法和内部迭代器都被 synchronized 关键字修饰,确保了在多线程环境中的线程安全。
    • 由于同步机制,Hashtable 的性能通常不如 HashMap。另外,Hashtable 不允许空键和空值,这也是与 HashMap 的一个区别。
    • 示例代码:
      Hashtable<String, String> hashtable = new Hashtable<>();
      hashtable.put("Java", "JVM");
      hashtable.put("Python", "CPython");
      

这两种类通过同步机制确保了在多线程环境中的线程安全,但同时也牺牲了一些性能。在需要线程安全且并发需求不高的场景下,可以考虑使用它们。对于高并发的场景,推荐使用 Collections.synchronizedListCollections.synchronizedMap 包装原始集合,或者使用 ConcurrentHashMap 等并发集合类。

3. ConcurrentHashMap原理

一句话回答:ConcurrentHashMap 通过分段锁(Segmentation)和细粒度锁(Fine-grained locking)来实现高并发环境下的线程安全。

细节解释:
ConcurrentHashMap 是 Java 并发包 java.util.concurrent 中的一个线程安全的哈希表实现。以下是它的一些关键原理:

  1. 分段锁(Segmentation)
    • ConcurrentHashMap 的早期版本(如 Java 5 和 Java 6)中,它使用分段锁来实现线程安全。这意味着哈希表被分为多个段(Segment),每个段可以独立地被锁定。这样,当一个线程在操作一个段时,其他线程可以同时操作其他段,从而提高了并发性能。
  2. 细粒度锁(Fine-grained locking)
    • 从 Java 7 开始,ConcurrentHashMap 采用了细粒度锁的策略。它不再使用分段锁,而是使用内部的 Node 数组来存储键值对。每个 Node 可以被视为一个锁,当进行修改操作时,只需要锁定涉及到的那个 Node,而不是整个哈希表。
  3. 哈希算法优化
    • ConcurrentHashMap 使用了一些优化的哈希算法来减少哈希碰撞,从而减少链表的长度,提高查找效率。
  4. 数据结构
    • 当哈希表中的元素数量达到一定阈值时,ConcurrentHashMap 会将链表转换为红黑树,以保持操作的对数时间复杂度。
  5. volatile 变量
    • ConcurrentHashMap 使用 volatile 关键字来保证变量的可见性,确保线程间对共享变量的修改能够立即被其他线程看到。
  6. 原子类
    • 它使用 java.util.concurrent.atomic 包中的原子类(如 AtomicInteger)来支持无锁的线程安全操作。
  7. CAS 操作
    • ConcurrentHashMap 在内部使用 compare-and-swap(CAS)操作来实现无锁的更新,进一步提高了并发性能。

ConcurrentHashMap 通过这些机制实现了高效的并发访问,适用于高并发的多线程环境。它提供了接近 HashMap 的性能,同时保持了线程安全性。

4. TreeMap原理

一句话回答:TreeMap 是基于红黑树实现的有序映射表,它保证数据按照自然顺序或自定义顺序进行排序。

细节解释:
TreeMap 是 Java 中 Map 接口的一个实现,它内部使用红黑树(一种自平衡二叉搜索树)来存储键值对。以下是 TreeMap 的一些关键原理:

  1. 红黑树结构
    • 红黑树是一种特殊的二叉搜索树,它通过颜色和特定的规则来保持树的平衡,确保树的高度大致为对数级别,从而提供快速的查找、插入和删除操作。
  2. 排序保证
    • TreeMap 保证所有的键都会按照自然顺序(实现 Comparable 接口的键)或通过构造函数提供的 Comparator 来排序。
  3. 自平衡特性
    • 红黑树的自平衡特性确保了即使在大量插入和删除操作后,树的查找效率仍然保持在对数时间复杂度。
  4. 旋转操作
    • 红黑树通过左旋和右旋操作来调整树的结构,以保持其平衡。这些旋转操作是红黑树插入和删除过程中的关键步骤。
  5. 颜色标记
    • 每个节点都有红色或黑色两种颜色标记。红黑树通过颜色标记和特定的规则来确保树的平衡。
  6. 插入和删除过程
    • 当插入或删除节点时,TreeMap 会检查红黑树的平衡性,并执行必要的旋转操作来恢复平衡。
  7. 迭代器
    • TreeMap 提供了两种类型的迭代器:KeySet 迭代器和 EntrySet 迭代器。这些迭代器可以按照键的排序顺序遍历键值对。
  8. 性能特点
    • 由于红黑树的自平衡特性,TreeMap 在插入、删除和查找操作上都具有较好的性能,时间复杂度为 O(log n)。

TreeMap 适用于需要有序映射的场景,例如实现有序的键值存储、实现有序集合等。它的有序特性使得它在处理排序数据时非常有用。

5. HashSet原理

一句话回答:HashSet 是基于 HashMap 实现的,它通过哈希表来存储唯一的元素,并保证元素的唯一性。

细节解释:
HashSet 是 Java 中 Set 接口的一个实现,其内部使用 HashMap 来存储元素。以下是 HashSet 的一些关键原理:

  1. 基于HashMap
    • HashSet 实际上是一个 HashMap 的封装,其中 HashMap 的键是 HashSet 存储的元素,而值是一个固定的虚拟对象。
  2. 元素唯一性
    • 由于 HashMap 的键是唯一的,所以 HashSet 能够保证存储的元素也是唯一的。
  3. 哈希函数
    • HashSet 使用元素的 hashCode() 方法来计算哈希值,然后根据这个哈希值将元素存储在 HashMap 的不同位置。
  4. 冲突解决
    • 当两个元素的哈希值相同时,HashSet 会使用 HashMap 的机制来解决冲突,例如链表或红黑树。
  5. 性能特点
    • HashSet 的基本操作(如添加、删除和查找)通常具有常数时间复杂度 O(1),这是通过哈希表的快速查找特性实现的。
  6. 迭代器
    • HashSet 提供迭代器来遍历集合中的元素,迭代器的顺序是不确定的,因为 HashMap 不保证元素的顺序。
  7. 空元素
    • HashSet 允许存储一个 null 元素,但只能存储一个。
  8. 容量和加载因子
    • HashSet 可以选择初始容量和加载因子,这些参数会影响 HashMap 的性能和内存使用。
  9. 扩容机制
    • HashSet 存储的元素数量超过当前容量和加载因子的乘积时,HashMap 会进行扩容操作,重新计算所有元素的哈希值并重新分配位置。

HashSet 是实现集合去重的常用数据结构,适用于需要快速查找和插入的场景,同时保持元素的唯一性。

6. 集合类的扩容原理

一句话回答:Java 中的集合类,如 ArrayListHashSetHashMap,通过动态扩容机制来适应不断增长的元素数量,它们在达到一定阈值时会增加容量。

细节解释:

  1. ArrayList 的扩容原理
    • ArrayList 基于动态数组实现。当添加元素时,如果当前数组已满,ArrayList 会创建一个新的数组,其容量是原始容量的1.5倍(newCapacity = oldCapacity + (oldCapacity >> 1))。
    • 然后将旧数组中的元素复制到新数组中。这种机制确保了 ArrayList 在添加元素时的摊销时间复杂度为 O(1)。
  2. HashMap 的扩容原理
    • HashMap 基于哈希表实现。当元素数量超过容量与加载因子(capacity * load factor)的乘积时,HashMap 会进行扩容。
    • 扩容包括创建一个新的哈希表,其容量通常是原始容量的两倍,并将所有元素重新映射到新哈希表上。这个过程称为“rehashing”。
  3. HashSet 的扩容原理
    • HashSet 内部使用 HashMap 来存储元素,因此其扩容原理与 HashMap 类似。当元素数量达到阈值时,HashSet 会触发 HashMap 的扩容机制。
  4. LinkedList 的扩容原理
    • LinkedList 基于双向链表实现,它没有固定的扩容机制。由于链表的特性,添加元素通常只需要 O(1) 时间复杂度,不需要像数组那样进行复制。
  5. 扩容的影响
    • 扩容操作是昂贵的,因为它涉及到创建新的内部数据结构和复制现有元素。因此,合理地选择初始容量和加载因子可以减少扩容的次数,提高性能。
  6. 加载因子
    • 加载因子是一个衡量 HashMap 填充程度的参数,它影响 HashMap 进行扩容的时机。加载因子越小,HashMap 在元素数量增加时扩容越频繁,但查找效率可能更高。

集合类的扩容原理是为了在动态数据集中保持操作效率,同时避免过度使用内存。合理地选择初始容量和加载因子可以优化性能和内存使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值