Java中ArrayList、LinkedList和Vector的底层原理

ArrayList

Java中的ArrayList底层原理主要涉及其数据结构、扩容机制、线程安全性以及元素存储和访问方式。以下是对ArrayList底层原理的总结:

数据结构

ArrayList的底层数据结构是一个动态数组。这意味着ArrayList可以根据需要自动增长其容量,从而存储更多的元素。实际上,ArrayList内部维护了一个Object类型的数组,用于存储列表中的元素。

扩容机制

当向ArrayList中添加元素时,如果当前数组的容量不足以容纳新元素,ArrayList会自动进行扩容。扩容操作涉及创建一个新的、容量更大的数组,并将原数组中的元素复制到新数组中。为了减少扩容时数据的拷贝次数,ArrayList在扩容时通常会选择一个比当前容量大的多的新容量(通常是当前容量的1.5倍)。

ArrayList的默认初始容量为10,但你可以在创建ArrayList实例时指定一个初始容量。如果你知道将要存储的元素数量,提前指定一个合适的初始容量可以减少扩容时数据的拷贝次数,从而提高性能。

线程安全性

ArrayList是非线程安全的。这意味着如果多个线程同时修改ArrayList(例如,一个线程在遍历列表时,另一个线程在添加或删除元素),可能会导致数据不一致或其他并发问题。因此,在多线程环境中使用ArrayList时,需要进行额外的同步操作来确保线程安全。

元素存储和访问

由于ArrayList底层是一个数组,因此元素的存储和访问都基于数组的索引。添加元素时,ArrayList会将新元素存储在数组的末尾(或指定的索引位置),并更新列表的大小。访问元素时,ArrayList会根据提供的索引直接访问数组中的相应位置。由于数组在内存中是连续存储的,因此访问数组元素的时间复杂度是O(1)。

然而,由于ArrayList在添加元素时可能需要扩容并复制数组,因此在列表的中间位置添加或删除元素的时间复杂度可能较高(最坏情况下为O(n))。相比之下,在列表的开头或结尾添加或删除元素的时间复杂度通常是O(1),因为这两个位置的操作不需要移动其他元素。

总的来说,ArrayList是一个功能强大且灵活的数据结构,适用于需要在内存中存储和访问大量元素的情况。但是,在多线程环境中使用时需要注意线程安全问题

LinkedList

Java中的LinkedList底层原理主要基于双向链表 数据结构。以下是关于LinkedList底层原理的总结:
数据结构

LinkedList使用双向链表作为其内部数据结构。双向链表中的每个节点(Node)都包含三个部分:

元素值(item):存储链表中的实际数据。

前节点引用(prev):指向链表中当前节点之前的节点。对于链表的第一个节点,此引用通常为null。

后节点引用(next):指向链表中当前节点之后的节点。对于链表的最后一个节点,此引用通常为null。

插入操作

在 LinkedList 中,增加操作同样依赖于其双向链表的特性。当你尝试向列表中添加一个新元素时,LinkedList 实际上会:

  1. 确定插入位置:根据提供的索引或方法(如 addFirst、addLast),LinkedList 会确定新元素应该插入的位置。
  2. 创建新节点:LinkedList 会创建一个新的节点(通常是一个内部类),该节点包含要添加的元素以及指向其前一个和后一个元素的引用(最初这些引用可能为空或指向其他元素)。
  3. 更新链接:根据插入位置,LinkedList 会更新新节点与其前一个和后一个元素的链接。具体来说,它会将新节点的 prev 引用指向前一个元素(如果存在的话),将新节点的 next 引用指向后一个元素(如果存在的话),并相应地更新前一个和后一个元素的引用。
  4. 维护大小:LinkedList 还会更新其内部的大小计数器,以反映列表中元素数量的增加。

注意事项

1.由于 LinkedList 是双向链表,因此在列表的任何位置进行删除或增加操作的时间复杂度都是 O(n),其中 n 是列表的大小。但是,由于不需要移动元素(如 ArrayList 在列表中间进行删除或增加时所做的那样),这些操作通常更快。

2.LinkedList 还提供了在列表开头和结尾进行高效删除和增加操作的方法(如 addFirst、addLast、removeFirst、removeLast),这些操作的时间复杂度是 O(1)。

3.由于 LinkedList 需要额外的空间来存储每个元素的引用,因此它在内存使用方面可能比基于数组的列表更高。

删除操作

在LinkedList中删除元素时,需要找到要删除的节点,并更新其前后节点的引用以断开连接。

  1. 删除头部节点:将头节点引用更新为其next节点,并设置新头节点的prev引用为null(如果新头节点不是null的话)。
    删除尾部节点:遍历链表找到倒数第二个节点,将其next引用设置为null。
  2. 删除指定索引的节点:遍历链表找到指定索引的节点,然后更新其前后节点的引用以断开连接。

访问操作

访问LinkedList中的元素通常需要通过遍历链表来实现,因为链表中的元素在内存中不是连续存储的。但是,由于LinkedList同时提供了向前和向后遍历的能力(通过ListIterator),因此访问操作在某些情况下可能比其他链表结构更高效。

线程安全性

与ArrayList和Vector不同,LinkedList本身不是线程安全的。如果多个线程同时修改LinkedList,则需要在外部进行同步操作以确保线程安全。但是,由于LinkedList的双向链表结构,它在并发修改时通常比基于数组的列表(如ArrayList)具有更好的性能。

性能特点

插入和删除操作 :在链表的开头或结尾进行插入和删除操作的时间复杂度通常为O(1),因为只需要修改几个引用即可。在链表的中间进行插入和删除操作的时间复杂度为O(n),其中n是链表的长度,因为需要遍历链表以找到正确的位置。
访问操作 :访问链表中的特定元素(如通过索引)需要遍历链表,因此其时间复杂度为O(n)。
空间效率:由于每个节点都需要额外的空间来存储引用(prev和next),因此LinkedList的空间效率略低于基于数组的列表(如ArrayList)。但是,这种差异在大多数情况下都是可以接受的,特别是当插入和删除操作的性能更重要时。

Vector

Java中的Vector类的底层原理主要涉及其数据结构、扩容机制、线程安全性以及元素存储和访问方式。以下是对Vector底层原理的总结:
数据结构

Vector的底层数据结构同样是一个动态数组。它内部维护了一个Object类型的数组,用于存储列表中的元素。与ArrayList类似,Vector也可以根据需要动态地增长或缩小其容量。

扩容机制 >

当向Vector中添加元素时,如果当前数组的容量不足以容纳新元素,Vector会自动进行扩容。扩容操作涉及创建一个新的、容量更大的数组,并将原数组中的元素复制到新数组中。扩容的倍数通常是原容量的两倍,但具体实现可能因Java版本或JVM实现而异。

线程安全性

Vector是一个线程安全的类。它的所有方法(包括添加、删除、获取元素等操作)都是同步的,这意味着在多线程环境下,多个线程可以同时访问和修改Vector对象,而不会产生数据竞争和不一致的问题。然而,这种线程安全性是以牺牲性能为代价的,因为同步操作会引入额外的开销。

元素存储和访问

与ArrayList一样,Vector使用数组的索引来存储和访问元素。添加元素时,Vector会将新元素存储在数组的末尾(或指定的索引位置),并更新列表的大小。访问元素时,Vector会根据提供的索引直接访问数组中的相应位置。

性能考虑

虽然Vector提供了线程安全性,但在单线程环境下,由于同步操作的开销,它的性能通常不如ArrayList。因此,在不需要线程安全性的情况下,使用ArrayList通常是一个更好的选择。如果需要线程安全性,可以考虑使用Collections.synchronizedList()方法将ArrayList包装为线程安全的列表,或者使用CopyOnWriteArrayList等并发集合类。

总的来说,Vector是一个基于动态数组实现的线程安全的列表类。然而,由于同步操作的开销和性能考虑,它在现代Java编程中逐渐被其他并发集合类所取代。

相关推荐
吾日三省吾码1 小时前
JVM 性能调优
java
LNTON羚通2 小时前
摄像机视频分析软件下载LiteAIServer视频智能分析平台玩手机打电话检测算法技术的实现
算法·目标检测·音视频·监控·视频监控
弗拉唐2 小时前
springBoot,mp,ssm整合案例
java·spring boot·mybatis
oi773 小时前
使用itextpdf进行pdf模版填充中文文本时部分字不显示问题
java·服务器
少说多做3433 小时前
Android 不同情况下使用 runOnUiThread
android·java
知兀3 小时前
Java的方法、基本和引用数据类型
java·笔记·黑马程序员
哭泣的眼泪4083 小时前
解析粗糙度仪在工业制造及材料科学和建筑工程领域的重要性
python·算法·django·virtualenv·pygame
蓝黑20204 小时前
IntelliJ IDEA常用快捷键
java·ide·intellij-idea
Ysjt | 深4 小时前
C++多线程编程入门教程(优质版)
java·开发语言·jvm·c++