List
List
可以通过如下方式,得到一个list
Integer arr[] = { 8, 4, 3, 9, 5, 6 }; List<Integer> x = Arrays.asList(arr);
也可通过如下方式将list变为数组
List<String> y = Arrays.asList(array); String[] string = y.toArray(new String[0]);
ArrayList
1.基于数组实现,是基于索引的数据结构,它使用索引在数组中搜索和读取数据是很快的,可以直接返回数组中index位置的元素,因此在随机访问集合元素上有较好的性能。Array获取数据的时间复杂度是O(1),但是要插入、删除数据却是开销很大的,因为这需要移动数组中插入位置之后的的所有元素。
2.我们创建ArrayList的时候,可以使用指定数组大小的构造函数或者直接是默认的构造函数。当使用默认构造函数的时候,数组的初始化大小是0,当第一次调用add()方法的时候,会变成默认的初始化大小10。当然,也可以指定长度,ArrayList在初始化的时候指定长度肯定是要比不指定长度的性能好很多, 这样不用重复的申请空间, 复制数组, 销毁老的分配空间
ArrayList扩容的核心从ensureCapacityInternal方法说起。可以看到前面介绍成员变量的提到的ArrayList有两个默认的空数组:
DEFAULTCAPACITY_EMPTY_ELEMENTDATA:是用来使用默认构造方法时候返回的空数组。如果第一次添加数据的话那么数组扩容长度为DEFAULT_CAPACITY=10。
EMPTY_ELEMENTDATA:出现在需要用到空数组的地方,其中一处就是使用自定义初始容量构造方法时候如果你指定初始容量为0的时候就会返回。
// 增加元素的方法 public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } //判断当前数组是否是默认构造方法生成的空数组,如果是的话minCapacity=10反之则根据原来的值传入下一个方法去完成下一步的扩容判断 private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; } //minCapacitt表示修改后的数组容量,minCapacity = size + 1 private void ensureCapacityInternal(int minCapacity) { //判断看看是否需要扩容 ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); }
下面谈谈ensureExplicitCapacity方法(modCount设计到Java的快速报错机制后面会谈到),可以看到如果修改后的数组容量大于当前的数组长度那么就需要调用grow进行扩容,反之则不需要。
//判断当前ArrayList是否需要进行扩容
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
// int[] a = new int[5]; 数组创建的时候是多大, a.length就等于5
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
最后看下ArrayList扩容的核心方法grow(),下面将针对三种情况对该方法进行解析:
-
当前数组是由默认构造方法生成的空数组并且第一次添加数据。此时minCapacity等于默认的容量(10)那么根据下面逻辑可以看到最后数组的容量会从0扩容成10。而后的数组扩容才是按照当前容量的1.5倍进行扩容;
-
当前数组是由自定义初始容量构造方法创建并且指定初始容量为0。此时minCapacity等于1那么根据下面逻辑可以看到最后数组的容量会从0变成1。这边可以看到一个严重的问题,一旦我们执行了初始容量为0,那么根据下面的算法前四次扩容每次都 +1,在第5次添加数据进行扩容的时候才是按照当前容量的1.5倍进行扩容。
-
当扩容量(newCapacity)大于ArrayList数组定义的最大值后会调用hugeCapacity来进行判断。如果minCapacity已经大于Integer的最大值(溢出为负数)那么抛出OutOfMemoryError(内存溢出)否则的话根据与MAX_ARRAY_SIZE的比较情况确定是返回Integer最大值还是MAX_ARRAY_SIZE。这边也可以看到ArrayList允许的最大容量就是Integer的最大值(-2的31次方~2的31次方减1)
private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); if (newCapacity - minCapacity < 0) newCapacity = minCapacity; if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); }
3.ArrayList非线程安全,底层是一个Object[],添加到ArrayList中的数据保存在了elementData属性中。
-
当调用
new ArrayList<>()
时,将一个空数组{}赋值给了elementData,这个时候集合的长度size为默认长度0; -
当调用
new ArrayList<>(100)
时,根据传入的长度,new一个Object[100]赋值给elementData,当然如果玩儿的话,传了一个0,那么将一个空数组{}赋值给了elementData; -
当调用new ArrayList<>(new HashSet())时,根据源码,我们可知,可以传递任何实现了Collection接口的类,将传递的集合调用toArray()方法转为数组内赋值给elementData;
注意:在传入集合的ArrayList的构造方法中,有这样一个判断
if (elementData.getClass() != Object[].class),
给出的注释是:c.toArray might (incorrectly) not return Object[] (see 6260652),即调用toArray方法返回的不一定是Object[]类型,查看ArrayList源码
public Object[] toArray() { return Arrays.copyOf(elementData, size);}
我们发现返回的确实是Object[],那么为什么还会有这样的判断呢?如果有一个类CustomList继承了ArrayList,然后重写了toArray()方法呢?
public class CustomList<E> extends ArrayList { @Override public Integer [] toArray() { return new Integer[]{1,2}; }; public static void main(String[] args) { Object[] elementData = new CustomList<Integer>().toArray(); System.out.println(elementData.getClass()); System.out.println(Object[].class); System.out.println(elementData.getClass() == Object[].class); } }
执行结果:
class [Ljava.lang.Integer;
class [Ljava.lang.Object;
false
接着说,如果传入的集合类型和我们定义用来保存添加到集合中值的Object[]类型不一致时,ArrayList做了什么处理?读源码看到,调用了Arrays.copyOf(elementData, size, Object[].class);
,继续往下走
public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
return copy;
}
我们发现定义了一个新的数组,将原数组的数据拷贝到了新的数组中去。
LinkedList
1. 相对于ArrayList,LinkedList的随机访问集合元素时性能较差,因为需要在双向列表中找到要index的位置,再返回;但在插入,删除操作是更快的。因为LinkedList不像ArrayList一样,不需要改变数组的大小,也不需要在数组装满的时候要将所有的数据重新装入一个新的数组,这是ArrayList最坏的一种情况,时间复杂度是O(n),而LinkedList中插入或删除的时间复杂度仅为O(1)。ArrayList在插入数据时还需要更新索引(除了插入数组的尾部)。
2. LinkedList需要更多的内存,因为ArrayList的每个索引的位置是实际的数据,而LinkedList中的每个节点中存储的是实际的数据和前后节点的位置。
3.将LinkedList转换成ArrayList: ArrayList<String> arrayList = new ArrayList<String>(linkedList);
使用场景:
(1)如果应用程序对数据有较多的随机访问,ArrayList对象要优于LinkedList对象;
( 2 ) 如果应用程序有更多的插入或者删除操作,较少的数据读取,LinkedList对象要优于ArrayList对象;
(3)不过ArrayList的插入,删除操作也不一定比LinkedList慢,如果在List靠近末尾的地方插入,那么ArrayList只需要移动较少的数据,而LinkedList则需要一直查找到列表尾部,反而耗费较多时间,这时ArrayList就比LinkedList要快。