完整教程:Java 集合体系 —— List 篇

完整教程:Java 集合体系 —— List 篇

在Java集合框架中,List接口是最常用的数据结构之一,它代表一个有序的集合,允许元素重复且可以通过索引访问。本文将深入解析List接口的三个主要实现类——ArrayList、LinkedList和Vector,从底层结构、核心操作、源码实现到适用场景进行全面对比分析,帮助开发者在实际开发中做出更合适的选择。

一、List接口概述List接口继承自Collection接口,定义了一组有序集合的操作规范,其核心特点包括:

允许存储重复元素维护元素的插入顺序支持通过索引(int类型)访问元素提供了丰富的增删改查方法List接口的主要方法包括:

add(E e):在末尾添加元素get(int index):获取指定索引的元素set(int index, E element):替换指定索引的元素remove(int index):删除指定索引的元素indexOf(Object o):返回元素首次出现的索引ArrayList、LinkedList和Vector作为List接口的具体实现,在数据结构、性能特性和线程安全性上各有不同,下面分别进行详细分析。

二、ArrayList详解与源码分析1. 底层数据结构ArrayList是基于动态数组实现的List,其底层维护了一个Object类型的数组(transient Object[] elementData),默认初始容量为10。当元素数量超过当前容量时,会自动进行扩容操作。

2. 核心源码分析构造方法

// 默认构造方法,初始化为空数组,首次添加元素时会扩容到10

public ArrayList() {

this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

}

// 指定初始容量的构造方法

public ArrayList(int initialCapacity) {

if (initialCapacity > 0) {

this.elementData = new Object[initialCapacity];

} else if (initialCapacity == 0) {

this.elementData = EMPTY_ELEMENTDATA;

} else {

throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);

}

}

添加元素

public boolean add(E e) {

// 确保容量足够,不足则扩容

ensureCapacityInternal(size + 1); // Increments modCount!!

elementData[size++] = e;

return true;

}

// 扩容核心逻辑

private void grow(int minCapacity) {

// overflow-conscious code

int oldCapacity = elementData.length;

// 新容量为旧容量的1.5倍

int newCapacity = oldCapacity + (oldCapacity >> 1);

if (newCapacity - minCapacity < 0)

newCapacity = minCapacity;

if (newCapacity - MAX_ARRAY_SIZE > 0)

newCapacity = hugeCapacity(minCapacity);

// 复制元素到新数组

elementData = Arrays.copyOf(elementData, newCapacity);

}

获取元素

public E get(int index) {

// 检查索引是否越界

rangeCheck(index);

return elementData(index);

}

// 直接通过数组索引访问,时间复杂度O(1)

E elementData(int index) {

return (E) elementData[index];

}

删除元素

public E remove(int index) {

rangeCheck(index);

modCount++;

E oldValue = elementData(index);

// 计算需要移动的元素数量

int numMoved = size - index - 1;

if (numMoved > 0)

// 移动元素填补空位

System.arraycopy(elementData, index+1, elementData, index, numMoved);

elementData[--size] = null; // 帮助GC回收

return oldValue;

}

3. 性能特点随机访问:通过索引直接访问元素,时间复杂度为O(1),性能优异添加元素:尾部添加(add(E e))时间复杂度为O(1),但扩容时需要复制数组,最坏情况为O(n)插入/删除元素:需要移动大量元素,时间复杂度为O(n)内存占用:连续内存空间,存在一定的空间浪费(为了避免频繁扩容)三、LinkedList详解与源码分析1. 底层数据结构LinkedList基于双向链表实现,每个节点(Node)包含三个部分:

存储的元素(item)前驱节点引用(prev)后继节点引用(next)其内部维护了两个指针:

transient Node first:指向头节点transient Node last:指向尾节点2. 核心源码分析节点结构

private static class Node {

E item;

Node next;

Node prev;

Node(Node prev, E element, Node next) {

this.item = element;

this.next = next;

this.prev = prev;

}

}

添加元素

// 尾部添加元素

public boolean add(E e) {

linkLast(e);

return true;

}

void linkLast(E e) {

final Node l = last;

final Node newNode = new Node<>(l, e, null);

last = newNode;

if (l == null)

first = newNode;

else

l.next = newNode;

size++;

modCount++;

}

// 指定位置插入元素

public void add(int index, E element) {

checkPositionIndex(index);

if (index == size)

linkLast(element);

else

linkBefore(element, node(index));

}

获取元素

public E get(int index) {

checkElementIndex(index);

return node(index).item;

}

// 查找指定索引的节点,时间复杂度O(n)

Node node(int index) {

// 优化:根据索引位置决定从头还是从尾开始遍历

if (index < (size >> 1)) {

Node x = first;

for (int i = 0; i < index; i++)

x = x.next;

return x;

} else {

Node x = last;

for (int i = size - 1; i > index; i--)

x = x.prev;

return x;

}

}

删除元素

public E remove(int index) {

checkElementIndex(index);

return unlink(node(index));

}

E unlink(Node x) {

// assert x != null;

final E element = x.item;

final Node next = x.next;

final Node prev = x.prev;

if (prev == null) {

first = next;

} else {

prev.next = next;

x.prev = null;

}

if (next == null) {

last = prev;

} else {

next.prev = prev;

x.next = null;

}

x.item = null; // 帮助GC

size--;

modCount++;

return element;

}

3. 性能特点随机访问:需要遍历链表,时间复杂度为O(n),性能较差添加/删除元素:只需修改节点引用,时间复杂度为O(1)(已知节点位置时)内存占用:每个元素需要额外存储前后节点引用,内存开销较大迭代性能:迭代器遍历性能好,但随机访问性能差四、Vector详解与源码分析1. 底层数据结构Vector与ArrayList类似,也是基于动态数组实现,但它是线程安全的集合。其底层同样维护了一个Object数组(protected Object[] elementData),默认初始容量为10。

Vectory与ArrayListd的区别:

1.Vector是线程同步的,所以Vector线程安全,但ArrayList线程不安全。

2.Vector的性能要逊于ArrayList。

3.ArrayList在调用空参构造时,此时,会初始化一个长度为0的Object数组,只有当向ArrayList中添加元素时,才会扩容至长度为10的Object数组。而Vector在调用空参构造时,会直接初始化一个长度为10的Object数组,不会在添加元素时再扩容。

4.ArrayList中的元素是允许为null的,但Vector中不允许出现null元素。

2. 核心源码分析线程安全实现Vector的线程安全主要通过在方法上添加synchronized关键字实现:

// 添加元素,方法上有synchronized关键字

public synchronized boolean add(E e) {

modCount++;

ensureCapacityHelper(elementCount + 1);

elementData[elementCount++] = e;

return true;

}

// 获取元素

public synchronized E get(int index) {

if (index >= elementCount)

throw new ArrayIndexOutOfBoundsException(index);

return elementData(index);

}

扩容机制与ArrayList不同,Vector默认扩容为原来的2倍:

private void grow(int minCapacity) {

// overflow-conscious code

int oldCapacity = elementData.length;

// capacityIncrement为扩容增量,默认为0,此时扩容为原来的2倍

int newCapacity = oldCapacity + ((capacityIncrement > 0) ?

capacityIncrement : oldCapacity);

if (newCapacity - minCapacity < 0)

newCapacity = minCapacity;

if (newCapacity - MAX_ARRAY_SIZE > 0)

newCapacity = hugeCapacity(minCapacity);

elementData = Arrays.copyOf(elementData, newCapacity);

}

3. 性能特点功能:与ArrayList基本一致,都提供动态数组的功能线程安全:所有方法都被synchronized修饰,保证线程安全性能:由于同步开销,在单线程环境下性能不如ArrayList扩容:默认扩容为原来的2倍,比ArrayList的1.5倍更激进五、三种集合的对比与适用场景特性ArrayListLinkedListVector底层结构动态数组双向链表动态数组线程安全否否是随机访问快(O(1))慢(O(n))快(O(1))插入删除慢(O(n))快(O(1))慢(O(n))内存占用较少(连续空间)较多(节点引用)较少(连续空间)扩容机制1.5倍无需扩容2倍适用场景选择建议:ArrayList:

适用于频繁随机访问元素的场景适合元素数量变化不大,或主要在尾部添加/删除元素的操作单线程环境下的首选List实现LinkedList:

适用于频繁在中间位置插入/删除元素的场景适合实现队列、栈等数据结构(LinkedList实现了Deque接口)元素数量不确定且经常变化的场景Vector:

适用于多线程环境下需要线程安全的场景但在实际开发中,更推荐使用Collections.synchronizedList(new ArrayList<>())替代对性能要求不高,且需要兼容旧代码的场景六、总结ArrayList、LinkedList和Vector作为List接口的三大实现类,各有其独特的设计和性能特性:

ArrayList以动态数组为基础,提供了高效的随机访问能力,适合读取操作频繁的场景LinkedList基于双向链表实现,在插入和删除操作上表现优异,适合频繁修改的场景Vector作为线程安全的动态数组,虽然保证了线程安全,但性能开销较大,在现代开发中已较少直接使用在实际开发中,应根据具体的业务场景、操作类型和并发需求选择合适的List实现,以达到最优的性能表现。同时,需要注意的是,这三个集合类都不是线程安全的(除Vector外),在多线程环境下使用时需要额外的同步措施。

相关文章

原创马超是哪个民族的?马超怎么死的?
28365365bet官网

原创马超是哪个民族的?马超怎么死的?

10-30 阅读: 1820
还在用夸克?这3款能安装插件的手机浏览器不香吗
365bet国际娱乐网址

还在用夸克?这3款能安装插件的手机浏览器不香吗

08-13 阅读: 1877
114DNS、Google DNS、阿里DNS、OpenDNS等公共DNS评测体验报告
腊肠狗怎么抓老鼠?
365bet国际娱乐网址

腊肠狗怎么抓老鼠?

09-14 阅读: 6248
sql数据库如何查看触发器
365bet国际娱乐网址

sql数据库如何查看触发器

01-18 阅读: 5562
阿布谈iG换人:大户人家 辅助的show time
28365365bet官网

阿布谈iG换人:大户人家 辅助的show time

08-25 阅读: 7883