目录
✨探索Java基础 数组、ArrayList和LinkedList✨
一、总体介绍
二、分别介绍
1. 数组(Array)
2. ArrayList
3. LinkedList
三、区别总结
四、常见面试题
如何选择使用数组、ArrayList和LinkedList?
如何实现线程安全的ArrayList?
解释ArrayList的底层实现及其扩容机制
LinkedList的插入和删除操作为什么比ArrayList快?
如何在不使用额外空间的情况下,反转一个链表?
✨探索Java基础 数组、ArrayList和LinkedList✨
在Java编程中,数据结构是非常重要的基础知识。在众多数据结构中,数组(Array)、ArrayList和LinkedList是最常用的三种。
一、总体介绍
数组(Array)、ArrayList和LinkedList都是用于存储数据的容器,但它们在存储机制、性能、使用场景等方面有着显著的区别。了解这些区别能帮助我们在不同场景下选择最适合的数据结构。
二、分别介绍
1. 数组(Array)
数组是最基本的数据结构之一,用于存储固定大小的同类型元素的集合。每个元素都可以通过其索引进行访问。
特点:
- 固定大小:数组一旦创建,大小不能改变。
- 索引访问:数组的每个元素通过索引访问,时间复杂度为O(1)。
- 内存连续:数组在内存中是连续分配的,这使得数组访问速度非常快,但增加和删除操作会比较复杂。
示例代码:
int[] numbers = new int[5]; // 创建一个长度为5的整数数组
numbers[0] = 10; // 给数组的第一个元素赋值
System.out.println(numbers[0]); // 输出第一个元素
2. ArrayList
ArrayList是Java集合框架的一部分,实际上是一个动态数组。它可以自动调整自身的大小以适应添加和删除的操作。
特点:
- 动态大小:ArrayList可以根据需要自动调整大小。
- 索引访问:与数组类似,ArrayList通过索引访问元素,时间复杂度为O(1)。
- 非线程安全:默认情况下,ArrayList是非线程安全的。
示例代码:
ArrayList<Integer> list = new ArrayList<>();
list.add(10); // 向ArrayList中添加元素
list.add(20);
System.out.println(list.get(0)); // 通过索引访问第一个元素
3. LinkedList
LinkedList是基于链表的数据结构,它可以存储任何类型的对象,并允许高效的插入和删除操作。
特点:
- 双向链表:LinkedList在内部实现是双向链表,每个节点包含对前一个和后一个节点的引用。
- 插入和删除操作快:插入和删除操作时间复杂度为O(1)。
- 索引访问慢:由于链表需要从头遍历到目标节点,索引访问时间复杂度为O(n)。
示例代码:
LinkedList<Integer> linkedList = new LinkedList<>();
linkedList.add(10); // 向LinkedList中添加元素
linkedList.add(20);
System.out.println(linkedList.get(0)); // 通过索引访问第一个元素
三、区别总结
-
存储方式:
- 数组:连续内存存储。
- ArrayList:动态数组,内存不连续。
- LinkedList:双向链表,内存不连续。
-
大小:
- 数组:固定大小。
- ArrayList:动态调整大小。
- LinkedList:动态调整大小。
-
性能:
- 数组:访问快,插入删除慢。
- ArrayList:访问快,插入删除相对慢。
- LinkedList:访问慢,插入删除快。
-
线程安全:
- 数组:线程安全(需要手动实现)。
- ArrayList:非线程安全。
- LinkedList:非线程安全。
四、常见面试题
- 如何选择使用数组、ArrayList和LinkedList?
- 如何实现线程安全的ArrayList?
- 解释ArrayList的底层实现及其扩容机制。
- LinkedList的插入和删除操作为什么比ArrayList快?
- 如何在不使用额外空间的情况下,反转一个链表?
如何选择使用数组、ArrayList和LinkedList?
选择合适的数据结构取决于具体的使用场景和需求:
-
数组(Array):
- 适用场景:数据量固定且已知;需要频繁随机访问元素。
- 优点:访问速度快(O(1));内存连续,缓存友好。
- 缺点:大小固定;插入和删除操作效率低(需要移动元素)。
-
ArrayList:
- 适用场景:需要动态调整大小;需要频繁随机访问元素。
- 优点:大小可动态调整;随机访问速度快(O(1))。
- 缺点:插入和删除操作相对较慢(O(n));扩容时可能会影响性能。
-
LinkedList:
- 适用场景:需要频繁插入和删除操作;数据量变化频繁且未知。
- 优点:插入和删除操作快(O(1));不需要预分配固定大小。
- 缺点:随机访问速度慢(O(n));额外的内存开销(存储节点的指针)。
如何实现线程安全的ArrayList?
默认情况下,ArrayList是非线程安全的。如果需要在多线程环境中使用ArrayList,可以通过以下几种方式实现线程安全:
-
使用
Collections.synchronizedList
:
List<Integer> synchronizedList = Collections.synchronizedList(new ArrayList<>());
-
使用CopyOnWriteArrayList(适用于读操作多,写操作少的场景)
List<Integer> cowList = new CopyOnWriteArrayList<>();
-
手动同步: 在操作ArrayList时,手动加锁:
List<Integer> list = new ArrayList<>();
synchronized (list) {
list.add(10);
}
解释ArrayList的底层实现及其扩容机制
底层实现: ArrayList是基于数组实现的动态数组。内部维护一个数组elementData
来存储元素,当数组容量不足时,会自动扩容。
扩容机制:
- 初始容量:当创建ArrayList时,如果不指定初始容量,默认初始容量为10。
- 扩容条件:当添加元素使得ArrayList的当前容量不够时,会触发扩容。
- 扩容策略:新的容量为旧容量的1.5倍,即
newCapacity = oldCapacity + (oldCapacity >> 1)
。 - 扩容过程:创建一个更大的新数组,将旧数组中的元素复制到新数组中,然后用新数组替换旧数组。
LinkedList的插入和删除操作为什么比ArrayList快?
LinkedList的插入和删除操作时间复杂度为O(1),主要原因是其链表结构:
- 链表结构:LinkedList是基于双向链表实现的,每个节点包含前一个和后一个节点的引用。
- 插入操作:插入一个元素只需要调整相邻节点的引用,不需要移动其他元素。
- 删除操作:删除一个元素同样只需要调整相邻节点的引用,不需要移动其他元素。
相比之下,ArrayList的插入和删除操作需要移动大量元素,时间复杂度为O(n)。
如何在不使用额外空间的情况下,反转一个链表?
对应LeetCode 题目 LCR 024. 反转链表https://leetcode.cn/problems/UHnkqh/description/
对应的题解:
力扣第24题 两两交换链表中的节点 c++精讲 。_力扣24题-CSDN博客https://blog.csdn.net/jgk666666/article/details/133253909?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171998475016800225528659%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fblog.%2522%257D&request_id=171998475016800225528659&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~blog~first_rank_ecpm_v1~rank_v31_ecpm-1-133253909-null-null.nonecase&utm_term=%E9%93%BE%E8%A1%A8&spm=1018.2226.3001.4450
反转链表可以通过就地反转的方法实现,不需要使用额外的空间。下面是单链表反转的示例代码:
此方法通过三个指针prev
、current
和next
逐步反转链表的指针,时间复杂度为O(n),空间复杂度为O(1)。
通过以上介绍,我们详细地了解了数组、ArrayList和LinkedList的基本概念、特点及其区别。在实际开发中,根据具体需求选择合适的数据结构能够显著提升程序的性能和可维护性。
觉得有用的话可以点点赞 (*/ω\*),支持一下。
如果愿意的话关注一下。会对你有更多的帮助。
每天都会不定时更新哦 >人< 。