ArrayList
它可以以O(1)时间复杂度对元素进行随机访问。因为数组申请的内存是一段连续的内存地址,由于所申请的内存地址是连续的,我们只需要知道第一个内存地址和数组空间,就可以推断出查找下标的内存地址,即所存放的元素。
- 优缺点:数组查询快,根据地址和索引直接获取元素。
- 数组增删慢,每次都需要创建新数组,且移动元素位置。
新旧数组之间元素的复制函数:
* @param src the source array.
* @param srcPos starting position in the source array.
* @param dest the destination array.
* @param destPos starting position in the destination data.
* @param length the number of array elements to be copied.
public static native void arraycopy(Object src, int srcPos,
Object dest, int destPos,
int length);
添加元素
只有在扩容的时候才会通过复制元素创建新的数组。
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);
//从原数组下标为0开始复制,复制长度为Math.min(original.length, newLength)
//原数组元素从新数组下标为0的位置开始填充,填充的长度为Math.min(original.length, newLength)
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}
删除元素remove
元素的删除是从下标为0的元素开始,每次删除后都会对原数组通过复制方式将其余元素向前移动。
正常删除方式使用:
//创建迭代器对象,局部变量初始化
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()){
iterator.next();//返回删除的元素
iterator.remove();
)
private class Itr implements Iterator<E> {
int cursor; // 作用就是用来判断数组中是否还存在元素
int lastRet = -1; // 下一个元素的下标
int expectedModCount = modCount;//创建对象时初始化变量
public boolean hasNext() {
return cursor != size;
}
public E next() {
checkForComodification();
int i = cursor;
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1;
return (E) elementData[lastRet = i];
}
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
//迭代器中remove方法最终调用集合中remove方法【说明集合中的remove方法不是提供给外部调用的】,通过数组复制方式进行删除
ArrayList.this.remove(lastRet);
//游标复位,保证下次删除从下标为0的位置开始
cursor = lastRet;//该方法中的cursor ==lastRet==0
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
ArrayList.this.remove(lastRet):
public E remove(int index) {
rangeCheck(index);
//即使删除modCount++
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
//删除元素时,原始数组与目标数组都是同一个数组,所以只是元素的移动。
//index:被删除元素的下标位置
//numMoved:被移动元素的个数
//index+1:被移动元素的起始位置
System.arraycopy(elementData, index+1, elementData, index,numMoved);
增强For循环之For…Each
jdk1.5之后出现的,其内部原理是一个迭代器Iterator;实现Iterator接口的类才能使用迭代器和增强for;简化数组和collection集合的遍历。
如下方式:该种方式删除会产生异常 java.util.ConcurrentModificationException。
for(Integer i : list){
System.out.println(list.size());
list.remove(i);
}
编译之后:
Iterator i$ = list.iterator();
while(i$.hasNext()) {
Integer i = (Integer)i$.next();
System.out.println(list.size());
list.remove(i);
}
next()方法里面会校验modCount 与 expectedModCount值,但是此时remove方法调用的不是迭代器中的remove方法,导致expectedModCount值不会发生改变,第二次遍历由于next()方法校验会抛出ConcurrentModificationException异常。
remove方法一共有3个:
- public E remove(int index);
- public boolean remove(Object o);
迭代器中的public void remove(),每次值发生变化会重置expectedModCount值;
扩容
初始化容量为10的集合容器;
每次扩容量为当前容量的一半;
扩容的条件为:当前数组中的元素大于数组长度
public boolean add(E e) {
//首次扩容size = 0
ensureCapacityInternal(size + 1); // Increments modCount!!
//size是数组索引:先赋值后运算
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == EMPTY_ELEMENTDATA) {//首次成立
//DEFAULT_CAPACITY = 10
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}
ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// 扩容的条件:minCapacity = size + 1;size在上次添加元素时会自增1
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//首次 minCapacity 当前数组中的元素个数
private void grow(int minCapacity) {
// overflow-conscious code
//当前数组容器的容量
int oldCapacity = elementData.length;
//每次容量扩容数量是oldCapacity + (oldCapacity >> 1);
//10 --> 15 -->22--->...
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:
//首次初始化容量为10的数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
LinkedList
void linkLast(E e) {
final Node<E> l = last;
//双向循环列表
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
线程安全问题
class ArrayListInThread implements Runnable {
//线程不安全
private List threadList = new ArrayList();
//线程安全
//private List threadList = Collections.synchronizedList(new ArrayList());
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//把当前线程名称加入list中
threadList.add(Thread.currentThread().getName());
}
public static void main(String[] args) throws InterruptedException {
ArrayListInThread listThread = new ArrayListInThread();
for (int i = 0; i < 100; i++) {
Thread thread = new Thread(listThread, String.valueOf(i));
thread.start();
}
//等待子线程执行完
Thread.sleep(2000);
System.out.println(listThread.threadList.size());
//输出list中的值
for (int i = 0; i < listThread.threadList.size(); i++) {
if (listThread.threadList.get(i) == null) {
System.out.println();
}
System.out.println(listThread.threadList.get(i) + " ");
}
}
}
结果中,有的值没有出现(结果一中3没有出现),有的出现了null值,这是由于赋值时出现了覆盖。赋值语句为:elementData[size++] = e,这条语句可拆分为两条:
elementData[size] = e;
size ++;
假设A线程执行完第一条语句时,CPU暂停执行A线程转而去执行B线程,此时ArrayList的size并没有加一,这时在ArrayList中B线程就会覆盖掉A线程赋的值,而此时,A线程和B线程先后执行size++,便会出现值为null的情况;
ArrayIndexOutOfBoundsException异常:
则是A线程在执行ensureCapacity(size+1)后没有继续执行,此时恰好minCapacity等于oldCapacity,B线程再去执行,同样由于minCapacity等于oldCapacity,ArrayList并没有增加长度,B线程可以继续执行赋值(elementData[size] = e)并size ++也执行了,此时,CPU又去执行A线程的赋值操作,由于size值加了1,size值大于了ArrayList的最大长度,
因此便出现了ArrayIndexOutOfBoundsException异常。
既然ArrayList是线程不安全的,但如果需要在多线程中使用,可以采用list list =Collections.synchronizedList(new ArrayList)来创建一个ArrayList对象。
数组与链表的优缺点
- ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。
- 对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。
- 对于新增和删除操作add和remove,LinkedList比较占优势,因为ArrayList要移动数据 。
- LinkedList是以元素链表的形式存储它的数据,每一个元素都和它的前一个和后一个元素链接在一起,在这种情况下,查找某个元素的时间复杂度是O(n)。
**备注1:**如果要在数组中增加一个元素,需要移动大量元素,那么这个元素后的所有元素的内存地址都要往后(前)移动(数组的内存地址是连续的),对最后一个元素插入(或删除)时才比较快,然后将要增加的元素放在其中。同样的道理,如果想删除一个元素,同样需要移动大量元素去填掉被移动的元素。
**备注2:**增加和删除一个元素对于链表数据结构只要修改元素中的指针(包括指针指向,节点值)就可以了。如果应用需要经常插入和删除元素你就需要用链表数据结构了。