ArraysList和LinkedList都是java中存放数据的集合。那么这两者都在什么场景下使用呢?
ArraysList
ArraysList底层实现是数组,数组结构的特点是增删慢、查询快。这里的增删慢指不是在尾部增删数据慢,而是在非尾部增删数据慢。因为ArrayList的底层实现是新建立一个数组,然后通过copy操作来复制数据,从而达到增删数据的效果。这个copy操作,就浪费了性能。
ArrayList可以指定容量。如果不指定容量,那么初始容量为0。当第一次调用add方法添加元素的时候,扩容到10。此后再需要扩容的时候,按照原来1.5倍空间进行扩容。扩容的本质也是新建一个数组,然后copy数据进行扩容。也很浪费性能。因此可以规定初始容量,减少ArrayList的扩容触发。
参考文章:浅谈ArrayList及其扩容机制
LinkedList
由源码可以看到,LinkedList内部维护了first和last头部和尾部节点。每个节点Node还维护了next和prev上一个和下一个节点。其是一个双向链表。链表的特点是查询慢,增删快。因为维护了上一个Node和下一个Node,所以增删就是对Node指针的改变,不需要进行数据的copy等操作,所以增删快。因为链表中没有角标索引,所以查询起来速度慢。
LinkedList没有最大容量个初始容量,也不需要扩容,理论只要内存够大,可以存无限的值。所以扩容带来的性能损耗都没有。
参考文章:linkedList详解
实际应用
在一个数据量大,数据传输频繁的场景中,需要用一个集合接收传输的数据,然后再用另外一个线程去消费集合中接收到的数据。要求先进先出,先消费集合前面的数据,再消费集合后面的元素。
上述场景中应该选择哪个集合呢?通过分析可以发现,添加元素都是往集合的尾部添加,所以ArrayList和LinkedList都可以,性能都不受影响。而消费数据是从集合头部开始消费的,因为数据量大,传输频繁,所以这里需要消费完一个数据,就删除一个数据,避免造成内存空间的浪费。那么删除数据,用哪个快呢?肯定是用LinkedList更快。因为这里不涉及到数据的查询,就是消费集合中的第一个元素,所以LinkedList查询慢的弊端在这里显现不出来。
此外,数量大,数据传输频繁,那么ArrayList很可能会触发扩容机制而影响性能,所以这里使用LinkedList更合适。
下面通过实验证明:
public static void main(String[] args) {
//使用ArrayList
ArrayList list=new ArrayList();
//启动线程添加数据,添加一百万条数据
new Thread(){
@Override
public void run() {
for (int i=0;i<1000000;i++){
list.add(i);
logger.error("add:"+i);
}
}
}.start();
//另一个线程消费数据,当消费完成后退出循环,并打印消费的数据
new Thread(){
@Override
public void run() {
while (true){
try {
Object remove = list.remove(0);
logger.error("data:" + remove);
}catch (Exception e){
break;
}
}
}
}.start();
}
通过日志可以看到开始消费的时间为13:19:41.928,结束消费的时间为13:21:39.505,将近两分钟。
改成用LinkedList:
public static void main(String[] args) {
//使用ArrayList
LinkedList list=new LinkedList();
//启动线程添加数据,添加一百万条数据
new Thread(){
@Override
public void run() {
for (int i=0;i<1000000;i++){
list.add(i);
logger.error("add:"+i);
}
}
}.start();
//另一个线程消费数据,当消费完成后退出循环,并打印消费的数据
new Thread(){
@Override
public void run() {
while (true){
try {
Object remove = list.remove(0);
logger.error("data:" + remove);
}catch (Exception e){
break;
}
}
}
}.start();
}
通过日志发现开始消费和结束消费的时间为22秒左右。可见LinkedList更适合该场景。