ArrayList 和 LinkedList 都是 Java 中常用的动态数组实现,都实现了 List 接口,但它们在内部数据结构和性能方面有所不同:
ArrayList 是基于动态数组的数据结构,它允许快速随机访问。数组的大小在创建时是固定的,当数组满时,ArrayList 会自动扩容,创建一个新的更大的数组,并将原数组的内容复制到新数组中。LinkedList 是基于双向链表的数据结构,每个元素都是一个节点,包含数据和两个指针,分别指向前一个节点和后一个节点。链表的特点是元素可以灵活地插入和删除,不需要移动其他元素。ArrayList 提供了更快的随机访问和顺序访问速度,时间复杂度为 O(1)。但是,在数组中间插入或删除元素时,需要移动目标位置后的所有元素,时间复杂度为 O(n)。LinkedList 在链表中间插入和删除元素非常快,时间复杂度为 O(1)。但是,随机访问较慢,时间复杂度为 O(n),因为需要从头节点或尾节点开始遍历链表。ArrayList 由于是基于数组的,需要连续的内存空间,而且在扩容时可能会浪费一些内存(因为新数组可能会留有未使用的空间)。LinkedList 每个元素都需要额外的内存来存储指向前一个和后一个节点的指针,因此在存储大量元素时,可能会比 ArrayList 使用更多的内存。ArrayList 是更好的选择。LinkedList 更适合。ArrayList 还是 LinkedList 取决于具体的应用场景和对性能的需求。在实际开发中,ArrayList 由于其优异的随机访问性能,通常是最常用的列表实现。只有在特定的场景下,当链表的特性(如频繁的插入和删除操作)能带来明显的性能优势时,才会考虑使用 LinkedList。Java 提供了丰富的集合框架(Collection Framework),用于存储和管理对象集合。这些集合可以分为几个主要类别:
ArrayList:基于动态数组的数据结构,提供快速的随机访问和顺序访问。LinkedList:基于双向链表的数据结构,提供快速的插入和删除操作。Vector:和 ArrayList 类似,但是是同步的,适用于多线程环境。不过,由于同步带来的性能开销,通常建议使用 ArrayList 并自行同步,或者使用并发集合。HashSet:基于哈希表实现,提供快速的插入、删除和查找操作。LinkedHashSet:具有 HashSet 的查找效率,并且维护了插入顺序。TreeSet:基于红黑树实现,可以确保元素处于排序状态。EnumSet:用于存放枚举类型,内部使用位向量实现,非常高效。PriorityQueue:基于优先级堆的无界优先队列。LinkedList:也可以用作队列,因为实现了 Queue 接口。ArrayDeque:是一个可扩容的双端队列,可以作为栈或队列使用。ArrayDeque:基于数组的双端队列实现。LinkedList:也可以用作双端队列。HashMap:基于哈希表实现,提供快速的查找、插入和删除操作。LinkedHashMap:维护了插入顺序的 HashMap。TreeMap:基于红黑树实现,可以确保键处于排序状态。Hashtable:和 HashMap 类似,但是是同步的,适用于多线程环境。同样,由于同步带来的性能开销,通常建议使用 HashMap 并自行同步,或者使用并发集合。EnumMap:键为枚举类型的特殊映射,内部使用数组实现,非常高效。ConcurrentHashMap:线程安全的 HashMap。CopyOnWriteArrayList:线程安全的 ArrayList,适用于读多写少的场景。CopyOnWriteArraySet:线程安全的 Set,适用于读多写少的场景。BlockingQueue:线程安全的队列,用于生产者-消费者模式。ConcurrentLinkedQueue:线程安全的非阻塞队列。Stack:栈是一个后进先出(LIFO)的数据结构,Java 中没有单独的 Stack 类,而是使用 Deque 接口的实现类,如 ArrayDeque,来实现栈的功能。java.util 包中,除了并发集合,它们大多在 java.util.concurrent 包中。Java 的集合框架提供了丰富的API,使得操作集合变得非常方便和灵活。ArrayList 是 Java 中使用最广泛的动态数组实现之一。它允许我们动态地添加和删除元素,而不需要担心数组的固定大小。ArrayList 的扩容机制是其核心特性之一,下面是它的工作原理:
ArrayList 对象时,可以指定一个初始容量,如果没有指定,默认容量为 10。ArrayList 中,并且数组的当前大小不足以容纳新元素时,ArrayList 需要进行扩容。ArrayList 通过一个内部数组来存储元素。当需要扩容时,它会创建一个新的更大的数组(通常是原数组大小的 1.5 倍),然后将原数组中的所有元素复制到新数组中。System.arraycopy() 方法实现的,它是一个本地方法,可以高效地复制数组。ArrayList 提供了动态添加元素的便利,但在大量添加元素的场景下,频繁的扩容可能会影响性能。ArrayList 时指定一个足够大的初始容量,这样可以减少扩容的次数。ArrayList 没有提供自动缩容的功能。如果需要减少存储空间的使用,可以通过调用 trimToSize() 方法来缩小数组的大小以匹配当前元素数量。ArrayList 能够灵活地处理元素数量的变化的关键,但它也带来了性能上的考虑。在实际使用中,根据应用场景和性能要求,合理地管理 ArrayList 的容量是非常重要的。HashMap 是 Java 中使用哈希表实现的映射接口,它存储键值对(key-value pairs)。HashMap 的扩容机制是其核心特性之一,用于处理哈希表中的哈希冲突和提高性能。下面是 HashMap 的扩容机制的工作原理:
HashMap 时,可以指定初始容量和负载因子。初始容量是哈希表中的桶数,负载因子是哈希表填充程度的度量标准。HashMap 中的元素数量达到容量和负载因子的乘积时,即 HashMap 的实际大小超过了负载因子与当前容量的乘积,HashMap 就会进行扩容。HashMap 中,哈希表的每个桶可能包含一个链表或一棵红黑树。当桶中的元素数量超过一定阈值时,链表会转换为红黑树,以提高搜索效率。HashMap 的性能,因为每次扩容都需要重新哈希和复制所有元素。HashMap 时指定一个足够大的初始容量。HashMap 的扩容机制是为了保持哈希表的性能和效率,同时处理哈希冲突。在实际使用中,根据应用场景和性能要求,合理地管理 HashMap 的容量是非常重要的。在 Java 的 HashMap 中,初始容量(Initial Capacity)和负载因子(Load Factor)是两个重要的参数,它们在创建 HashMap 时可以进行调整,以优化性能和内存使用。
HashMap 创建时的桶数,即内部数组的大小。默认的初始容量是 16。HashMap 使用哈希值与数组长度的模运算来定位元素,2 的幂可以使得这个运算更高效。HashMap 填充程度的一个指标,它决定了 HashMap 何时进行扩容。负载因子的默认值是 0.75。HashMap 中的元素数量达到负载因子与内部数组大小的乘积时,HashMap 就会进行扩容,通常是容量翻倍。HashMap 和 Hashtable 都是 Java 中用于存储键值对的数据结构,但它们之间有一些关键的区别:
HashMap 不是同步的,如果多个线程同时访问并修改 HashMap,必须外部同步。Hashtable 是同步的,它所有的公共方法都是同步的,适用于多线程环境。但是,这会带来性能开销,因为它需要锁定整个表来防止并发修改。HashMap 允许使用一个 null 键和多个 null 值。Hashtable 不允许使用 null 键或 null 值。HashMap 提供了更快的迭代速度,并且迭代顺序是不确定的。Hashtable 的迭代速度较慢,并且迭代顺序也是不确定的。HashMap 继承自 AbstractMap 类。Hashtable 继承自 Dictionary 类,这是一个已经被废弃的类。HashMap 通常提供比 Hashtable 更好的性能,因为 HashMap 的实现更加优化。Hashtable 是早期 Java 版本中的实现,而 HashMap 是在 Java 2(JDK 1.2)中引入的。HashMap 的底层数据结构是一个数组,数组的每个元素是一个链表(在 Java 8 及更高版本中,链表在达到一定长度后会转换为红黑树以提高性能)。这个数组被称为桶(bucket)数组,每个桶对应一个哈希值。当插入一个键值对时,首先会根据键的哈希值计算出桶的索引,然后将键值对存储在相应的桶中的链表(或红黑树)中。如果两个不同的键产生了相同的哈希值,会发生哈希冲突,这时会在同一个桶中的链表(或红黑树)中存储这两个键值对。Hashtable 的许多特性已经被 HashMap 替代,并且 Hashtable 的同步性能较差,通常建议在不需要线程安全的场景下使用 HashMap,在需要线程安全的场景下使用 ConcurrentHashMap。ConcurrentHashMap 是 Java 中的一个线程安全的映射实现,它位于 java.util.concurrent 包中。ConcurrentHashMap 的底层数据结构在 Java 8 及其之后的版本中经历了一些变化,下面是主要的组成:
ConcurrentHashMap 中的元素以节点(Node)的形式存储,每个节点包含键、值、哈希值和指向下一个节点的指针。ConcurrentHashMap 使用了一个分段锁(Segment)的数据结构,其中内部数组被分割成多个段,每个段是一个独立的锁结构,用于减少锁竞争。ConcurrentHashMap 的底层数据结构被重新设计,去掉了分段锁,转而使用一个大的桶数组(也称为哈希桶数组或哈希表),类似于 HashMap 的结构。ConcurrentHashMap 使用了无锁算法和 CAS 操作来实现并发安全,这是一种乐观锁策略,它允许在不加锁的情况下对数据进行修改,只有当预期值与实际值相同时才进行更新。ConcurrentHashMap 使用了细粒度的同步机制,只对哈希桶数组中的特定桶进行锁定,而不是整个映射,这大大减少了锁竞争,提高了并发性能。ConcurrentHashMap 的设计目的是提供一种高效的线程安全映射,它在多线程环境中提供了良好的并发性能,同时避免了 Hashtable 的全局锁带来的性能瓶颈。