Java 容器类(二)LinkedList、Verctor部分源码阅读

182 阅读2分钟

LinkedList、Verctor部分源码阅读

一、LinkedList

首先来看LinkedList的类声明:

public class LinkedList<E> extends AbstractSequentialList<E>
    implements List<E>, Deque<E>, Cloneable, java.io.Serializable {
	
	//······
}

LinkedList的继承树如下: Alt text

List本身是一个有序(插入顺序)的集合,可以包含重复的元素,提供了按索引访问的方式,继承自Collection。

List有两个重要的实现类,其一是ArrayList,另外一个就是LinkedList。

LinkedList是一个双链表,在添加、删除元素时,具有比ArrayList更好的性能。但是在get和set时性能弱于ArrayList,这显然是数组和链表的性能特性的区别,所以,我们在LinkedList的类定义中可以看到:

  transient int size = 0;
  transient Node<E> first;
  transient Node<E> last;

transient 关键字:“其实这个关键字的作用很好理解,就是简单的一句话:将不需要序列化的属性前添加关键字transient,序列化对象的时候,这个属性就不会被序列化。”

换言之,当我们需要序列化存储LinkedList时,不需要存储LinkedList的头指针、尾指针、size性。

另外,LinkedList实现了Queue接口,因此,做如下声明即是一个队列(仅支持尾插头出):

    //队列
    Queue<Integer> q = new LinkedList<>();
    q.add(1);
    q.remove();
  

而这样声明,这是一个双端队列。

	//双端队列
    LinkedList<Integer> l = new LinkedList<>();
    l.addFirst(1);
    l.addLast(2);
    l.add(3);//默认的是和Queue一样进行尾插


    l.removeFirst();
    l.removeLast();
    

LinkedList 是线程不安全的。

二、Vector

事先回顾一下ArrayList,ArrayList是一个线程不安全、自动扩容的动态数组,它构造函数的三种参数形式分别是:具体的initialCapicity,不填写(默认为10开始按1.5倍进行扩容),填0(默认为0,从0->1->2->3->4->6进行1.5倍进行扩容),扩容方式是开辟新的更大的数组空间,然后将所有数据移动到新的数组空间。

而Vector则也是基于数组形式实现的,vector本意是向量,vector的类声明如下:

public class Vector<E>
    extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
	    protected Object[] elementData;
		protected int elementCount;
		protected int capacityIncrement;
	}
}

Vector同样有多个构造函数,首先是默认构造函数,它默认创建了一个大小为10的Vector。

public Vector() {
    this(10);
}

其次是:

public Vector(int initialCapacity) {
    this(initialCapacity, 0);
}

最后是:

public Vector(int initialCapacity, int capacityIncrement) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    this.elementData = new Object[initialCapacity];
    this.capacityIncrement = capacityIncrement;
}

这三个构造函数从上到下都会逐次调用。最终初始化的参数包括:elementData、capacityIncrement,后者是单词空间扩容的步长。

Vector的add() 方法簇:

public synchronized boolean add(E e) {
    modCount++;
    add(e, elementData, elementCount);
    return true;
}

private void add(E e, Object[] elementData, int s) {
    if (s == elementData.length)//末尾插入
        elementData = grow();
    elementData[s] = e;
    elementCount = s + 1;
}

private Object[] grow() {
    return grow(elementCount + 1);
}

private Object[] grow(int minCapacity) {
    int oldCapacity = elementData.length;
    int newCapacity = ArraysSupport.newLength(oldCapacity,
            minCapacity - oldCapacity, /* minimum growth */
            capacityIncrement > 0 ? capacityIncrement : oldCapacity
                                       /* preferred growth */);
    return elementData = Arrays.copyOf(elementData, newCapacity);
}

其中newLength的三个参数分别为oldCapacity、minGrowth、prefGrowth,如果capacityIncrement > 0 ,那就取capacityIncrement ,否则数据空间 * 2,然后复制数组,capacityIncrement是我们构造函数中填入的扩容步长,如果不填默认为0。

Iterator和Enumeration的区别

Enumeration接口中定义了一些方法,通过这些方法可以枚举(一次获得一个)对象集合中的元素。这种传统接口已被迭代器取代。

public Enumeration<E> elements() {
    return new Enumeration<E>() {
        int count = 0;

        public boolean hasMoreElements() {
            return count < elementCount;
        }

        public E nextElement() {
            synchronized (Vector.this) {
                if (count < elementCount) {
                    return elementData(count++);
                }
            }
            throw new NoSuchElementException("Vector Enumeration");
        }
    };
}

该方法用于获得一个Enumeration实例,我们可以重写内部的两个方法hasMoreElements()、nextElement()。实际上和Iterator的hasNext和next相似。

那么,Enumeration有什么作用呢?我们知道,在这类元素集合中,我们不能在迭代时对数据进行插入修改操作。例如:

    Iterator iterator = v.iterator();//获取Iterator
    while (iterator.hasNext()){
        Integer value = (Integer) iterator.next();
        System.out.println(value);
        if(value == 45){
            v.add(10000);//对vector进行差入
        }
    }

产生报错:Exception in thread "main" java.util.ConcurrentModificationException

对调用栈进行溯源,可以发现,在Vector中实现的Iterator的next方法如下:

    public E next() {
        synchronized (Vector.this) {
            checkForComodification();//报错
            int i = cursor;
            if (i >= elementCount)
                throw new NoSuchElementException();
            cursor = i + 1;
            return elementData(lastRet = i);
        }
    }

 final void checkForComodification() {
        if (modCount != expectedModCount)
            throw new ConcurrentModificationException();
    }

而expectedModCount在iterator被创建时,赋值为:

int expectedModCount = modCount;

当我们对数据进行增删,ModCount发生了改变,抛出异常。!

而在LinkedList的Iterator的next()函数定义处,我们可以发现,ArrayList中同样进行了checkForComodification(),但是在操作中没有对数据进行加锁。所以我们对ArrayList迭代时,进行增删也会报出错误。

    public boolean hasNext() {
        return nextIndex < size;
    }

    public E next() {
        checkForComodification();
        if (!hasNext())
            throw new NoSuchElementException();

        lastReturned = next;
        next = next.next;
        nextIndex++;
        return lastReturned.item;
    }

但是采用for循环遍历一个LinkedList并不会报错,但是采用list.forEach(new Consumer)去遍历同样会报错。

而采用Enumeration进行遍历,则不会产生报错。

  Vector<Integer> v = ne w Vector<>();
        for (int i = 0; i < 100; i++) {
            v.add(i);
        }
    
   Enumeration iterator = v.elements();//获取Iterator
   while (iterator.hasMoreElements()){
       Integer value = (Integer) iterator.nextElement();
       System.out.println(value);
       if(value == 45){
           v.add(10000);
       }
   }