Java8源码分析-ArrayList、LinkedList、Vector
java集合框架体系图
ArrayList
LinkedList
Vector
集合涉及的类
Iterator
Array
集合框架体系
List接口:
1.有序的
2.允许多个null元素
3.具体的实现类常用的:ArrayList、Vector、LinkedList
在实际开发中,我们如何选择list的具体实现类:
1.安全性问题(多线程)
2.是否频繁插入,删除操作
3.是否是存储后遍历
下面就来介绍了三种常用的集合类
1.ArrayList
1.1.内部为数组,初始化长度为10
private static final int DEFAULT_CAPACITY = 10;
transient Object[] elementData;
1.2.增加元素与扩容
1.增加元素:在不扩容的情况下,会把元素插入到尾部。在扩容的情况下,把新元素添加到扩容以后的数组中
2.扩容:把原来的数组复制到另一个内存空间更大的数组中。增加0.5倍,最大容量为Integer.MAX_VALUE-8
在ensureCapacityInternal()方法中调用了grow()获得新数组的容量
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!! 确保容量
elementData[size++] = e; //将新元素加到数组后面
return true;
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;//源码注释为一些虚拟机在数组中保留一些标题字,所以要减少1个为
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1); //如果超出了数组大小,正常情况下,1.5倍
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)//极端情况,快满了,限制大小
newCapacity = hugeCapacity(minCapacity);//扩容上限为0x7fffffff-8 即int大小-8
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
1.3.详解 int hugeCapacity(int minCapacity) 方法
在上面的grow(int minCapacity)中调用了此方法来获得扩容的大小
private static int hugeCapacity(int minCapacity) {
if (minCapacity < 0) // overflow
throw new OutOfMemoryError();
return (minCapacity > MAX_ARRAY_SIZE) ?
Integer.MAX_VALUE :
MAX_ARRAY_SIZE;
}
解析:当minCapacity到临界值时,即当minCapacity*1.5 >MAX_ARRAY_SIZE 就会调用hugeCapacity(minCapacity)
,minCapacity值的临界值范围可能是MAX_ARRAY_SIZE/1.5 ~ MAX_ARRAY_SIZE-1
这个时候 newCapacity 取得是 MAX_ARRAY_SIZE或者Integer.MAX_VALUE
溢出情况:当minCapacity加到了Integer.MAX_VALUE 的时候,再加1,就变成负数了,所以这里就判断为负数的时候,抛出溢出异常
1.4.删除 remove(int index)
remove的时候,1.定位到位置、2.然后将后面的元素往前挪一位、3.数组长度--size(减一),最后一个元素置空
(数组和链表,效率比较:删除一个元素的时候,数组和链表复杂度都是o(n),但是链表只要定位到了,那删除就是o(1),也就是链表效率高)
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; // clear to let GC do its work
return oldValue;
}
1.5.clear()的时候,不仅仅设置size=0就行了,要遍历设置为null
因为要释放内存,将引用删掉,垃圾管理器就会去清理堆内存里面的对象数据
public void clear() {
modCount++;
// clear to let GC do its work
for (int i = 0; i < size; i++)
elementData[i] = null;
size = 0;
}
1.6 ArrayList与Vector的区别
ArrayList线程不安全,适合在单线程中使用,效率较高
Vector线程安全,适合在多线程中使用,但效率较低
1.7.Iterator in java.util.ArrayList
1.7.1.next()的实现
public E next() {
checkForComodification();
int i = cursor;//cursor 默认值为0
if (i >= size)
throw new NoSuchElementException();
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
cursor = i + 1; //每次完了只会cursor都会增加1
return (E) elementData[lastRet = i];
}
1.7.2.hasNext()的实现
当前位置不是最大值的时候,返回true
public boolean hasNext() {
return cursor != size;
}
1.7.3.remove()的实现
删除当前位置
public void remove() {
if (lastRet < 0)
throw new IllegalStateException();
checkForComodification();
try {
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
2.LinkedList
2.1.内部为双链表结构,静态内部类Node作为元素结构
类的成员变量有:
transient int size = 0;//链表大小
transient Node<E> first;//头部节点
transient Node<E> last;//尾部节点
2.2.静态内部类作为元素结构
private static class Node<E> {
E item; //元素
Node<E> next; //下一个
Node<E> prev; //上一个
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
2.3.增加 linkFirst(E e),连接到第一个节点,即头部
addFirst(E e)调用的就是此方法
/**
* Links e as first element.
*/
private void linkFirst(E e) {
final Node<E> f = first;//1.临时设置f为头部
final Node<E> newNode = new Node<>(null, e, f);//2.创建一个元素
first = newNode;//3.将头部指向新节点
if (f == null)
last = newNode; //如果头部是null,说明是第一个节点
else
f.prev = newNode; //4.然后将旧的头结点上一个指向新节点的尾部
size++;
modCount++;
}
2.4.增加 linkLast(E e)
链接到最后一个节点,即尾部,比较简单,两个步骤。减少了arraylist扩容的步骤,效率大大提高,很舒服
addLast(E e) 、add(E e)调用的就是此方法
/**
* Links e as last element.
*/
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++;
}
2.5.遍历 根据索引查询元素
根据索引来查询该位置的元素就体现了链表的弊端,复杂度o(n),而数组是o(1)
get(int index) 用的就是它
这里用了一个技巧, 通过index判断在前半段还是后半段。如果是前半段就从头部开始查询,否则从后半段查询,这样大概可以减少一半的时间
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
2.6.删除 remove(Object o)
删除,首先要遍历查询到位置,遍历的阶段比数组稍慢。
但是查询到位置之后,链表删除效率就更高,o(1),数组是o(n)
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);//查询到node之后,元素指针移动的删除操作 o(1) 区别于数组
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
2.7.定位 indexOf(Object o) lastIndexOf(Object o)
找第一个,从头遍历。找最后一个,从尾遍历。效率和数组基本一样,都是遍历
public int indexOf(Object o) {
int index = 0;
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null)
return index;
index++;
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item))
return index;
index++;
}
}
return -1;
}
3.Vector
和ArrayList一样,都是由数组实现的。
不同的是它支持线程的同步,即某一时刻只有一个线程能够写Vector,避免多线程同时写而引起的不一致性,在public方法加入了synchronized修饰符,但实现同步需要很高的花费,因此,访问它比访问ArrayList慢。
3.1.扩容
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
如果capacityIncrement(增加容量)传来的<=0,则oldCapacity+oldCapacity 即扩容两倍,可能就是因为要能同步,从而此集合可能会频繁插入数据,所以干脆就每次增加两倍。当数据量比较大的时候,Vector比较有优势
集合涉及的类
Iterator
Iterator是一个接口,它是集合的迭代器。集合可以通过Iterator去遍历集合中的元素。Iterator提供的API接口如下:
forEachRemaining(Consumer<? super E> action):为每个剩余元素执行给定的操作,直到所有的元素都已经被处理或行动将抛出一个异常
hasNext():如果迭代器中还有元素,则返回true。
next():返回迭代器中的下一个元素
remove():删除迭代器新返回的元素。
Array
思考
1.像这种方法为什么不这是public 而是private?即为什么不想被外部使用?
//判断index是否在有效范围内
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}