java list 分析_Java 经常使用List集合使用场景分析

Java 经常使用List集合使用场景分析

过年前的最后一篇,本章经过介绍ArrayList,LinkedList,Vector,CopyOnWriteArrayList 底层实现原理和四个集合的区别。让你清楚明白,为何工做中会经常使用ArrayList和CopyOnWriteArrayList?了解底层实现原理,咱们能够学习到不少代码设计的思路,开阔本身的思惟。本章通俗易懂,还在等什么,快来学习吧!java

知识图解:node

1460000013262803?w=753&h=1061

技术:ArrayList,LinkedList,Vector,CopyOnWriteArrayList

说明:本章基于jdk1.8,github上有ArrayList,LinkedList的简单源码代码

源码:https://github.com/ITDragonBl...git

知识预览

ArrayList : 基于数组实现的非线程安全的集合。查询元素快,插入,删除中间元素慢。

LinkedList : 基于链表实现的非线程安全的集合。查询元素慢,插入,删除中间元素快。

Vector : 基于数组实现的线程安全的集合。线程同步(方法被synchronized修饰),性能比ArrayList差。

CopyOnWriteArrayList : 基于数组实现的线程安全的写时复制集合。线程安全(ReentrantLock加锁),性能比Vector高,适合读多写少的场景。github

ArrayList 和 LinkedList 读写快慢的本质

ArrayList : 查询数据快,是由于数组能够经过下标直接找到元素。 写数据慢有两个缘由:一是数组复制过程须要时间,二是扩容须要实例化新数组也须要时间。

LinkedList : 查询数据慢,是由于链表须要遍历每一个元素直到找到为止。 写数据快有一个缘由:除了实例化对象须要时间外,只须要修改指针便可完成添加和删除元素。

本章会经过源码分析,验证上面的说法。面试

注:这里的块和慢是相对的。并非LinkedList的插入和删除就必定比ArrayList快。明白其快慢的本质:ArrayList快在定位,慢在数组复制。LinkedList慢在定位,快在指针修改。数组

ArrayList

ArrayList 是基于动态数组实现的非线程安全的集合。当底层数组满的状况下还在继续添加的元素时,ArrayList则会执行扩容机制扩大其数组长度。ArrayList查询速度很是快,使得它在实际开发中被普遍使用。美中不足的是插入和删除元素较慢,同时它并非线程安全的。

咱们能够从源码中找到答案安全

// 查询元素

public E get(int index) {

rangeCheck(index); // 检查是否越界

return elementData(index);

}

// 顺序添加元素

public boolean add(E e) {

ensureCapacityInternal(size + 1); // 扩容机制

elementData[size++] = e;

return true;

}

// 从数组中间添加元素

public void add(int index, E element) {

rangeCheckForAdd(index); // 数组下标越界检查

ensureCapacityInternal(size + 1); // 扩容机制

System.arraycopy(elementData, index, elementData, index + 1, size - index); // 复制数组

elementData[index] = element; // 替换元素

size++;

}

// 从数组中删除元素

private void fastRemove(int index) {

modCount++;

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

}

从源码中能够得知,

ArrayList在执行查询操做时:

第一步:先判断下标是否越界。

第二步:而后在直接经过下标从数组中返回元素。数据结构

ArrayList在执行顺序添加操做时:

第一步:经过扩容机制判断原数组是否还有空间,若没有则从新实例化一个空间更大的新数组,把旧数组的数据拷贝到新数组中。

第二步:在新数组的最后一位元素添加值。并发

ArrayList在执行中间插入操做时:

第一步:先判断下标是否越界。

第二步:扩容。

第三步:若插入的下标为i,则经过复制数组的方式将i后面的全部元素,日后移一位。

第四步:新数据替换下标为i的旧元素。

删除也是同样:只是数组往前移了一位,最后一个元素设置为null,等待JVM垃圾回收。高并发

从上面的源码分析,咱们能够获得一个结论和一个疑问。

结论是:ArrayList快在下标定位,慢在数组复制。

疑问是:可否将每次扩容的长度设置大点,减小扩容的次数,从而提升效率?其实每次扩容的长度大小是颇有讲究的。若扩容的长度太大,会形成大量的闲置空间;若扩容的长度过小,会形成频发的扩容(数组复制),效率更低。

LinkedList

LinkedList 是基于双向链表实现的非线程安全的集合,它是一个链表结构,不能像数组同样随机访问,必须是每一个元素依次遍历直到找到元素为止。其结构的特殊性致使它查询数据慢。

咱们能够从源码中找到答案

// 查询元素

public E get(int index) {

checkElementIndex(index); // 检查是否越界

return node(index).item;

}

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 void add(int index, E element) {

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

if (index == size) // 在链表末尾添加

linkLast(element);

else // 在链表中间添加

linkBefore(element, node(index));

}

void linkBefore(E e, Node succ) {

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++;

}

从源码中能够得知,

LinkedList在执行查询操做时:

第一步:先判断元素是靠近头部,仍是靠近尾部。

第二步:若靠近头部,则从头部开始依次查询判断。和ArrayList的elementData(index)相比固然是慢了不少。

LinkedList在插入元素的思路:

第一步:判断插入元素的位置是链表的尾部,仍是中间。

第二步:若在链表尾部添加元素,直接将尾节点的下一个指针指向新增节点。

第三步:若在链表中间添加元素,先判断插入的位置是否为首节点,是则将首节点的上一个指针指向新增节点。不然先获取当前节点的上一个节点(简称A),并将A节点的下一个指针指向新增节点,而后新增节点的下一个指针指向当前节点。

Vector

Vector 的数据结构和使用方法与ArrayList差很少。最大的不一样就是Vector是线程安全的。从下面的源码能够看出,几乎全部的对数据操做的方法都被synchronized关键字修饰。synchronized是线程同步的,当一个线程已经得到Vector对象的锁时,其余线程必须等待直到该锁被释放。从这里就能够得知Vector的性能要比ArrayList低。

若想要一个高性能,又是线程安全的ArrayList,可使用Collections.synchronizedList(list);方法或者使用CopyOnWriteArrayList集合

public synchronized E get(int index) {

if (index >= elementCount)

throw new ArrayIndexOutOfBoundsException(index);

return elementData(index);

}

public synchronized boolean add(E e) {

modCount++;

ensureCapacityHelper(elementCount + 1);

elementData[elementCount++] = e;

return true;

}

public synchronized boolean removeElement(Object obj) {

modCount++;

int i = indexOf(obj);

if (i >= 0) {

removeElementAt(i);

return true;

}

return false;

}

CopyOnWriteArrayList

在这里咱们先简单了解一下CopyOnWrite容器。它是一个写时复制的容器。当咱们往一个容器添加元素的时候,不是直接往当前容器添加,而是先将当前容器进行copy一份,复制出一个新的容器,而后对新容器里面操做元素,最后将原容器的引用指向新的容器。因此CopyOnWrite容器是一种读写分离的思想,读和写不一样的容器。

应用场景:适合高并发的读操做(读多写少)。若写的操做很是多,会频繁复制容器,从而影响性能。

CopyOnWriteArrayList 写时复制的集合,在执行写操做(如:add,set,remove等)时,都会将原数组拷贝一份,而后在新数组上作修改操做。最后集合的引用指向新数组。

CopyOnWriteArrayList 和Vector都是线程安全的,不一样的是:前者使用ReentrantLock类,后者使用synchronized关键字。ReentrantLock提供了更多的锁投票机制,在锁竞争的状况下能表现更佳的性能。就是它让JVM能更快的调度线程,才有更多的时间去执行线程。这就是为何CopyOnWriteArrayList的性能在大并发量的状况下优于Vector的缘由。

private E get(Object[] a, int index) {

return (E) a[index];

}

public boolean add(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();

}

}

private boolean remove(Object o, Object[] snapshot, int index) {

final ReentrantLock lock = this.lock;

lock.lock();

try {

Object[] current = getArray();

int len = current.length;

......

Object[] newElements = new Object[len - 1];

System.arraycopy(current, 0, newElements, 0, index);

System.arraycopy(current, index + 1, newElements, index, len - index - 1);

setArray(newElements);

return true;

} finally {

lock.unlock();

}

}

总结

看到这里,若是面试官问你ArrayList和LinkedList有什么区别时

若是你回答:ArrayList查询快,写数据慢;LinkedList查询慢,写数据快。面试官只能算你勉强合格。

若是你回答:ArrayList查询快是由于底层是由数组实现,经过下标定位数据快。写数据慢是由于复制数组耗时。LinkedList底层是双向链表,查询数据依次遍历慢。写数据只需修改指针引用。

若是你继续回答:ArrayList和LinkedList都不是线程安全的,小并发量的状况下可使用Vector,若并发量不少,且读多写少能够考虑使用CopyOnWriteArrayList。

由于CopyOnWriteArrayList底层使用ReentrantLock锁,比使用synchronized关键字的Vector能更好的处理锁竞争的问题。

面试官会认为你是一个基础扎实,内功深厚的人才!!!

到这里Java 经常使用List集合使用场景分析就结束了。过年前的最后一篇博客,有点浮躁,可能身在职场,心在老家!最后祝你们新年快乐!!!狗年吉祥!!!大富大贵!!!可能都没人看博客了 ⊙﹏⊙‖∣ 哈哈哈哈(ಡωಡ)hiahiahia

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值