[改善Java代码]频繁插入和删除时使用LinkedList
一、分析
前面有文章分析了列表的表里方式,也就是“读”的操作。本文将介绍表的“写”操作:即插入、删除、修改动作。
二、场景
1.插入元素
列表中我们使用最多的是ArrayList,下面看看他的插入(add方法)算法,源代码如下:
- public void add(int index,E element){
- /*检查下标是否越界,代码不在拷贝*/
- //若需要扩容,则增大底层数组的长度
- ensureCapacity(size + 1);
- //给index下标之后的元素(包括当前元素)的下标加1,空出index位置(将elementData从index起始,复制到index+1的职位
- System.arraycopy(elementData,index,elementData,index + 1,size - index);
- //赋值index位置元素
- elementData[index] = element;
- //列表的长度+1
- size++;
- }
- }
注意看arraycopy方法,只要是插入一个元素,其后的元素就会向后移动一位,虽然arraycopy是一个本地方法,效率非常高,但频繁的插入,每次后面的元素都需要拷贝一遍,效率变低了,特别是在头位置插入元素时。
现在的问题是,开发中确实会遇到要插入元素的情况,那有什么更好的方法来解决此效率问题吗?
有....使用LinkedList类即可,我们知道,LinkedList是一个双向链表,它的插入只是修改相邻元素的next和previous引用,其插入算法(add方法)如下:
- public void add(int index,E element){
- addBefore(element,(index==size?header:entry(index)));
- }
这里调用了私有的addBefore方法,改方法实现了在一个元素之前插入元素的算法,代码如下:
- private Entry addBefore(E e,Entry entry){
- //组装一个新的节点,previous指向原节点的前节点,next指向原节点
- Entry newEntry = new Entry(e,entry,entry.previous);
- //前节点的next指向自己
- newEntry.previous.next = newEntry;
- //后节点的previous指向自己
- newEntry.next.previous = newEntry;
- //长度+1
- size++;
- //修改计数器+1
- modCount ++;
- return newEntry;
- }
这是一个典型的双向链表的插入算法,把自己插入到链表,然后把前节点的next和后节点的previous指向自己。这样一个插入元素(也就是一个Entry对象)的过程中,没有任何元素会有拷贝过程,只是引用地址改变了.效率当然就高了.
经过实际测试得知,LinkedList的插入效率比ArrayList快50倍以上.
(2)删除元素
ArrayList删除指定位置上的元素、删除指定值元素,删除一个下标范围内的元素集等删除动作,三者的实现原理基本相似,都是找到索引位置,然后删除。我偶们常用的删除下标的方法(remove方法)为例来看看删除动作的性能到底如何,源码如下:
- public E remove(int index){
- //下标校验
- RangeCheck(index);
- //修改计数器+1
- modCount++;
- //记录要删除的元素
- E oldValue = (E)elementData(index);
- //有多少个元素向前移动
- int numMoved = size - index - 1;
- if(numMoved > 0)
- //index后的元素向前移动一位
- System.arraycopy(elementData,index + 1,elementData,index,numMoved);
- //列表长度减1,并且最后一位设为null
- elementData[--size] = null;
- //返回删除的值
- return oldValue;
- }
注意看,index位置后的元素都向前移动了一位,最后一个位置空出来了,这又是一次数组拷贝,和插入一样,如果数据量大,删除动作必然会暴露出性能和效率方面的问题。
我们再来看看LinkedList的删除动作,比如删除指定位置元素,删除头元素等。我们看看最基本的删除指定位置元素的方法remove,源代码如下:
- private E remove(Entry e){
- //取得原始值
- E result = e.element;
- //前节点next指向当前节点的next
- e.previous.next = e.next;
- //后节点的previouse指向当前节点的previous
- e.next.previous = e.previous;
- //置空当前节点的next和previous
- e.next = e.previous= null;
- //当前元素置空
- e.element = null;
- //列表长度减1
- size --;
- //修改计数器+1
- modCount++;
- return result;
- }
这也是双向链表的标准删除算法,没有任何耗时的操作,全部是引用指针的改变,效率自然就更高了。
实际测试可知,处理大批量的删除操作,LinkedList比ArrayList块40倍以上。
(3)修改元素
写操作还有一个动作:修改元素,在这点上LinkedList输给了ArrayList,这是因为,LinkedList是顺序存取的,因此定位元素必然是一个遍历的过程,效率大大折扣。
我们来开set方法的代码:
- public E set(int index,E element){
- //定位节点
- Entry e = entry(index);
- E oldVal = e.element;
- //节点元素替换
- e.element = element;
- return oldVal;
- }
看似很简洁,这里使用了entry方法定位元素。而LinkedList这种顺序取列表的元素定位方式会折半遍历,这是一个极其耗时的操作。而ArrayList的修改动作则是数组元素的直接替换,简单高效。
在修改动作上,LinkedList比ArrayList慢的多,特别是进行大量修改的时候,完全不是在一个数量级上。
上面通过源代码分析完成了ArrayList和LinkedList之间的PK,其中LinkedList胜两局:删除和插入效率更高;ArrayList胜一局,修改效率更高.
总体上来说,在"写"方面LinkedList占优势,而且在实际使用中,修改上一个比较少的动作.因此,如果有大量写的操作(更多的是插入和删除操作),推荐使用LinkedList.
不过何为少量,何为大量..
这就需要依赖于开发的系统了..一个实时的交易系统,即使写的操作再少,使用LinkedList也比ArrayList合适.因为此类系统是争分夺秒的,多N个毫秒可能就会造成交易数据的不准确.
而对于一个批量系统来说,几十毫秒,几百毫秒,甚至是几千毫秒的差别和意义都不大,这时候使用LinkedList和ArrayList就看个人的爱好了,当然,如果系统已经处于性能临界点了那就必须使用LinkedList.
[改善Java代码]频繁插入和删除时使用LinkedList的更多相关文章
- [改善Java代码]覆写equals方法时不要识别不出自己
建议45: 覆写equals方法时不要识别不出自己 我们在写一个JavaBean时,经常会覆写equals方法,其目的是根据业务规则判断两个对象是否相等,比如我们写一个Person类,然后根据姓名判断 ...
- java.sql.date 插入数据库没有时分秒
java.sql.date 插入数据库没有时分秒 把java中实体类的sql.date类型改成java.sql.Timestamp类型即可 util.date 转 Timestamp: java.sq ...
- [改善Java代码]构造代码块会想你所想
建议37: 构造代码块会想你所想 镜像博文:http://www.cnblogs.com/DreamDrive/p/5413408.html http://www.cnblogs.com/DreamD ...
- [改善Java代码]使用构造块精炼程序
建议36: 使用构造代码块精炼程序 什么叫代码块(Code Block)?用大括号把多行代码封装在一起,形成一个独立的数据体,实现特定算法的代码集合即为代码块,一般来说代码块是不能单独运行的,必须要有 ...
- [改善Java代码]易变业务使用脚本语言编写
建议16: 易变业务使用脚本语言编写 Java世界一直在遭受着异种语言的入侵,比如PHP.Ruby.Groovy.JavaScript等,这些“入侵者”都有一个共同特征:全是同一类语言—脚本语言,它们 ...
- [改善Java代码]集合运算时使用更优雅的方式
在初中代数中,我们经常会求两个集合的并集.交集.差集等,在Java中也存在着此 类运算,那如何实现呢? 一提到此类集合操作,大部分的实现者都会说:对两个集合进行遍历,即可求出结果.是的,遍历可以实现并 ...
- [改善Java代码]推荐在复杂字符串操作中使用正则表达式
一.分析 字符串的操作,诸如追加.合并.替换.倒序.分隔等,都是在编码过程中经常用到的,而且Java也提供了append.replace.reverse.split等方法来完成这些操作,它们使用起来 ...
- [改善Java代码]正确使用String,StringBuffer,StringBuilder
CharSequence接口有三个实现类与字符串有关:String,StringBuffer,StringBuffer.虽然它们都与字符串有关,但是其处理机制是不同的. String类是不可改变的量, ...
- [改善Java代码]非稳定排序推荐使用List
我们知道Set与List的最大区别就是Set中的元素不可以重复(这个重复指的equals方法的返回值相等),其他方面则没有太大的区别了,在Set的实现类中有一个比较常用的类需要了解一下:TreeSet ...
随机推荐
- angular的注入实现
angular需要对用户的传入函数进行静态分析,抽取当中的依赖,才能工作.因此用户的函数,包括控制器函数,工厂函数,服务函数与$watch回调都只是一个模板,用于取toString,真正运行的是编译后 ...
- Struts2在Action中访问WEB资源
什么是WEB资源? 这里所说的WEB资源是指:HttpServletRequest, HttpSession, ServletContext 等原生的 Servlet API. 为什么需要访问WEB资 ...
- iOS Launch Images name
4.0 inch screen:Default-568h@2x.png (old 640x1136 ~ for iPhone 5) 4.0 inch screen: LaunchImage-700-5 ...
- java数字保留两位小数四舍五入
import java.math.BigDecimal; import java.text.DecimalFormat; import java.text.NumberFormat; public c ...
- ASP.NET MVC中Session以及处理方式
转载原地址 http://www.cnblogs.com/darrenji/p/3951065.html
- UVa 10900 So you want to be a 2n-aire? (概率DP,数学)
题意:一 个答题赢奖金的问题,玩家初始的金额为1,给出n,表示有n道题目,t表示说答对一道题目的概率在t到1之间,每次面对一道题,可以选择结束游戏, 获得当 前奖金:回答下一道问题,答对的概率p在t到 ...
- MD5验证工具:md5sum
linux 下 shell命令 ,制作md5码 也用于软件的md5校验 MD5算法常常被用来验证网络文件传输的完整性,防止文件被人篡改.MD5 全称是报文摘要算法(Message-Digest ...
- mahout算法源码分析之Itembased Collaborative Filtering(二)RowSimilarityJob
Mahout版本:0.7,hadoop版本:1.0.4,jdk:1.7.0_25 64bit. 本篇开始之前先来验证前篇blog的分析结果,编写下面的测试文件来进行对上篇三个job的输出进行读取: p ...
- Caused by: Cannot locate the chosen ObjectFactory implementation: spring - [unknown location] 的解决方式
1.添加网上所说的struts2 plugin jar包 2. <!-- Struts2配置 --> <filter> <filter-name>struts2&l ...
- ckeditor异常问题
上传图片时点击上传按钮时,图片不能上传,有两种可能 1:采用ssh框架 , 上传图片对应的struts.xml没有配置<constant name="struts.action.exc ...