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

embedded/2024/10/18 8:22:20/

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 会确定新元素应该插入的位置。
  1. 创建新节点:LinkedList 会创建一个新的节点(通常是一个内部类),该节点包含要添加的元素以及指向其前一个和后一个元素的引用(最初这些引用可能为空或指向其他元素)。
  1. 更新链接:根据插入位置,LinkedList 会更新新节点与其前一个和后一个元素的链接。具体来说,它会将新节点的 prev 引用指向前一个元素(如果存在的话),将新节点的 next 引用指向后一个元素(如果存在的话),并相应地更新前一个和后一个元素的引用。
  1. 维护大小: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。
  1. 删除指定索引的节点:遍历链表找到指定索引的节点,然后更新其前后节点的引用以断开连接。

访问操作

访问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编程中逐渐被其他并发集合类所取代。


http://www.ppmy.cn/embedded/38191.html

相关文章

VitePress快速上手

完整教程:https://blog.share888.top/note/front-end/vitePress/01-vitePress%E5%AE%89%E8%A3%85.html https://blog.share888.top/ VitePress快速上手 官方文档:https://vitepress.dev/zh/guide/markdown VitePress中文网:https://vitejs…

SparkSql介绍

概述 SparkSQL,顾名思义,就是Spark生态体系中的构建在SparkCore基础之上的一个基于SQL的计算模块。SparkSQL的前身不叫SparkSQL,而叫Shark,最开始的时候底层代码优化,sql的解析、执行引擎等等完全基于Hive&#xff0c…

Leetcode—706. 设计哈希映射【简单】(constexpr)

2024每日刷题(127) Leetcode—706. 设计哈希映射 数组实现代码 class MyHashMap { public:MyHashMap() {memset(arr, -1, sizeof(arr));}void put(int key, int value) {arr[key] value;}int get(int key) {if(arr[key] -1) {return -1;} return arr…

【3dmax笔记】026:挤出和壳修改器的使用

文章目录 一、修改器二、挤出三、壳 一、修改器 3ds Max中的修改器是一种强大的工具,用于创建和修改复杂的几何形状。这些修改器可以改变对象的形状、大小、方向和位置,以生成所需的效果。以下是一些常见的3ds Max修改器及其功能: 挤出修改…

CTF-reverse二维四向迷宫路径求解

二维四向迷宫是一个re中的常考点,说不上难,但也不简单,本篇记录了常规的二维四向迷宫解题套路以及帮助快速解题的脚本 可能你看我的教程会觉得十分繁琐,但实际只要你用了一次熟练之后,基本都是拿到迷宫就一题一分钟解决…

【智能楼宇秘籍】一网关多协议无缝对接BACnet+OPC+MQTT

在繁华的都市中心,一座崭新的大型商业综合体拔地而起,集购物、餐饮、娱乐、办公于一体,是现代城市生活的缩影。然而,这座综合体的幕后英雄——一套高度集成的楼宇自动化系统,正是依靠多功能协议网关,实现了…

深入了解 NumPy:深度学习中的数学运算利器

文章目录 1. 导入NumPy2. 创建NumPy数组3. 数组的算术运算4. N维数组4.1 创建和操作多维数组4.2 高维数组 5. NumPy的广播功能5.1 基本广播示例5.2 更复杂的广播示例 6. 访问数组元素6.1 基于索引的访问6.2 遍历数组6.3 基于条件的访问6.4 高级索引6.5 性能考虑 在深度学习和数…

关于Git的commit message规范

前几天在提交代码的时候突然发现自己的commit message写的有点问题,然后到网上查了下发现Git的commit message也是有规范的,下面我总结了三条我认为最重要的。 1.commit message需要简洁明了,突出变更的目的 2.提交信息的前缀用来表示你的这…