关于LinkedList的使用场景及优化
java吧
全部回复
仅看楼主
level 9
tuzi215 楼主
有人问到这个问题,顺便上网查资料深入了解了一下。以往问及什么时候用ArrayList什么时候用LinkedList,往往答案是,当大量随机检索数据时使用ArrayList,当频繁插入删除操作时使用LinkedList。按照数据结构来说,确实是LinkedList在插入删除操作时的复杂度要低于ArrayList。但在现实代码实现以及实际执行性能上,却不一定是这样。根据查阅的资料整理如下,欢迎讨论。
首先要了解两种List的不同应该先了解其代码实现。
ArrayList是通过索引访问的数组实现,而LinkedList使用过 链表原理实现。
ArrayList的get(i)不需要遍历可以直接通过索引获取数据,而LinkedList需要遍历,遍历到i索引位置时将当前位置数据返回。
网上有人贴了这样一段代码:
public static void main(String[] args) {
// TODO Auto-generated method stub
LinkedList list = new LinkedList();
for (int i =0;i<=1000000; i++) {
list.add(i);
}
long start = System.currentTimeMillis();
list.get(1);
long end = System.currentTimeMillis();
System.out.println(" time to traverse 1st element " + (end - start) );
start = System.currentTimeMillis();
list.get(999999);
end = System.currentTimeMillis();
System.out.println(" time take to traverse 999999th element " + (end - start) );
}
time to traverse 1st element 0
time to traverse 999999th element 0
他问既然LinkedList需要遍历读取数据,为什么我获取第一个数据和最后一个数据用的时间是一样的。
看一下LinkedList的get代码就应该明白了,遍历可能会从列表头部或末尾开始进行,取决于i在列表的前半部还是后半部分:
public Object get(int index) {
//check the index is valid first .. code not shown here
Entry e = header; //starting node
//go forwards or backwards depending on which is closer
if (index < size/2) {
for (int i = 0; i <= index; i++)
e = e.next; }
else {
for (int i = size; i > index; i--)
e = e.previous;
}
return e;
}
同样在特定索引位置插入数据时,也是先遍历到那个位置再进行插入。这样很容易理解,当很大数据量时(比如1W),在LinkedList前部和后部进行删除插入操作,速度会很快,但是如果是在中部进行操作,测试结果就不那么乐观了,性能甚至不及ArrayList。
所以建议在数据量很大,同时存在链表头部以及尾部的频繁操作,并且很少执行中部数据操作时使用链表。其实这种情况也可以考虑使用ArrayDeque,因为链表在新增数据时会创建额外对象,记录上一node和下一个node的信息,会占用额外空间,而ArrayDeque则不会增加额外空间,不会增加GC工作。其他情况在ArrayList初始化大小设置合理时使用ArrayList能获得更好性能。
知道LinkedList的应用场景,再说说如何正确使用LinkedList。
首先是遍历,遍历LinkedList必须使用iterator不能使用for循环,因为每次for循环体内通过get(i)取得某一元素时都需要对list重新进行遍历,性能消耗极大。
另外不要试图使用indexOf等返回元素索引,并利用其进行遍历,例如下面的代码:
final int startPos = lst.indexOf( first ) + 1; //使用indexlOf对list进行了遍历,当结果为空时会遍历整个列表。
final ListIterator<String> iter = lst.listIterator( startPos ); //如果startPos的元素在列表前半部,那这两行代码相当于平均要遍历了列表1.25的长度,因为 listIterator( startPos )也需要从头遍历,当到达指定元素后开始循环如下操作。
while ( iter.hasNext() ) {
if ( iter.next().length() == 5 )
iter.remove();
}
以上总结,网上的讨论很激烈,持有各自意见,以上为我的总结。有问题可以给我指出,谢谢
2013年12月24日 09点12分 1
level 9
tuzi215 楼主
2013年12月24日 09点12分 2
level 9
tuzi215 楼主
2013年12月24日 09点12分 3
level 8
多数都用ArrayList吧
2013年12月24日 09点12分 5
对的
2013年12月25日 01点12分
level 8
恩 等学了看 thanks
2013年12月24日 09点12分 6
level 10
楼主的意思可是,如果要经常在集合的首尾部分频繁增删数据就使用linked list, 而如果随机在哪个位置增删数据的话,linked list 和Arraylist的区别实质不大?[委屈]其实我也才学java两个月而已[太开心]
——来自 诺基亚 Lumia 520
2013年12月24日 09点12分 7
LinkedList在头部和尾部增删数据在小数据量的时候和al相差无几 大数据量的时候头尾操作比较快 但是中部操作慢 所以一般不是特意要利用双向链表的时候都是用al
2013年12月24日 10点12分
level 12
[咦]在不是特别限制内存使用的情况下就尽量使用ArrayList,LinkedList一般只拿来当栈用
2013年12月24日 10点12分 8
普通测试中对两个list分别为在头部进行增删操作 结果是LinkedList更占内存 应该是因为LinkedList中没个node都需要额外创建对象记录上下一条node的引用
2013年12月24日 16点12分
回复 tuzi215 :[呼~]但是ArrayList不事先多分配一些空间那么需要调整容量时开销就大了,所以用ArrayList时一般应该先确定一个足够大的容量。
2013年12月25日 02点12分
回复 zzb12 :恩。 1亿条数据存储 ll比al多了3g多内存占用
2013年12月25日 03点12分
回复 tuzi215 :[喷]不要这么大的数量级
2013年12月25日 04点12分
level 9
tuzi215 楼主
今天回来晚了 明天发一下测试结果 在al和ll头部分别进行增删操作
2013年12月24日 16点12分 10
level 8
AL的增删都是靠的System.arraycopy,它的效率一点都不慢,如果插入和删除的是末尾元素,连copy都不需要,只不过AL有时需要扩容,但是在实例化时定好容量可以有效避免...
2013年12月24日 18点12分 11
level 8
不觉明厉,我只是不明白到底在什么情况下需要在al里面操作万量级或以上的数据
2013年12月24日 18点12分 12
level 8
对于中间的元素,AL可以瞬间定位,然后迅速增删,但是需要后期处理(System.arraycopy),LL增删的时候也很快,但是需要前期准备(从头部或者末尾遍历过来),所以只从理论上说,数据量越大,AL的优势越大,LL的优势应该在头部的几个元素
2013年12月24日 18点12分 13
恩 数据量过大时 并且同时存在对list头部尾部频繁操作时才会考虑使用LinkedList
2013年12月25日 01点12分
level 12
[咦]对比了一下C++感觉因为Java里这些容器存储的都是引用,所以ArrayList和LinkedList的区别就不明显,如果是C++里元素的类型是上k字节的结构或对象,这时候vector和list的区别就大了
2013年12月25日 02点12分 14
level 9
tuzi215 楼主
public class TestList {
private final static int SIZE = 100000000;
private LinkedList<Integer> ll = new LinkedList<Integer>();
private ArrayList<Integer> al = new ArrayList<Integer>(1000000);
public static void main(String[] args) {
TestList testList = new TestList();
System.out.println(" time to add element to 1st of ArrayList " + String.valueOf(testList.TestAddToHeadOfArrayList() ));
System.out.println(" time to add element to 1st of LinkedList " + String.valueOf(testList.TestAddToHeadOfLinkedList()) );
}
public long TestAddToHeadOfArrayList() {
for (int i = 0; i < SIZE; ++i) {
al.add(i);
}
long start = System.nanoTime();
al.add(0,-1);
long end = System.nanoTime();
return end - start;
}
public long TestAddToHeadOfLinkedList() {
for (int i = 0; i < SIZE; ++i) {
ll.add(i);
}
long start = System.nanoTime();
ll.add(0,-1);
long end = System.nanoTime();
return end - start;
}
}
使用如上代码测试,测试环境:
CPU I-5 2.6GHZ 内存16G 硬盘SSD 250G JVM1.6
在1亿条数据的al和ll列表前端执行插入,结果:
al --- 51522813 纳秒(当插入数据后正好列表空间已满时,执行时间大概249400289纳秒)
ll --- 91185 纳秒
2013年12月25日 05点12分 15
level 2
ArrayList、LinkedList实际场景怎么选择呢?感觉还是不知道如何使用
因为使用他们时,一定会有读有写的,不可能说只读或只写
2019年02月21日 03点02分 16
1