zoukankan      html  css  js  c++  java
  • 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插入删除更快

  • 相关阅读:
    ok~加油!
    解析window.open链接的参数
    Arrya数组添加过滤条件
    Oracle 查询今天、昨日、本周、本月和本季度的所有记录
    Sql Server日期查询-SQL查询今天、昨天、7天内、30天
    Lua 中 pairs 和 ipairs 的区别
    关于SignalR连接数量问题的记录
    IceStorm示例运行步骤
    从 OPC 到 OPC UA
    SQL Server 2008 R2 Express Profiler
  • 原文地址:https://www.cnblogs.com/huanglf714/p/11050509.html
Copyright © 2011-2022 走看看