【搞定Java基础-集合篇】第二篇 源码ArrayList、LinkedList和Vector的区别

文章目录


https://blog.csdn.net/u012814441/article/details/80671604

1、ArrayList、LinkedList、HashMap中都有一个字段叫modCount(表示list结构上被修改的次数。add,remove这些都会改变modcount)
  • 结构上的修改指的是那些改变了list的长度大小或者使得遍历过程中产生不正确的结果的其它方式。
1.1 Fail-Fast 机制: java.util.ArrayList 不是线程安全的,如果出现线程不安全,那么将抛出ConcurrentModificationException,这就是所谓fail-fast策略。【通过modCount来实现,该字段被Iterator以及ListIterator的实现类所使用,如果该值被意外更改,Iterator或者ListIterator 将抛出ConcurrentModificationException异常】

这一策略在源码中的实现是通过 modCount 域,modCount 顾名思义就是修改次数,对ArrayList 内容的修改都将增加这个值,那么在迭代器初始化过程中会将这个值赋给迭代器的 expectedModCount。

在迭代过程中,判断这个对象的 modCount 跟 expectedModCount 是否相等,如果不相等就表示已经有其他线程修改了 ArrayList。

所以在这里和大家建议,当大家遍历那些非线程安全的数据结构时,尽量使用迭代器

一、ArrayList源码分析
1.1 ArrayList构造方法初始化一个空数组,直到第一次add的时候才初始化这个数组(因为第一次扩充时默认大小10一定是现在大小0*1.5的,所以第一次扩充是扩充到10)
  • ArrayList 的底层是一个 Object 数组,并且由 transient 修饰。
private static final int DEFAULT_CAPACITY = 10;
transient Object[] elementData; // non-private to simplify nested class access
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
1.2 调用add方法插入数据【1、当使用add方法的时候首先调用ensureCapacityInternal方法,传入size+1进去,检查是否需要扩充elementData数组的大小。2、检查完毕之后再将e赋值给elementData数组 ,size再自增1。】【newCapacity = 扩充数组为原来的1.5倍(不能自定义),如果还不够,就使用它指定要扩充的大小minCapacity ->然后判断 minCapacity 是否大于 MAX_ARRAY_SIZE(Integer.MAX_VALUE - 8) ,如果大于,就取 Integer.MAX_VALUE—>ArrayList中copy数组的核心就是System.arraycopy方法,将original数组的所有数据复制到copy数组中,这是一个本地方法】
private int size;
public boolean add(E e) {
//size变量是用来记录数组的元素总数。
        ensureCapacityInternal(size + 1); // 1、 Increments modCount!! 检查是否要扩充elementData数组
        elementData[size++] = e; //2、插入数据
        return true;
    }
    
    
    
private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        ensureExplicitCapacity(minCapacity);//数组至少要有minCapacity那么大
    }
private void ensureExplicitCapacity(int minCapacity) {
        modCount++; //只要进入这个方法就会增加modCount
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);// 数组不够大就扩充数组
    }
    
    
   // 1、newCapacity =  扩充数组为原来的1.5倍,如果还不够,就使用它指定要扩充的大小minCapacity 【->然后判断 minCapacity 是否大于 MAX_ARRAY_SIZE ,如果大于,就取 Integer.MAX_VALUE】
   // 2、copyOf 复制原数组到新数组
 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);
        // MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8,如果大于MAX_ARRAY_SIZE,则 hugeCapacity 会返回 Integer.MAX_VALUE
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    
    
    @SuppressWarnings("unchecked")
    public static <T> T[] copyOf(T[] original, int newLength) {
        return (T[]) copyOf(original, newLength, original.getClass());
    }
    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
        @SuppressWarnings("unchecked")
        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)); // ********核心System.arraycopy,本地方法
        return copy;
    }
    
public static native void arraycopy(Object src,  int  srcPos,
                                        Object dest, int destPos,
                                        int length);// copy的核心方法,从原数组的某个位置开始复制到目的数组的某个位置开始,复制length个数
1.3 add(int index, E element)指定位置插入数据【1、ensureCapacityInternal检查是否要扩充数组,2、数据index及以后都后移一位,index位置插入数据System.arraycopy】
public void add(int index, E element) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        ensureCapacityInternal(size + 1); //1、Increments modCount!!检查是否要扩充elementData数组
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index); // 2、为了空出index的位置,将elementData数组从index开始到(size - index)位置,都后移1位。
        elementData[index] = element; // 3、插入数据
        size++;
    }
1.4 remove 指定位置移除元素[1、numMoved=size-1-index表示删除元素之后要移动元素的总数,将elementData数组从index+1开始的numMoved个元素,往前移动1位(覆盖index位置的元素)。2、接着将elementData数组的最后一个元素设置为空,方便GC回收内存。 System.arraycopy],返回被删除的值
public E remove(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        modCount++;
        E oldValue = (E) elementData[index];
        int numMoved = size - index - 1; //表示删除元素之后要移动元素的总数
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved); //1、将elementData数组从index+1开始的numMoved个元素,往前移动1位(覆盖index位置的元素)
        elementData[--size] = null; // 2、将elementData数组的最后一个元素设置为空,方便GC回收内存。
        return oldValue;
    }
1.5 get(int index)查找操作
public E get(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
        return (E) elementData[index];
    }
二、Vector源码分析
2.1 Vector构造方法一开始就初始化长度为10,和数组扩增时容量为原来的2倍的object数组
public class Vector<E>
    extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
	
	protected Object[] elementData;
	
	protected int elementCount;        // 集合中元素的个数
	
	protected int capacityIncrement;   // 扩容增量
	
	// 构造函数1:
public Vector(int initialCapacity, int capacityIncrement) {
	super();
	if (initialCapacity < 0)
		throw new IllegalArgumentException("Illegal Capacity: "+
										   initialCapacity);
	this.elementData = new Object[initialCapacity];
	this.capacityIncrement = capacityIncrement;
}
 
// 构造函数2:
public Vector(int initialCapacity) {
	this(initialCapacity, 0);
}
 
// 构造函数3:
public Vector() {
	this(10);
}
 
// 构造函数4:
public Vector(Collection<? extends E> c) {
	elementData = c.toArray();
	elementCount = elementData.length;
	// c.toArray might (incorrectly) not return Object[] (see 6260652)
	if (elementData.getClass() != Object[].class)
		elementData = Arrays.copyOf(elementData, elementCount, Object[].class);

	
	// 自己实现的迭代器
	private class Itr implements Iterator<E> {
        // ...
        int expectedModCount = modCount;
	}
	
	final class ListItr extends Itr implements ListIterator<E> {
		// ...
	}
}
2.2 add方法插入数据。比起ArrayList,Vector许多对外公开的方法都加上了synchronized关键字声明,方法基本和ArrayList一样,比较明显不同就是grow方法数组容量的扩增算法,oldCapacity + ((capacityIncrement > 0) ?capacityIncrement : oldCapacity)。 如果你指定了capacityIncrement变量的值,那么数组容量按你设置的值来扩增,否则每次默认容量就只是扩增一倍
public synchronized boolean add(E e) {// 插入数据
        modCount++;
        ensureCapacityHelper(elementCount + 1); //检查是否要扩增数组
        elementData[elementCount++] = e;
        return true;
}
private void ensureCapacityHelper(int minCapacity) {
        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
}
private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                         capacityIncrement : oldCapacity);
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        elementData = Arrays.copyOf(elementData, newCapacity);
}
三、LinkedList 源码【双向链表】
3.1 构造方法[为空,此时first和last都为null]
transient Node<E> first;
transient Node<E> last;
public LinkedList() { 
}

//双向链表节点
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;
        }
    }
3.2 add插入数据
public boolean add(E e) {
   linkLast(e);
      return true; 
}
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++;
    }
3.3 add 根据下标插入数据【1.node()方法找到该下标的Node; 2、在该Node前插入】
public void add(int index, E element) {
        checkPositionIndex(index);//检查是否越界
        if (index == size)
            linkLast(element);
        else//node方法寻找链表中index位置的节点
            linkBefore(element, node(index));
    }
    
    
Node<E> node(int index) {
//判断index下标是在整个链表前半段还是后半段。 
//如果是前半段,x指向链表的头指针first,从头部遍历循环到index的位置,返回index的节点。 
//如果是后半段,x指向链表的尾指针last, 从尾部遍历循环到index的位置,返回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;
        }
    }
    
    
void linkBefore(E e, Node<E> succ) {
        // assert succ != null;
        final Node<E> pred = succ.prev;
        final Node<E> newNode = new Node<>(pred, e, succ);
        succ.prev = newNode;
        if (pred == null)
            first = newNode;
        else
            pred.next = newNode;
        size++;
        modCount++;
    }
3.4 remove 删除某个下标的数据【1.node()方法找到该下标的Node; 2、删除】
public E remove(int index) {
        checkElementIndex(index); //检查是否越界
        return unlink(node(index)); //1.node()方法找到该下标的Node; 2、删除
}
E unlink(Node<E> x) {
        // assert x != null;
        final E element = x.item;
        final Node<E> next = x.next;
        final Node<E> prev = x.prev;
        if (prev == null) {
            first = next;
        } else {
            prev.next = next;
            x.prev = null;
        }
        if (next == null) {
            last = prev;
        } else {
            next.prev = prev;
            x.next = null;
        }
        x.item = null;
        size--;
        modCount++;
        return element;
    }
3.5 get 查找数据
public E get(int index) {
        checkElementIndex(index);
        return node(index).item;
    }
四、总结
1) 从使用方法的角度分析。ArrayList属于非线程安全,而Vector则属于线程安全。如果是开发中没有线程同步的需求,推荐优先使用ArrayList。因此其内部没有synchronized,执行效率会比Vector快很多。
  • Vector可以指定增长因子,如果该增长因子指定了,那么扩容的时候会每次新的数组大小会在原数组的大小基础上加上增长因子;如果不指定增长因子,那么就给原数组大小*2
2) 从数据结构的角度分析。ArrayList是一个数组结构(Vector同理),数组在内存中是一片连续存在的片段,在查找元素的时候数组能够很方便的通过内存计算直接找到对应的元素内存。但是它也有很大的缺点。我们假设需要往数组插入或删除数据的位置为i,数组元素长度为n,则需要搬运数据n-i次才能完成插入、删除操作,导致其效率不如LinkedList。
3) LinkedList的底层是一个双向链表结构,在进行查找操作的时候需要花费非常非常多的时间来遍历整个链表(哪怕只遍历一半),这就是LinkedList在查找效率不如ArrayList快的原因。但是由于其链表结构的特殊性,在插入、删除数据的时候,只需要修改链表节点的前后指针就可以完成操作,其的效率远远高于ArrayList。

在这里插入图片描述

五、Stack
  • Stack 的源码,可以看到 Stack 继承自 Vector,在Java中Stack类表示“后进先出”(LIFO)的对象堆栈

package java.util;
 
public class Stack<E> extends Vector<E> {
    
    // 栈默认是空的
    public Stack() {
    }
 
    // 入栈
    public E push(E item) {
        
        // 将元素存入栈顶。
        // addElement()的实现在Vector中
        addElement(item);
 
        return item;
    }
 
    // 出栈,移除元素
    public synchronized E pop() {
        E       obj;
        int     len = size();
 
        obj = peek();
        // 删除栈顶元素,removeElementAt()的实现在Vector中
        removeElementAt(len - 1);
 
        return obj;
    }
 
    // 出栈,不移除元素
    public synchronized E peek() {
        int     len = size();
 
        if (len == 0)
            throw new EmptyStackException();
        // 返回栈顶元素,elementAt()具体实现在Vector中
        return elementAt(len - 1);
    }
 
    // 判断是否为空
    public boolean empty() {
        return size() == 0;
    }
 
    // 查找
    public synchronized int search(Object o) {
        // 获取元素索引,elementAt()具体实现在Vector中
        int i = lastIndexOf(o);
 
        if (i >= 0) {
            return size() - i;
        }
        return -1;
    }
 
    /** use serialVersionUID from JDK 1.0.2 for interoperability */
    private static final long serialVersionUID = 1224463164541339165L;
}
  • Stack继承Vector,他对Vector进行了简单的扩展,允许将向量视为堆栈。这个五个操作如下:

    • 1、empty():测试堆栈是否为空。

    • 2、peek():查看堆栈顶部的对象,但不从堆栈中移除它。

    • 3、pop():移除堆栈顶部的对象,并作为此函数的值返回该对象。

    • 4、push(E item):把项压入堆栈顶部。

    • 5、search(Object o):返回对象在堆栈中的位置,以 1 为基数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值