java list常用_Java集合之常用List

ArrayList

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

查询数据快,是因为数组可以通过下标直接找到元素。

写数据慢有两个原因:一是数组复制过程需要时间,二是扩容需要实例化新数组也需要时间。

ArrayList在执行查询操作时:

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

第二步:然后在直接通过下标从数组中返回元素。

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

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

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

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

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

第二步:扩容。

第三步:若插入的下标为i,则通过复制数组的方式将i后面的所有元素,往后移一位。

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

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

扩容机制

在JDK1.7当中,当第一个元素添加时,ensureCapacityInternal()方法会计算ArrayList的扩容大小,默认为10;

其中grow()方法最为重要,如果需要扩容,那么扩容后的大小是原来的1.5倍,实际上最终调用了Arrays.copyOf()方法得以实现;

//添加元素e

public booleanadd(E e) {

ensureCapacityInternal(size+ 1);//将对应角标下的元素赋值为e:

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

}//得到最小扩容量

private void ensureCapacityInternal(intminCapacity) {//如果此时ArrayList是空数组,则将最小扩容大小设置为10:

if (elementData ==EMPTY_ELEMENTDATA) {

minCapacity=Math.max(DEFAULT_CAPACITY, minCapacity);

}//判断是否需要扩容:

ensureExplicitCapacity(minCapacity);

}//判断是否需要扩容

private void ensureExplicitCapacity(intminCapacity) {//操作数+1

modCount++;//判断最小扩容容量-数组大小是否大于0:

if (minCapacity - elementData.length > 0)//扩容:

grow(minCapacity);

}//ArrayList动态扩容的核心方法:

private void grow(intminCapacity) {//获取现有数组大小:

int oldCapacity =elementData.length;//位运算,得到新的数组容量大小,为原有的1.5倍:

int newCapacity = oldCapacity + (oldCapacity >> 1);//如果新扩容的大小依旧小于传入的容量值,那么将传入的值设为新容器大小:

if (newCapacity - minCapacity < 0)

newCapacity=minCapacity;//如果新容器大小,大于ArrayList最大长度:

if (newCapacity - MAX_ARRAY_SIZE > 0)//计算出最大容量值:

newCapacity =hugeCapacity(minCapacity);//数组复制:

elementData =Arrays.copyOf(elementData, newCapacity);

}//计算ArrayList最大容量:

private static int hugeCapacity(intminCapacity) {if (minCapacity < 0)throw newOutOfMemoryError();//如果新的容量大于MAX_ARRAY_SIZE。将会调用hugeCapacity将int的最大值赋给newCapacity:

return (minCapacity > MAX_ARRAY_SIZE) ?Integer.MAX_VALUE :

MAX_ARRAY_SIZE;

}

JDk1.6版本时,扩容之后容量为 1.5 倍+1

JDk1.6版本以后扩容1.5倍

若扩容的长度太大,会造成大量的闲置空间;若扩容的长度太小,会造成频发的扩容(数组复制),效率更低。

System.arraycopy() 和 Arrays.copyOf()方法

两者联系和区别

联系:

看两者源代码可以发现 copyOf() 内部实际调用了 System.arraycopy() 方法

区别:

arraycopy() 需要目标数组,将原数组拷贝到你自己定义的数组里或者原数组,而且可以选择拷贝的起点和长度以及放入新数组中的位置 copyOf() 是系统自动在内部新建一个数组,并返回该数组

ensureCapacity方法

在 add 大量元素之前用 ensureCapacity 方法,以减少增量重新分配的次数,因为每次扩容都是创建一个新的对象数组,然后将元素挪至新数组中。相当耗费空间和时间

/*** 如有必要,增加此 ArrayList 实例的容量,以确保它至少可以容纳由minimum capacity参数指定的元素数。

*@paramminCapacity 所需的最小容量*/

public void ensureCapacity(intminCapacity) {int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)?

//如果不是默认空数组,则将下限设为0

0

//如果ArrayList是通过无参构造且尚未添加元素的实例,则

: DEFAULT_CAPACITY;//也就是说该方法只有一个特例,就是当该实例是通过无参构造且尚未添加元素时,此时扩容应该至少扩张为//DEFAULT_CAPACITY,否则不予处理,在第一次add时扩容为DEFAULT_CAPACITY(回顾之前的解释)。//其他情况下,只要大于0就开始扩容程序。

if (minCapacity >minExpand) {

ensureExplicitCapacity(minCapacity);

}

}

LinkedList

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

JDK1.7之前的版本是环形链表,而到了JDK1.7以后进行了优化,变成了直线型链表结构

不能像数组一样随机访问,必须是每个元素依次遍历直到找到元素为止。其结构的特殊性导致它查询数据慢。

LinkedList在执行查询操作时:

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

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

LinkedList在插入元素的思路:

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

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

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

Vector

Vector的底层是基于数组实现的线程安全的集合。线程同步(方法被synchronized修饰),性能比ArrayList差,默认大小也是10。

主要特点:查询快,增删慢  , 线程安全,但是效率低。

实现原理:

创建对象与ArrayList类似,但有一点不同,它可以设置扩容是容量增长大小。

根据Vector的三个构造器就可以很明了的理解 new Vector(); 与 new Vector(10);与 new Vector(10,0); 三个是等同的。

1.无参构造器publicVector() {this(10);

}2.传一个参数(容量大小) 容量大小即底层数组大小public Vector(intinitialCapacity) {this(initialCapacity, 0);

}3.传两个参数(容量大小,容量修正) 容量修正即扩容时的增加量public Vector(int initialCapacity, intcapacityIncrement) {super();if (initialCapacity < 0)throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);this.elementData = newObject[initialCapacity];this.capacityIncrement =capacityIncrement;

}

扩容机制:

jdk1.8的扩容算法:newCapacity = oldCapacity + ( ( capacityIncrement > 0 ) ? capacityIncrement : oldCapacity );

jdk1.6的扩容算法:newCapacity = ( capacityIncrement > 0 ) ? ( oldCapacity + capacityIncrement ) : (  oldCapacity  * 2 );

参数介绍:capacityIncrement 是容量修正(即容量新增大小),没有设置,默认为0    ,newCapacity 是扩容后的容量大小,oldCapacity 是扩容前的大小

一观察,就会发现1.6与1.8的写法变化不大,但是仔细一分析,就会发现jdk1.6中有使用乘法运算,即 oldCapacity  * 2。 在jdk1.8中换成了加法运算,这是因为乘法的效率是低于加法的,这应该算法的优化。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值