移除集合效率高还是add高_ArrayList和LinkedList的源码学习,理解两者在插入、删除、和查找的性能差异...

本文详细分析了ArrayList和LinkedList在Java中的实现,包括它们的数据结构、添加元素(add)、删除元素(remove)以及遍历操作的源码。在向List末尾添加元素时,ArrayList效率较高,但在中间插入和删除时,LinkedList表现更好。遍历方面,迭代器速度最快,接着是forEach,for循环最慢。对于频繁插入和删除的场景,建议使用LinkedList。
摘要由CSDN通过智能技术生成

List的使用

List的子类

1). ArrayList

数据结构:数组

2). Vector

数据结构:数组

3). LinkedList

数据结构:循环双向链表

ArrayList 、Vector、LinkedList都来自AbstractList的实现,AbstratList直接实现了List接口并扩展自AbstactCollection。

一)、对ArrayList的 操作

== ArrayList的属性 ==

//默认容量

private static final int DEFAULT_CAPACITY = 10;

//空元素数据

private static final Object[] EMPTY_ELEMENTDATA = {};

//默认容量为空的空元素数据

private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

//底层结构

transient Object[] elementData;

//数组的大小

private int size;

== 创建ArrayList对象 ==

创建new ArrayList()时默认数组大小为 elementData = {}

public ArrayList() {

this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; //{}

}

== 对ArrayList进行add操作的源码剖析==

1). 首次添加元素时,给 elementData重新建立长度为10 的数组

2).添加元素时通过ensureCapacityInternal(size + 1)来判断内部容量是否需要扩容

I: 当内部容量需要扩容时,在原数组长度的基础上以1.5倍的规则进行扩展。

II: 通过System.arraycopy()将原数组的元素复制到新数组中。

3).将元素添加到尾部add(E e):

public boolean add(E e) {

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

elementData[size++] = e;

return true;

}

private void ensureCapacityInternal(int minCapacity) {

if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {

minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);

}

ensureExplicitCapacity(minCapacity);

}

private void ensureExplicitCapacity(int minCapacity) {

modCount++;

// overflow-conscious code

if (minCapacity - elementData.length > 0)

grow(minCapacity);

}

private void grow(int minCapacity) {

// overflow-conscious code

int oldCapacity = elementData.length;

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

if (newCapacity - minCapacity < 0)

newCapacity = minCapacity;

if (newCapacity - MAX_ARRAY_SIZE > 0)

newCapacity = hugeCapacity(minCapacity);

// minCapacity is usually close to size, so this is a win:

elementData = Arrays.copyOf(elementData, newCapacity);

}

private static int hugeCapacity(int minCapacity) {

if (minCapacity < 0) // overflow

throw new OutOfMemoryError();

return (minCapacity > MAX_ARRAY_SIZE) ?

Integer.MAX_VALUE :

MAX_ARRAY_SIZE;

}

4).添加元素到指定位置add(int index, E element):

public void add(int index, E element) {

//范围检查

rangeCheckForAdd(index);

//是否进行扩容

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

/**

以index为原数组elementData的起始位置,顺序获取size - index个元素,将这size - index个元素复制到以index+1为起始

位置的elementData数组中。

*/

System.arraycopy(elementData, index, elementData, index + 1,size - index);

elementData[index] = element;

size++;

}

添加元素的核心代码:

/**

以index为原数组elementData的起始位置,顺序获取size - index个元素,将这size - index个元素复制到以index+1为起始

位置的elementData数组中。

*/

System.arraycopy(elementData, index, elementData, index + 1,size - index);

== 删除指定位置的元素 remove(int index) ==

public class Test {

public static void main(String[] args) {

List list = new ArrayList();

list.add("a");

list.remove(0);

}

}

public E remove(int index) {

//范围查找

rangeCheck(index);

modCount++;

E oldValue = elementData(index);

int numMoved = size - index - 1;

if (numMoved > 0)

/**

将elementData数组的的元素从index+1,开始共复制numMove

个元素到elementData中从index开始为起始赋值位置

*/

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

elementData[--size] = null; // clear to let GC do its work

return oldValue;

}

private void rangeCheck(int index) {

if (index >= size)

throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

}

remove的核心代码:

/**

将elementData数组的的元素从index+1,开始共复制numMove

个元素到elementData中从index开始为起始赋值位置

*/

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

二)、对LinkedList进行操作

定义:一个LinkedList由多个表项连接而成,一个表项由3部分构成,前驱表项、内容、后驱表项。

== 向末尾添加元素add(E) ==

public class Test {

public static void main(String[] args) {

LinkedList list1 = new LinkedList();

list1.add("1");

}

}

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;

//判断链表是否为null,若链表为空则将当前新建表项设为第一个表项

if (l == null)

first = newNode;

//若最后一个表项不为空,则将该表项的next指针指向新的表项

else

l.next = newNode;

size++;

modCount++;

}

}

== 向指定位置插入元素add(int index, E e) ==

public class Test {

public static void main(String[] args) {

LinkedList list1 = new LinkedList();

list1.add("1");

list1.add(0,"bb");

list1.add(0,"bb");

System.out.println(list1.size());//size = 3

}

}

public void add(int index, E element) {

//检查范围,判断需要插入的位置是否在size范围内

checkPositionIndex(index);

//末尾插入

if (index == size)

linkLast(element);

//在某个位置前插入

else

linkBefore(element, node(index));

}

//根据index查询对应的表项

Node node(int index) {

// assert isElementIndex(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;

}

}

//改变链表的部分指向

void linkBefore(E e, Node succ) {

// assert succ != null;

final Node pred = succ.prev;

final Node newNode = new Node<>(pred, e, succ);

succ.prev = newNode;

if (pred == null)

first = newNode;

else

pred.next = newNode;

size++;

modCount++;

}

结论:

1).当需要向List的末尾添加元素时,使用ArrayList的效率比LinkedList的效率

高,但总体速率相差不大。

原因:

i).使用LinkedList添加元素时每次都要new 一个Node对象,不间断的生成新

的对象占用了一定的系统资源。

ii).ArrayList只有在空间不足的情况下才会产生数组扩容和数组复制,所以

决定大部份的追加操作效率非常高。

2).操作List向指定位置添加元素时,若插入的位置在前半段或后半段部分使用

LinkedList进行插入,若插入位置在中间使用ArrayList进行插入性能较好,若

插入的数据在末尾 LinkedList和ArrayList的速率大致相同。

3). 当频繁对List进行删除和插入操作时使用LinkedList.

注:删除指定位置的元素原理和结论与在指定位置插入元素相同。

== 删除指定位置的元素remove(index) ==

public E remove(int index) {

checkElementIndex(index);

return unlink(node(index));

}

//先找到对应的index对应的表项

Node node(int index) {

// assert isElementIndex(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;

}

}

//修改链表的指向

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;

size--;

modCount++;

return element;

}

三)、对List集合进行遍历操作

三种遍历方式:

List list = new ArrayList();

List list = new LinkedLisr();

//foEach;增强for循环

String temp = null;

for(String str : list){

temp = str;

}

//迭代器

for(Iterator it = list.Iterator() ; it.hasNext() ; ){

temp = it.next();

}

//for循环

for(int i = 0 ; i < list.size() ; i++){

temp = list.get(i)

}

结论:

1).使用迭代器遍历LinkedList和ArrayList列表时速度相同

2).使用forEach遍历LinkedList和ArrayList列表时速度相同

3). 迭代器遍历速度比forEach遍历的速度快

4).使用for循环时遍历list时ArrayList的速率远远的大于LinkedList。

原因:使用for循环遍历采用了随机访问机制。遍历LinkedList列表时,每次 get(int index),都要对列表进行一次遍历操作,查找index对应的表项,再取出表项对应的值,而ArrayList是基于数组结构的顺序存储,直接通过下标则可以获取元素。

遍列表的速率:

迭代器 > forEach > for循环

四)、为什么更简单的forEach遍历速率会低于迭代器遍历呢?

原因:通过反编译知道forEach的遍历是基于迭代器遍历而实现的

反编译的迭代器遍历:

for(Iterator it = list.Iterator() ; it.hasNext() ; ){

String s = it.next();

String s1 = s; //forEach遍历比迭代器遍历多了一步赋值操作

}

迭代器遍历:

for(Iterator it = list.Iterator() ; it.hasNext() ; ){

String s = it.next();

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值