对list的基础巩固

List接口和实现类

list接口继承自Collection接口,主要处理有序集合,所谓有序,就是存放在此数据结构的数据会排序。

arrayList

public class ArrayList extends AbstractList
        implements List, RandomAccess, Cloneable, java.io.Serializable

arraylist 继承自AbstractList,底层数组实现。

    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    transient Object[] elementData; // non-private to simplify nested class access

空构造器,默认初始大小为10

 /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

/**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; //这里虽然复赋值为null数组,在第一次往里面添加元素时会扩大至容量10
    }

有参构造

// initialCapacity表示list的初始容量
public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

添加和移除操作:add() ,remove()

 public void add(int index, E e) {
            rangeCheckForAdd(index);
            checkForComodification();
            parent.add(parentOffset + index, e);
            this.modCount = parent.modCount;
            this.size++;
        }

        public E remove(int index) {
            rangeCheck(index);
            checkForComodification();
            E result = parent.remove(parentOffset + index);
            this.modCount = parent.modCount;
            this.size--;
            return result;
        }

可以看出arraylist是非线程安全的,多线程操作需要程序员自己处理并发的问题。

linkedList

public class LinkedList
    extends AbstractSequentialList
    implements List, Deque, Cloneable, java.io.Serializable

可以看出linkedlist未继承AbstractList,而是自己实现list。继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。实现 List 接口,能对它进行队列操作。实现 Deque 接口,即能将LinkedList当作双端队列使用。实现了Cloneable接口,即覆盖了函数clone(),能克隆。实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。

/**
     *空构造器
     */
    public LinkedList() {
    }

添加元素,删除元素

public boolean add(E e) {
        linkLast(e);
        return true;
    }

public boolean remove(Object o) {
        if (o == null) {
            for (Node x = first; x != null; x = x.next) {
                if (x.item == null) {
                    unlink(x);
                    return true;
                }
            }
        } else {
            for (Node x = first; x != null; x = x.next) {
                if (o.equals(x.item)) {
                    unlink(x);
                    return true;
                }
            }
        }
        return false;
    }

vector

public class Vector
    extends AbstractList
    implements List, RandomAccess, Cloneable, java.io.Serializable

可以看出,vector和arraylist有点类似,和arraylist最大的区别就是vector是线程安全的。
构造函数

/**
  * initialCapacity是初始容量,capacityIncrement表示每次扩容时需  要扩大的量,当此参数小于等于0时,2倍扩容
  */
public Vector(int initialCapacity, int capacityIncrement) {
        super();
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        this.elementData = new Object[initialCapacity];
        this.capacityIncrement = capacityIncrement;
    }

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

    /**
     *
     */
    public Vector() {
        this(10);
    }

voctor的添加元素和删除元素方法

public synchronized void addElement(E obj) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = obj;
    }

    public synchronized boolean removeElement(Object obj) {
        modCount++;
        int i = indexOf(obj);
        if (i >= 0) {
            removeElementAt(i);
            return true;
        }
        return false;
    }

从这个方法可以看出,voctor实现同步的办法是加了synchronized。
一般情况下,arraylist遍历元素时使用foreach,因为arraylist底层是数组实现,查询比较快。而linkedlist底层是双向链表,对插入和删除性能比较好,在遍历时用foreach效率就会很低(此处更正,不只foreach效率低,并且使用foreach遍历list无论是否多线程操作都会发生fail-fast错误。),一般就会使用到Iterator(称为迭代器) 来提高效率,因为创建它的代价很小。但在多线程时对它使用不当会发生一个叫fail-fast(称为快速失败)的问题,也就是会报java.util.ConcurrentModificationException,原因在于iterator是对linkedlist中对象的引用的一个拷贝,比如:

Object o = new Object();
//o就是对new Object()对象的引用,相当于一个指针指向在堆中的真实对象,而对对象引用的拷贝是指用一个新的引用指向o这个引用。
Object ob = o;

在iterator容器中全是对真实对象引用的拷贝。层层寻找,我们找到了这个方法。

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

就是这个方法抛出了这个异常,modCount这个参数我们发现在abstractList中

//modCount是抽象类AbstractList中的变量,默认为0,用于记录集合操作过程中作的修改次数
protected transient int modCount = 0;

expectedModCount 存在abstractList中,初始等于modCount;

//expectedModCount表示迭代器对集合进行修改的次数。
int expectedModCount = modCount;

这里不难分析出,一个是表示线程对集合修改次数,一个地iterator对集合修改次数。当两个值不相等,抛出异常。假设场景:当一个线程对iterator遍历时(假设它正在遍历,不存在添加修改),expectedModCount就不会改变。而另外一个线程同时在删除或添加元素,modCount就是改变。这就导致这两个值不一致,报错。

public E next() {
            checkForComodification();   //遍历前检查
            try {
                int i = cursor;
                E next = get(i);
                lastRet = i;
                cursor = i + 1;
                return next;
            } catch (IndexOutOfBoundsException e) {
                checkForComodification();
                throw new NoSuchElementException();
            }
        }

和我们猜测的一样每次next(),都会检查的,解决办法就是保证容器的操作原子性,加锁,同步,当然,也可以使用java.util.concurrent包下的很多实现了线程安全的容器。

~~~~划水~

通过查api文档知道,arraylist,linkedlist是在java.util包下线程不安全的集合类,vorctor虽然是线程安全的,但它使用的是synchronized来实现线程安全,它的效率太低了。今天我们来说java.util.concurrent包下的线程安全的集合类。
CopyOnWriteArrayList,通过名字就知道,它的策略是在写的时候复制list,简单的讲,一个线程在对list写的时候,不是直接对list写,而是对list进行复制,对复制出来的list写,写完后在把引用指向这个复制的list。在写的期间不影响其他线程对list的访问。

看看它的读写方法

 //可以看到读方法是没有加锁的
@SuppressWarnings("unchecked")
    private E get(Object[] a, int index) {
        return (E) a[index];
    }

  
    public E get(int index) {
        return get(getArray(), index);
    }

//写方法手动加锁
public E set(int index, E element) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            E oldValue = get(elements, index);

            if (oldValue != element) {
                int len = elements.length;
                Object[] newElements = Arrays.copyOf(elements, len);
                newElements[index] = element;
                setArray(newElements);
            } else {
                // Not quite a no-op; ensures volatile write semantics
                setArray(elements);
            }
            return oldValue;
        } finally {
            lock.unlock();
        }
    }


    public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1);
            newElements[len] = e;
            setArray(newElements);
            return true;
        } finally {
            lock.unlock();
        }
    }

 
    public void add(int index, E element) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray();
            int len = elements.length;
            if (index > len || index < 0)
                throw new IndexOutOfBoundsException("Index: "+index+
                                                    ", Size: "+len);
            Object[] newElements;
            int numMoved = len - index;
            if (numMoved == 0)
                newElements = Arrays.copyOf(elements, len + 1);
            else {
                newElements = new Object[len + 1];
                System.arraycopy(elements, 0, newElements, 0, index);
                System.arraycopy(elements, index, newElements, index + 1,
                                 numMoved);
            }
            newElements[index] = element;
            setArray(newElements);
        } finally {
            lock.unlock();
        }
    }

写方法为什么加锁呢,是为了在一个线程对list复制时其他线程不能复制,写完后把引用指向新的list时其他线程才能对新的list进行拷贝进行写操作。这样保证了线程安全,注意这样的list,**线程不能及时获取到准确的数据。应用于读多写少的场景。比如黑名单。

那么问题来了,CopyOnWriteArrayList不能满足我们想的需求啊,我们希望及时获取到被修改的数据,但又不想用vector(因为它效率低,读方法和写方法都是synchronized)。那怎么办呢,那么接下来我们就该聊聊集合工具类Collections提供的synchronizedList方法了。它是个静态方法,不需要new。我们看看它的源码吧!

  public static  List synchronizedList(List list) {
        return (list instanceof RandomAccess ?
                new SynchronizedRandomAccessList<>(list) :
                new SynchronizedList<>(list));
    }

可以看出,它需要传入一个list对象,然后看它是否属于RandomAccess的子类(instanceof判断一个对象是否属于某个类),我们研究看看RandomAccess这个类是个啥。

public interface RandomAccess {
}

源代码就这么点,它就是接口,而且没有定义任何方法。那就百度查查是干啥的。
RandomAccess 就是一个标记接口,用于标明实现该接口的List支持快速随机访问,主要目的是使算法能够在随机和顺序访问的List中性能更加高效(在Collections二分查找时)。原来是提高效率的。
假如我们需要同步arraylist,可以查到,arraylist是实现了这个接口的。说明arraylist instanceof RandomAccess 返回为true,程序便会new SynchronizedRandomAccessList<>(list)。现在该研究研究SynchronizedRandomAccessList是个啥了。

       SynchronizedRandomAccessList(List list) {
            super(list);
        }

继续往里面走

        SynchronizedList(List list) {
            super(list);
            this.list = list;
        }

this.list是啥

 static class SynchronizedList extends SynchronizedCollection
        implements List {
        private static final long serialVersionUID = -7754090372962971524L;

        final List list;
     ... //后面代码略

原来是Collections类自己实现的的一个静态list容器,我们看他的添加和获取方法如何处理多线程并发。

        public E get(int index) {
            synchronized (mutex) {return list.get(index);}
        }
        public E set(int index, E element) {
            synchronized (mutex) {return list.set(index, element);}
        }
        public void add(int index, E element) {
            synchronized (mutex) {list.add(index, element);}
        }

又是synchronized,可这个mutex是啥玩意,对他加锁就可以实现解决多线程问题?

 static class SynchronizedCollection implements Collection, Serializable {
        private static final long serialVersionUID = 3053995032091335093L;

        final Collection c;  // Backing Collection
        final Object mutex;     // Object on which to synchronize
        ...//其他代码略

这有是啥呢?先看看它指向哪个对象?

        SynchronizedCollection(Collection c) {
            this.c = Objects.requireNonNull(c);
            mutex = this;
        }

默认指向自己呐!大家可注意到SynchronizedList是继承了SynchronizedCollection的,这两个类都是Collections类自己实现的list容器。原来搞了半天,是对自己的容器对象加锁(默认情况,也可以指定加锁对象!)这里我们应该差不多可以总结SynchronizedList和vector的区别了,SynchronizedList是对代码块实现同步。vector是对整个方法实现同步。哈哈,SynchronizedList效率确实会高那么点点的。还有一个很大不同是它俩扩容机制,vector扩容是2倍增长,arraylist每次扩容是前一次的50%.
还有一个不同是SynchronizedList这个静态方法也可以把linkedlist实现同步。哈哈,vector却不能啊!
具体哪个场景用哪个,就得自己斟酌了。
~~~~划水~

你可能感兴趣的:(对list的基础巩固)