java容器二:List接口实现类源码分析

一、ArrayList

1、存储结构

动态数组elementData

 transient Object[] elementData;  

除此之外还有一些数据

 1     //默认初始容量
 2     private static final int DEFAULT_CAPACITY = 10;
 3 
 4     //共享空数组
 5     private static final Object[] EMPTY_ELEMENTDATA = {};
 6 
 7     //默认初始空数组
 8     private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
 9 
10     //动态数组中对象数量
11     private int size;

 

EMPTY_ELEMENTDATA(下面简称EE)和DEFAULTCAPICITY_EMPTY+ELEMENTDATA(以下简称DEE)作用与区别

1、当用无参构造器构造一个ArrayList实例时,使用DEE

1     /**
2      * Constructs an empty list with an initial capacity of ten.
3      * 无参构造器:将DEE空数组传递给elementData
4      */
5     public ArrayList() {
6         this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
7     }

 

2、当使用传入初始容量但是大小为0的构造器构造一个空ArrayList实例时,使用EE

 1   /**
 2      * Constructs an empty list with the specified initial capacity.
 3      *有参构造器,判断传入的数组大小,若为有效数字且不为0则根据传递的值构造一个数组
 4      * 若传入的为0,则将EE空数组给elementData
 5      * 若传入的为无效数字,则抛出异常
 6      * @param  initialCapacity  the initial capacity of the list
 7      * @throws IllegalArgumentException if the specified initial capacity
 8      *         is negative
 9      */
10     public ArrayList(int initialCapacity) {
11         if (initialCapacity > 0) {
12             this.elementData = new Object[initialCapacity];
13         } else if (initialCapacity == 0) {
14             this.elementData = EMPTY_ELEMENTDATA;
15         } else {
16             throw new IllegalArgumentException("Illegal Capacity: "+
17                                                initialCapacity);
18         }
19     }

3、区别:作用不同
DEE空数组是无参构造器中调用,后来增加第一个元素时扩容为默认大小10
EE是在构造出空数组(传入有参参数0)时调用,它的作用在于使得所有ArrayList空实例都指向同一个对象也就是EE

2、get/set方法

 1     public E get(int index) {
 2         Objects.checkIndex(index, size);
 3         return elementData(index);
 4     }
 5 
 6     public E set(int index, E element) {
 7         Objects.checkIndex(index, size);
 8         E oldValue = elementData(index);
 9         elementData[index] = element;
10         return oldValue;
11     }

 

 

3、add方法

(1)在尾部添加一个元素add(E e)

 1     public boolean add(E e) {
 2         modCount++;
 3         add(e, elementData, size);
 4         return true;
 5     }
 6 
 7     private void add(E e, Object[] elementData, int s) {
 8         if (s == elementData.length)
 9             elementData = grow();
10         elementData[s] = e;
11         size = s + 1;
12     }

 

(2)在指定位置上添加一个元素add(int index,E e)

 1     public void add(int index, E element) {
 2         rangeCheckForAdd(index);
 3         modCount++;
 4         final int s;
 5         Object[] elementData;
 6         if ((s = size) == (elementData = this.elementData).length)
 7             elementData = grow();
 8         System.arraycopy(elementData, index,
 9                          elementData, index + 1,
10                          s - index);
11         elementData[index] = element;
12         size = s + 1;
13     }

 

(3)grow方法:在两个add方法中都会在插入元素之前检查数组容量是否足够,若不足则需要扩容

 1     private Object[] grow(int minCapacity) {
 2         return elementData = Arrays.copyOf(elementData,
 3                                            newCapacity(minCapacity));
 4     }
 5 
 6     private Object[] grow() {
 7         return grow(size + 1);
 8     }
 9 
10     private int newCapacity(int minCapacity) {
11         // overflow-conscious code
12         int oldCapacity = elementData.length;
13         //重点步骤
14         int newCapacity = oldCapacity + (oldCapacity >> 1);
15         if (newCapacity - minCapacity <= 0) {
16             if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
17                 return Math.max(DEFAULT_CAPACITY, minCapacity);
18             if (minCapacity < 0) // overflow
19                 throw new OutOfMemoryError();
20             return minCapacity;
21         }
22         return (newCapacity - MAX_ARRAY_SIZE <= 0)
23             ? newCapacity
24             : hugeCapacity(minCapacity);
25     }    

 

4、remove方法

(1)移除某一位置上的对象remove(int index)

 1     public E remove(int index) {
 2         Objects.checkIndex(index, size);
 3 
 4         modCount++;
 5         E oldValue = elementData(index);
 6 
 7         int numMoved = size - index - 1;
 8         if (numMoved > 0)
 9             System.arraycopy(elementData, index+1, elementData, index,
10                              numMoved);
11         elementData[--size] = null; // clear to let GC do its work
12 
13         return oldValue;
14     }

 

 

(2)移除某一对象remove(Object o)

 1   //遍历找到o的位置,然后移除,这里的fastRemove方法相对于上面的remove方法少了index验证的步骤,因为能够找到要被删除元素的index则index一定合理    
 2   public boolean remove(Object o) {
 3         if (o == null) {
 4             for (int index = 0; index < size; index++)
 5                 if (elementData[index] == null) {
 6                     fastRemove(index);
 7                     return true;
 8                 }
 9         } else {
10             for (int index = 0; index < size; index++)
11                 if (o.equals(elementData[index])) {
12                     fastRemove(index);
13                     return true;
14                 }
15         }
16         return false;
17     }

 

 

5、序列化

ArrayList接口是实现了Serializable接口的,说明是可以序列化的

但是我们在前面已经看到保存对象集合的elementData用transient修饰,是不被序列化的

 

这是因为elementData为了后续扩容会保存一定的容量,为了避免序列化这部分内容,序列化就采取上诉的方式用readObject和writeObject方法来序列化

 

 1     private void readObject(java.io.ObjectInputStream s)
 2         throws java.io.IOException, ClassNotFoundException {
 3 
 4         // Read in size, and any hidden stuff
 5         s.defaultReadObject();
 6 
 7         // Read in capacity
 8         s.readInt(); // ignored
 9 
10         if (size > 0) {
11             // like clone(), allocate array based upon size not capacity
12             SharedSecrets.getJavaObjectInputStreamAccess().checkArray(s, Object[].class, size);
13             Object[] elements = new Object[size];
14 
15             // Read in all elements in the proper order.
16             for (int i = 0; i < size; i++) {
17                 elements[i] = s.readObject();
18             }
19 
20             elementData = elements;
21         } else if (size == 0) {
22             elementData = EMPTY_ELEMENTDATA;
23         } else {
24             throw new java.io.InvalidObjectException("Invalid size: " + size);
25         }
26     }
 1     private void writeObject(java.io.ObjectOutputStream s)
 2         throws java.io.IOException {
 3         // Write out element count, and any hidden stuff
 4         int expectedModCount = modCount;
 5         s.defaultWriteObject();
 6 
 7         // Write out size as capacity for behavioural compatibility with clone()
 8         s.writeInt(size);
 9 
10         // Write out all elements in the proper order.
11         for (int i=0; i<size; i++) {
12             s.writeObject(elementData[i]);
13         }
14 
15         if (modCount != expectedModCount) {
16             throw new ConcurrentModificationException();
17         }
18     }

 

 

二、Vector

Vector和ArrayList操作基本相同,只不过对方法加了synchronized做同步处理,以add方法为例

 1     //add1
 2     public synchronized boolean add(E e) {
 3         modCount++;
 4         add(e, elementData, elementCount);
 5         return true;
 6     }
 7 
 8     //add2
 9     public void add(int index, E element) {
10         insertElementAt(element, index);
11     }
12 
13     public synchronized void insertElementAt(E obj, int index) {
14         if (index > elementCount) {
15             throw new ArrayIndexOutOfBoundsException(index
16                                                      + " > " + elementCount);
17         }
18         modCount++;
19         final int s = elementCount;
20         Object[] elementData = this.elementData;
21         if (s == elementData.length)
22             elementData = grow();
23         System.arraycopy(elementData, index,
24                          elementData, index + 1,
25                          s - index);
26         elementData[index] = obj;
27         elementCount = s + 1;
28     }

 

 1     //扩容重点步骤:确定扩容的大小    
 2     private int newCapacity(int minCapacity) {
 3         // overflow-conscious code
 4         int oldCapacity = elementData.length;
 5         int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
 6                                          capacityIncrement : oldCapacity);
 7         if (newCapacity - minCapacity <= 0) {
 8             if (minCapacity < 0) // overflow
 9                 throw new OutOfMemoryError();
10             return minCapacity;
11         }
12         return (newCapacity - MAX_ARRAY_SIZE <= 0)
13             ? newCapacity
14             : hugeCapacity(minCapacity);
15     }

 

三、LinkedList

1、存储结构

双向链表存储,有一个头结点first,一个尾节点last,每一个节点有两个指针,一个指向它的前驱节点,一个指向它的后继节点

 1     /**
 2      * Pointer to first node.
 3      */
 4     transient Node<E> first;
 5 
 6     /**
 7      * Pointer to last node.
 8      */
 9     transient Node<E> last;
10 
11     private static class Node<E> {
12         E item;
13         Node<E> next;
14         Node<E> prev;
15 
16         Node(Node<E> prev, E element, Node<E> next) {
17             this.item = element;
18             this.next = next;
19             this.prev = prev;
20         }
21     }

 

2、get/set方法

 1     public E get(int index) {
 2         checkElementIndex(index);
 3         return node(index).item;
 4     }
 5 
 6     /**
 7      * Returns the (non-null) Node at the specified element index.
 8      * 用二分法简单判断一下对象在链表前半部分还是后半部分
 9      * 决定是从first指针向后遍历还是last指针向前遍历
10      */
11     Node<E> node(int index) {
12         // assert isElementIndex(index);
13 
14         if (index < (size >> 1)) {
15             Node<E> x = first;
16             for (int i = 0; i < index; i++)
17                 x = x.next;
18             return x;
19         } else {
20             Node<E> x = last;
21             for (int i = size - 1; i > index; i--)
22                 x = x.prev;
23             return x;
24         }
25     }

 

1     public E set(int index, E element) {
2         checkElementIndex(index);
3         Node<E> x = node(index);
4         E oldVal = x.item;
5         x.item = element;
6         return oldVal;
7     }

 

3、add方法

 1 /**
 2      * Links e as last element.
 3      */
 4     void linkLast(E e) {
 5         final Node<E> l = last;
 6         final Node<E> newNode = new Node<>(l, e, null);
 7         last = newNode;
 8         if (l == null)
 9             first = newNode;
10         else
11             l.next = newNode;
12         size++;
13         modCount++;
14     }
15 
16     /**
17      * Inserts element e before non-null Node succ.
18      */
19     void linkBefore(E e, Node<E> succ) {
20         // assert succ != null;
21         final Node<E> pred = succ.prev;
22         final Node<E> newNode = new Node<>(pred, e, succ);
23         succ.prev = newNode;
24         if (pred == null)
25             first = newNode;
26         else
27             pred.next = newNode;
28         size++;
29         modCount++;
30     }

 

4、remove方法

 1     /**
 2      * Unlinks non-null last node l.
 3      */
 4     private E unlinkLast(Node<E> l) {
 5         // assert l == last && l != null;
 6         final E element = l.item;
 7         final Node<E> prev = l.prev;
 8         l.item = null;
 9         l.prev = null; // help GC
10         last = prev;
11         if (prev == null)
12             first = null;
13         else
14             prev.next = null;
15         size--;
16         modCount++;
17         return element;
18     }

 

 

四、线程安全解决方案

ArrayList和LinkedList都是线程不安全的,因此若想要实现线程安全有一下替换的实现方案

方案一:用Collections.synchronizedList()方法构造出一个线程安全的list(ArrayList和LinkedList都可以使用)

 List<String> synList1 = Collections.synchronizedList(new ArrayList<>());

List<String> synList2 = Collections.synchronizedList(new LinkedList<>()); 

 

方案二:用java..concurrent包下面的CopyOnWriteArrayList替代ArrayList

CopyOnWriteArrayList:重点是读写分离

1、原理:

写操作的时候会在一个复制的数组上进行,并且要加锁

读操作在原数组上进行

写操作完毕之后,读操作的数组指针指向新的复制数组

2、关键代码

写操作

 1     public boolean add(E e) {
 2         synchronized (lock) {
 3             Object[] elements = getArray();
 4             int len = elements.length;
 5             Object[] newElements = Arrays.copyOf(elements, len + 1);
 6             newElements[len] = e;
 7             setArray(newElements);
 8             return true;
 9         }
10     }
11 
12     public E remove(int index) {
13         synchronized (lock) {
14             Object[] elements = getArray();
15             int len = elements.length;
16             E oldValue = elementAt(elements, index);
17             int numMoved = len - index - 1;
18             if (numMoved == 0)
19                 setArray(Arrays.copyOf(elements, len - 1));
20             else {
21                 Object[] newElements = new Object[len - 1];
22                 System.arraycopy(elements, 0, newElements, 0, index);
23                 System.arraycopy(elements, index + 1, newElements, index,
24                                  numMoved);
25                 setArray(newElements);
26             }
27             return oldValue;
28         }
29     }

 

读操作

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

 

3、应用场景

因为CopyOnWriteArrayList允许读写同时进行,大大提高了读操作的性能,因此适用于读多写少的应用场景

4、缺点

(1)内存占用:有一个复制的写数组,因此内存占用增加了一倍

(2)数据不一致:读操作不能实时性得到写操作数据

因此不适合与内存敏感、实时性要求高的应用场景

 

方案三:用Vector替代ArrayList

 

五、ArrayList与Vector、LinkedList比较

1、ArrayList与Vector比较

(1)ArrayList是线程不安全的,因此读写效率高,Vector是线程安全的

(2)扩容时ArrayList扩大为原来的1.5倍,Vector扩大为原来的2倍

 

2、ArrayList与LinkedList比较

(1)ArrayList是用动态数组实现,LinkedList是用双向链表实现

(2)ArrayList支持随机访问,LinkedList不支持

(3)LinkedList插入删除更快

 

posted @ 2019-06-19 21:19  huanglf714  阅读(257)  评论(0编辑  收藏  举报