以下几篇文章都是对java集合的一个介绍,这些文章并非都是我的原创,主要是集合了各种途径获取的一个总结。对于集合,我们主要从以下四点关注:
1.是否允许为空;
2.是否允许重复数据;
3.是否有序,有序是指读取顺序与存放顺序是否一致;
4.是否线程安全。
ArrayList
(1)ArrayList 是一个数组队列,相当于动态数组。与Java中的数组相比,它的容量能动态增长。它继承于AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口。
(2)ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
(3)ArrayList 实现了RandmoAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。稍后,我们会比较List的“快速随机访问”和“通过Iterator迭代器访问”的效率。
(4)ArrayList 实现了Cloneable接口,即覆盖了函数clone(),能被克隆。
(5)ArrayList 实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。
(6)和Vector不同,ArrayList中的操作不是线程安全的!所以,建议在单线程中才使用ArrayList,而在多线程中可以选择Vector或者CopyOnWriteArrayList。
ArrayList是一个以数组形式实现的集合,首先我们来看一下ArrayList的基本元素。
ArrayList与Collection的关系如图:
ArrayList包含了两个重要的对象:elementData和Size。
(1)elementData是“Object[]类型的数组”,它保存了添加到ArrayList中的的元素。实际上,elementData是一个动态数组,我们可以通过构造函数ArrayList(int initialCapacity)来执行它的初始化容量;如果通过不含参数的构造函数ArrayList()来创建,则它的默认容量是10。
(2)size是动态数组的实际大小。
添加元素
public class Test {
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
list.add("00");
list.add("11");
}
}
我们来看一下添加操作的底层源码实现:
public boolean add(E e) {
//这个方法是为了实现扩容的作用
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
底层在调用add方法的时候只是给elementData的某个位置添加了一个数据而已,下图为例
为了方便理解才这样画的,elementData中存储的应该是堆内存中元素的引用,而不是实际的元素,这样画会让人误以为elementData中存放了实际元素,在这里提醒一下。
*扩容*
数组扩容的时候我们会先把数组乘以3,再除2后加1.为什么这样呢?
1、如果一次性扩容太大,必然会造成空间的浪费。
2、如果一次扩容不够,那么下一次扩容操作会很快发生,这回降低程序的运行效率,要知道扩容还是比较耗费性能的;
所以扩容多少,是jdk开发人员在时间和空间上的一个权衡。最后调用的是Arrays的copyOf方法,将原数组里的内容全部复制到新的数组里面。
删除元素
ArrayList支持两种删除方式:1.按照下标删除;2.按照元素删除,这会删除与指定元素相同的第一个元素。
//jdk源码
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // Let gc do its work
return oldValue;
}
两种删除方式都是调用这段代码。都是做了两件事:
1、将指定元素后面位置的所有元素,利用System.arraycopy方法整体向前移动一位。
2、最后一个位置的元素指定外围null,这样gc就会去回收它。
插入元素
插入元素用的也是add方法,其方法与删除操作类似。
ArrayList的遍历方式
(1)通过迭代器遍历
//迭代器遍历
String value = null;
Iterator it = list.iterator();
while(it.hasNext()) {
value = (String) it.next();
System.out.println(value);
}
System.out.println("--------分割线-----------");
//随机访问,通过索引遍历
String value2 = null;
for(int i = 0; i < list.size(); i++) {
value = list.get(i);
}
String value3 = null;
//for循环遍历
for(String str : list) {
value3 = str;
}
那么我们来比较一下它们的运行效率如何:
运行结果:
iteratorThroughRandomAccess:3 ms
iteratorThroughIterator:8 ms
iteratorThroughFor2:5 ms
由此可见,遍历ArrayList时,使用随机访问(即,通过索引序号访问)效率最高,而使用迭代器的效率最低!
ArrayList的优缺点
优点:
1、ArrayList底层是以数组实现,是一种随机访问模式,再加上实现了RandomAccess接口,因此查找也就是get非常快。
2、ArrayList顺序添加非常方便。
缺点:
无论是删除或插入元素(除非是最后一个元素)都涉及到一次元素复制,如果比较多则是一个耗费性能的过程。
因此ArrayList比较适合顺序添加、随机访问的场景。
ArrayList和Vector的区别
ArrayList是线程非安全的,这很明显,因为ArrayList中所有方法都不是同步的,在并发的情况下一定会出现线程安全问题。那么解决方法有两种:
1、使用Collections.synchronizedList方法把ArrayList变成一个线程安全的List。
List<String> synchronizedList = Collections.synchronizedList(list);
synchronizedList.add("44");
2、另一个方法就是使用vector,它是ArrayList线程安全版本,大部分实现完全一样,区别在于:
1、vector是线程安全的,ArrayList是非线程安全;
2、vector可以指定增长因子,如果该增长因子指定了,那么扩容的时候每次新数组大小就会在原基础上加上增长因子;如果不指定那么就是原数组*2。
为什么ArrayList的elementData是用transient修饰
ArrayList中数组的定义:
private transient Object[] elementData;
ArrayList实现了serializable接口,这意味着ArrayList可以被序列化,用transient修饰elementData意味着我们不希望elementData数组序列化。这时为什么?因为序列化ArrayList的时候,ArrayList数组未必是满的,比如说数组大小是10,现在只有3和数组,那么是否有必要序列化整个elementData呢?显然是没有必要的,因此ArrayList重写了writeObject方法,每次序列化都调用它,先调用defaultWriteObject() 方法序列化ArrayList中非transient元素,然后遍历elementData,只序列化那些有的元素,这样:
1、加快了序列化的速度;
2、减小了序列化之后文件的大小。