标签:依次 length 操作 就会 顺序 new 最大值 表结构 链表实现
ArrayList : 基于数组实现的非线程安全的集合。查询元素快,插入,删除中间元素慢。
查询数据快,是因为数组可以通过下标直接找到元素。
写数据慢有两个原因:一是数组复制过程需要时间,二是扩容需要实例化新数组也需要时间。
ArrayList在执行查询操作时:
第一步:先判断下标是否越界。
第二步:然后在直接通过下标从数组中返回元素。
ArrayList在执行顺序添加操作时:
第一步:通过扩容机制判断原数组是否还有空间,若没有则重新实例化一个空间更大的新数组,把旧数组的数据拷贝到新数组中。
第二步:在新数组的最后一位元素添加值。
ArrayList在执行中间插入操作时:
第一步:先判断下标是否越界。
第二步:扩容。
第三步:若插入的下标为i,则通过复制数组的方式将i后面的所有元素,往后移一位。
第四步:新数据替换下标为i的旧元素。
删除也是一样:只是数组往前移了一位,最后一个元素设置为null,等待JVM垃圾回收。
扩容机制
在JDK1.7当中,当第一个元素添加时,ensureCapacityInternal()方法会计算ArrayList的扩容大小,默认为10;
其中grow()方法最为重要,如果需要扩容,那么扩容后的大小是原来的1.5倍,实际上最终调用了Arrays.copyOf()方法得以实现;
//添加元素e public boolean add(E e) { ensureCapacityInternal(size + 1); //将对应角标下的元素赋值为e: elementData[size++] = e; return true; } //得到最小扩容量 private void ensureCapacityInternal(int minCapacity) { //如果此时ArrayList是空数组,则将最小扩容大小设置为10: if (elementData == EMPTY_ELEMENTDATA) { minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } //判断是否需要扩容: ensureExplicitCapacity(minCapacity); } //判断是否需要扩容 private void ensureExplicitCapacity(int minCapacity) { //操作数+1 modCount++; //判断最小扩容容量-数组大小是否大于0: if (minCapacity - elementData.length > 0) //扩容: grow(minCapacity); } //ArrayList动态扩容的核心方法: private void grow(int minCapacity) { //获取现有数组大小: 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(int minCapacity) { if (minCapacity < 0) throw new OutOfMemoryError(); //如果新的容量大于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参数指定的元素数。 * @param minCapacity 所需的最小容量 */ public void ensureCapacity(int minCapacity) { int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)? //如果不是默认空数组,则将下限设为0 0 //如果ArrayList是通过无参构造且尚未添加元素的实例,则 : DEFAULT_CAPACITY; //也就是说该方法只有一个特例,就是当该实例是通过无参构造且尚未添加元素时,此时扩容应该至少扩张为 //DEFAULT_CAPACITY,否则不予处理,在第一次add时扩容为DEFAULT_CAPACITY(回顾之前的解释)。 //其他情况下,只要大于0就开始扩容程序。 if (minCapacity > minExpand) { ensureExplicitCapacity(minCapacity); } }
基于双向链表实现的非线程安全的集合。查询元素慢,插入,删除中间元素快。
JDK1.7之前的版本是环形链表,而到了JDK1.7以后进行了优化,变成了直线型链表结构
不能像数组一样随机访问,必须是每个元素依次遍历直到找到元素为止。其结构的特殊性导致它查询数据慢。
LinkedList在执行查询操作时:
第一步:先判断元素是靠近头部,还是靠近尾部。
第二步:若靠近头部,则从头部开始依次查询判断。和ArrayList的elementData(index)
相比当然是慢了很多。
LinkedList在插入元素的思路:
第一步:判断插入元素的位置是链表的尾部,还是中间。
第二步:若在链表尾部添加元素,直接将尾节点的下一个指针指向新增节点。
第三步:若在链表中间添加元素,先判断插入的位置是否为首节点,是则将首节点的上一个指针指向新增节点。否则先获取当前节点的上一个节点(简称A),并将A节点的下一个指针指向新增节点,然后新增节点的下一个指针指向当前节点。
Vector的底层是基于数组实现的线程安全的集合。线程同步(方法被synchronized修饰),性能比ArrayList差,默认大小也是10。
主要特点:查询快,增删慢 , 线程安全,但是效率低。
创建对象与ArrayList类似,但有一点不同,它可以设置扩容是容量增长大小。
根据Vector的三个构造器就可以很明了的理解 new Vector(); 与 new Vector(10);与 new Vector(10,0); 三个是等同的。
1.无参构造器 public Vector() { this(10); } 2.传一个参数(容量大小) 容量大小即底层数组大小 public Vector(int initialCapacity) { this(initialCapacity, 0); } 3.传两个参数(容量大小,容量修正) 容量修正即扩容时的增加量 public Vector(int initialCapacity, int capacityIncrement) { super(); if (initialCapacity < 0) throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); this.elementData = new Object[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中换成了加法运算,这是因为乘法的效率是低于加法的,这应该算法的优化。
标签:依次 length 操作 就会 顺序 new 最大值 表结构 链表实现
原文地址:https://www.cnblogs.com/ywblogs/p/11978164.html