java list数据结构_Java数据结构-------List

三种List:ArrayList,Vector,LinkedList

类继承关系图

85d85e454abe3b845de48656d4db164b.png

ArrayList和Vector通过数组实现,几乎使用了相同的算法;区别是ArrayList不是线程安全的,Vector绝大多数方法做了线程同步。

LinkedList通过双向链表实现。

源代码分析

1、添加元素到列表尾端(Appends the specified element to the end of this list.)

ArrayList:当所需容量超过当前ArrayList的大小时,需要进行扩容,对性能有一定的影响。

优化策略:在能有效评估ArrayList数组初始值大小的情况下,指定其容量大小有助于性能提升,避免频繁的扩容。

public booleanadd(E e) {

ensureCapacityInternal(size+ 1); //Increments modCount!! 确保内部数组有足够的空间

elementData[size++] = e; //将元素放在数组尾部

return true;

}private void ensureCapacityInternal(intminCapacity) {if (elementData ==EMPTY_ELEMENTDATA) {

minCapacity= Math.max(DEFAULT_CAPACITY, minCapacity); //如果数组为空数组,取初始容量和minCapacity中的最大值,初始容量DEFAULT_CAPACITY = 10

}

ensureExplicitCapacity(minCapacity);

}private void ensureExplicitCapacity(intminCapacity) {

modCount++; //被修改次数,iterator成员变量expectedModCount为创建时的modCount的值,用来判断list是否在迭代过程中被修改//overflow-conscious code

if (minCapacity - elementData.length > 0)

grow(minCapacity);//如果所需容量大小大于数组的大小就进行扩展

}private void grow(intminCapacity) {//overflow-conscious code

int oldCapacity =elementData.length;int newCapacity = oldCapacity + (oldCapacity >> 1); //旧容量的1.5倍。二进制右移一位差不多相当于十进制除以2,对CPU来说,右移比除运算速度更快。如果oldCapacity为偶数,newCapacity为1.5*oldCapacity,否则为1.5*oldCapacity-1。

if (newCapacity - minCapacity < 0) //如果计算出的容量不够用,就使用minCapacity

newCapacity =minCapacity;if (newCapacity - MAX_ARRAY_SIZE > 0) //如果计算出的容量大于MAX_ARRAY_SIZE=Integer.MAX_VALUE-8,

newCapacity =hugeCapacity(minCapacity);//minCapacity is usually close to size, so this is a win:

elementData = Arrays.copyOf(elementData, newCapacity);//调用System.arraycopy方法复制数组

}//判断是否大于数组最大值Integer.MAX_VALUE,疑问:设置MAX_ARRAY_SIZE=Integer.MAX_VALUE-8的意义是什么?

private static int hugeCapacity(intminCapacity) {if (minCapacity < 0) //overflow

throw newOutOfMemoryError();return (minCapacity > MAX_ARRAY_SIZE) ?Integer.MAX_VALUE :

MAX_ARRAY_SIZE;// }

LinkedList:每次新增元素都需要new一个Node对象,并进行更多的赋值操作。在频繁的调用中,对性能会产生一定的影响。

public booleanadd(E e) {

linkLast(e);return true;

}voidlinkLast(E e) {final Node l =last;final Node newNode = new Node<>(l, e, null); //每增加一个节点,都需要new一个Node

last =newNode;if (l == null)

first=newNode;elsel.next=newNode;

size++;

modCount++;

}

2、在列表任意位置添加元素

ArrayList:基于数组实现,数组需要一组连续的内存空间,如果在任意位置插入元素,那么该位置之后的元素需要重新排列,效率低。

public void add(intindex, E element) {

rangeCheckForAdd(index);//检查索引是否越界

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

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

size- index);//每次操作都会进行数组复制,System.arraycopy可以实现数组自身的复制

elementData[index] =element;

size++;

}private void rangeCheckForAdd(intindex) {if (index > size || index < 0)throw newIndexOutOfBoundsException(outOfBoundsMsg(index));

}

LinkedList:先找到指定位置的元素,然后在该元素之前插入元素。在首尾插入元素,性能较高;在中间位置插入,性能较低。

//在列表指定位置添加元素

public void add(intindex, E element) {

checkPositionIndex(index);//检查索引是否越界

if (index == size) //index为列表大小,相当于在列表尾部添加元素

linkLast(element);elselinkBefore(element, node(index));

}//返回指定索引的元素,在首尾查找速度快,在中间位置查找速度较慢,需要遍历列表的一半元素。

Node node(intindex) {//assert isElementIndex(index);

if (index < (size >> 1)) { //如果index在列表的前半部分,从头结点开始向后遍历

Node x =first;for (int i = 0; i < index; i++)

x=x.next;returnx;

}else { //如果index在列表的后半部分,从尾结点开始向前遍历

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

x=x.prev;returnx;

}

}//在指定节点succ之前添加元素

void linkBefore(E e, Nodesucc) {//assert succ != null;

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

succ.prev=newNode;if (pred == null) //只有succ一个节点

first =newNode;elsepred.next=newNode;

size++;

modCount++;

}

3、删除任意位置元素

ArrayList:每次删除都会复制数组。删除的位置越靠前,开销越大;删除的位置越靠后,开销越小。

public E remove(intindex) {

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; //clear to let GC do its work 最后一个位置设置为null

returnoldValue;

}

LinkedList:先通过循环找到要删除的元素,然后删除该元素。删除首尾的元素,效率较高;删除中间元素,效率较差。

public E remove(intindex) {

checkElementIndex(index);returnunlink(node(index));

}

E unlink(Nodex) {//assert x != null;

final E element =x.item;final Node next =x.next;final Node prev =x.prev;if (prev == null) { //x为第一个元素

first =next;

}else{

prev.next=next;

x.prev= null;

}if (next == null) { //x为最后一个元素

last =prev;

}else{

next.prev=prev;

x.next= null;

}

x.item= null;

size--;

modCount++;returnelement;

}

4、遍历列表

三种遍历方式:foreach,迭代器,for遍历随机访问。

foreach的内部实现也是使用迭代器进行遍历,但由于foreach存在多余的赋值操作,比直接使用迭代器稍慢,影响不大。for遍历随机访问对ArrayList性能较好,对LinkedList是灾难性的。

并发List

Vector和CopyOnWriteArrayList是线程安全的实现;

ArrayList不是线程安全的,可通过Collections.synchronizedList(list)进行包装。

CopyOnWriteArrayList,读操作不需要加锁,

1、读操作

CopyOnWriteArrayList:读操作没有锁操作

public E get(intindex) {returnget(getArray(), index);

}finalObject[] getArray() {returnarray;

}private E get(Object[] a, intindex) {return(E) a[index];

}

Vector:读操作需要加对象锁,高并发情况下,锁竞争影响性能。

public synchronized E get(intindex) {if (index >=elementCount)throw newArrayIndexOutOfBoundsException(index);returnelementData(index);

}

2、写操作

CopyOnWriteArrayList:需要加锁且每次写操作都需要进行一次数组复制,性能较差。

public booleanadd(E e) {final ReentrantLock lock = this.lock;

lock.lock();try{

Object[] elements=getArray();int len =elements.length;

Object[] newElements= Arrays.copyOf(elements, len + 1); //通过复制生成数组副本

newElements[len] = e; //修改副本

setArray(newElements); //将副本写会

return true;

}finally{

lock.unlock();

}

}

Vector:和读一样需要加对象锁,相对CopyOnWriteArrayList来说不需要复制,写性能比CopyOnWriteArrayList要高。

public synchronized booleanadd(E e) {

modCount++;

ensureCapacityHelper(elementCount+ 1); //确认是否需要扩容

elementData[elementCount++] =e;return true;

}private void ensureCapacityHelper(intminCapacity) {//overflow-conscious code

if (minCapacity - elementData.length > 0)

grow(minCapacity);

}

总结:在读多写少的高并发应用中,适合使用CopyOnWriteArrayList;在读少写多的高并发应用中,Vector更适合。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值