精华内容
参与话题
问答
  • ArrayList底层实现

    2020-05-31 22:14:43
    ArrayList底层实现 Modifier and Type Method and Description boolean add(E e) 将指定的元素追加到此列表的末尾 void add(int index, E element) 在此列表中的指定位置插入指定的元素。 boolean ...

    ArrayList底层实现

    Modifier and Type Method and Description
    boolean add(E e) 将指定的元素追加到此列表的末尾
    void add(int index, E element) 在此列表中的指定位置插入指定的元素。
    boolean addAll(Collection<? extends E> c) 按指定集合的Iterator返回的顺序将指定集合中的所有元素追加到此列表的末尾。
    boolean addAll(int index, Collection<? extends E> c) 将指定集合中的所有元素插入到此列表中,从指定的位置开始。
    void clear() 从列表中删除所有元素。
    Object clone() 返回此 ArrayList实例的浅拷贝。
    boolean contains(Object o) 如果此列表包含指定的元素,则返回 true 。
    void ensureCapacity(int minCapacity) 如果需要,增加此 ArrayList实例的容量,以确保它可以至少保存最小容量参数指定的元素数。
    void forEach(Consumer<? super E> action) 对 Iterable的每个元素执行给定的操作,直到所有元素都被处理或动作引发异常。
    E get(int index) 返回此列表中指定位置的元素。
    int indexOf(Object o) 返回此列表中指定元素的第一次出现的索引,如果此列表不包含元素,则返回-1。
    boolean isEmpty() 如果此列表不包含元素,则返回 true 。
    Iterator iterator() 以正确的顺序返回该列表中的元素的迭代器。
    int lastIndexOf(Object o) 返回此列表中指定元素的最后一次出现的索引,如果此列表不包含元素,则返回-1。
    ListIterator listIterator() 返回列表中的列表迭代器(按适当的顺序)。
    ListIterator listIterator(int index) 从列表中的指定位置开始,返回列表中的元素(按正确顺序)的列表迭代器。
    E remove(int index) 删除该列表中指定位置的元素。
    boolean remove(Object o) 从列表中删除指定元素的第一个出现(如果存在)。
    boolean removeAll(Collection<?> c) 从此列表中删除指定集合中包含的所有元素。
    boolean removeIf(Predicate<? super E> filter) 删除满足给定谓词的此集合的所有元素。
    protected void removeRange(int fromIndex, int toIndex) 从这个列表中删除所有索引在 fromIndex (含)和 toIndex之间的元素。
    void replaceAll(UnaryOperator operator) 将该列表的每个元素替换为将该运算符应用于该元素的结果。
    boolean retainAll(Collection<?> c) 仅保留此列表中包含在指定集合中的元素。
    E set(int index, E element) 用指定的元素替换此列表中指定位置的元素。
    int size() 返回此列表中的元素数。
    void sort(Comparator<? super E> c) 使用提供的 Comparator对此列表进行排序以比较元素。
    Spliterator spliterator() 在此列表中的元素上创建late-binding和故障快速 Spliterator 。
    List subList(int fromIndex, int toIndex) 返回此列表中指定的 fromIndex (包括)和 toIndex之间的独占视图。
    Object[] toArray() 以正确的顺序(从第一个到最后一个元素)返回一个包含此列表中所有元素的数组。
    T[] toArray(T[] a) 以正确的顺序返回一个包含此列表中所有元素的数组(从第一个到最后一个元素); 返回的数组的运行时类型是指定数组的运行时类型。
    void trimToSize() 修改这个 ArrayList实例的容量是列表的当前大小。
    展开全文
  • arraylist底层实现

    2018-05-18 10:30:25
    ArrayList是List接口的可变数组的实现实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。 每个ArrayList实例都有一个容量...

    ArrayList是List接口的可变数组的实现。实现了所有可选列表操作,并允许包括 null 在内的所有元素。除了实现 List 接口外,此类还提供一些方法来操作内部用来存储列表的数组的大小。
        每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组的大小。它总是至少等于列表的大小。随着向ArrayList中不断添加元素,其容量也自动增长。自动增长会带来数据向新数组的重新拷贝,因此,如果可预知数据量的多少,可在构造ArrayList时指定其容量。在添加大量元素前,应用程序也可以使用ensureCapacity操作来增加ArrayList实例的容量,这可以减少递增式再分配的数量。
        注意,此实现不是同步的。如果多个线程同时访问一个ArrayList实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步。
    对于ArrayList而言,它实现List接口、底层使用数组保存所有元素。其操作基本上是对数组的操作。
    ArrayList提供了三种方式的构造器,可以构造一个默认初始容量为10的空列表、构造一个指定初始容量的空列表以及构造一个包含指定collection的元素的列表,这些元素按照该collection的迭代器返回它们的顺序排列的。
    ArrayList提供了set(int index, E element)、add(E e)、add(int index, E element)、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c)这些添加元素的方法
    ArrayList提供了根据下标或者指定对象两种方式的删除功能。 注意:从数组中移除元素的操作,也会导致被移除的元素以后的所有元素的向左移动一个位置。
    从上述代码中可以看出,数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量的增长大约是其原容量的1.5倍。这种操作的代价是很高的,因此在实际使用时,我们应该尽量避免数组容量的扩张。当我们可预知要保存的元素的多少时,要在构造ArrayList实例时,就指定其容量,以避免数组扩容的发生。或者根据实际需求,通过调用ensureCapacity方法来手动增加ArrayList实例的容量。
    ArrayList还给我们提供了将底层数组的容量调整为当前列表保存的实际元素的大小的功能。它可以通过trimToSize方法来实现。
    ArrayList也采用了快速失败的机制,通过记录modCount参数来实现。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。
    Copy-On-Write简称COW,是一种用于程序设计中的优化策略。其基本思路是,从一开始大家都在共享同一个内容,当某个人想要修改这个内容的时候,才会真正把内容Copy出去形成一个新的内容然后再改,这是一种延时懒惰策略。从JDK1.5开始Java并发包里提供了两个使用CopyOnWrite机制实现的并发容器,它们是CopyOnWriteArrayList和CopyOnWriteArraySet。CopyOnWrite容器非常有用,可以在非常多的并发场景中使用到。

     

    展开全文
  • ArrayList底层实现原理

    2020-07-05 12:14:28
    ArrayList底层实现原理 ArrayList是基于数组实现的,是一个动态数组,其容量可以自动增长,初始容量是10。 ArrayList是线程不安全的,可以单线程情况下使用,如果要在多线程情况下使用,可以用Collections....

    ArrayList底层实现原理

    ArrayList是基于数组实现的,是一个动态数组,其容量可以自动增长,初始容量是10。

    ArrayList是线程不安全的,可以单线程情况下使用,如果要在多线程情况下使用,可以用Collections.synchronizedList(List l)函数返回一个线程安全的ArrayList类,也可以使用concurrent并发包下的CopyOnWriteArrayList类。

    ArrayList实现了Serializable接口,因此它支持序列化,能够通过序列化传输,实现了RandomAccess接口,支持快速随机访问,实际上就是通过下标序号进行快速访问,实现了Cloneable接口,能被克隆。

    每个ArrayList实例都有一个容量,该容量是指用来存储列表元素的数组大小。它总是至少等于列表的大小。随着ArrayList中不断增添元素,其容量也会自动增长,自动增长会带来数据向新数组的重新拷贝,因此,如果可以预见数据量的多少,就可以在构造ArrayList时指定其容量。在添加大量元素前,应用程序也可以使用ensureCapacity操作来增加ArrayList实例的容量,这可以减少递增式再分配的数量。

    源码分析:

    私有属性

    	/** 
          * The array buffer into which the elements of the ArrayList are stored. 
          * The capacity of the ArrayList is the length of this array buffer. 
          */  
         private transient Object[] elementData;  
       
         /** 
          * The size of the ArrayList (the number of elements it contains). 
          * 
          * @serial 
          */  
         private int size;
    

    elementData存储ArrayList中的元素,size表示包含元素的数量

    Java的serialization提供了一种持久化对象实例的机制。当持久化对象时,可能有一个特殊的对象数据成员,我们不想用serialization机制来保存它。为了在一个特定对象的一个域上关闭serialization,可以在这个域前加上关键字transient。

    有点抽象,看个例子应该能明白

    public class UserInfo implements Serializable {  
         private static final long serialVersionUID = 996890129747019948L;  
         private String name;  
         private transient String psw;  
       
         public UserInfo(String name, String psw) {  
             this.name = name;  
             this.psw = psw;  
         }  
       
         public String toString() {  
             return "name=" + name + ", psw=" + psw;  
         }  
     }  
       
     public class TestTransient {  
         public static void main(String[] args) {  
             UserInfo userInfo = new UserInfo("张三", "123456");  
             System.out.println(userInfo);  
             try {  
                 // 序列化,被设置为transient的属性没有被序列化  
                 ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream(  
                         "UserInfo.out"));  
                 o.writeObject(userInfo);  
                 o.close();  
             } catch (Exception e) {  
                 // TODO: handle exception  
                 e.printStackTrace();  
             }  
             try {  
                 // 重新读取内容  
                 ObjectInputStream in = new ObjectInputStream(new FileInputStream(  
                         "UserInfo.out"));  
                 UserInfo readUserInfo = (UserInfo) in.readObject();  
                 //读取后psw的内容为null  
                 System.out.println(readUserInfo.toString());  
             } catch (Exception e) {  
                 // TODO: handle exception  
                 e.printStackTrace();  
             }  
         }  
     }
    

    构造方法

    ArrayList提供了三种方式的构造器,可以构造一个默认初始容量为10的空列表、构造一个指定初始容量的空列表以及构造一个包含指定collection的元素的列表,这些元素按照该collection的迭代器返回它们的顺序排列的。

    	// ArrayList带容量大小的构造函数。    
        public ArrayList(int initialCapacity) {    
            super();    
            if (initialCapacity < 0)    
                throw new IllegalArgumentException("Illegal Capacity: "+    
                                                   initialCapacity);    
            // 新建一个数组    
            this.elementData = new Object[initialCapacity];    
        }    
       
        // ArrayList无参构造函数。默认容量是10。    
        public ArrayList() {    
            this(10);    
        }    
       
        // 创建一个包含collection的ArrayList    
        public ArrayList(Collection<? extends E> c) {    
            elementData = c.toArray();    
            size = elementData.length;    
            if (elementData.getClass() != Object[].class)    
                elementData = Arrays.copyOf(elementData, size, Object[].class);    
        }
    

    元素存储

    ArrayList提供了set(int index, E element)、add(E e)、add(int index, E element)、addAll(Collection<? extends E> c)、addAll(int index, Collection<? extends E> c)这些添加元素的方法。

    20 // 用指定的元素替代此列表中指定位置上的元素,并返回以前位于该位置上的元素。  
    21 public E set(int index, E element) {  
    22    RangeCheck(index);  
    23  
    24    E oldValue = (E) elementData[index];  
    25    elementData[index] = element;  
    26    return oldValue;  
    27 }    
    28 // 将指定的元素添加到此列表的尾部。  
    29 public boolean add(E e) {  
    30    ensureCapacity(size + 1);   
    31    elementData[size++] = e;  
    32    return true;  
    33 }    
    34 // 将指定的元素插入此列表中的指定位置。  
    35 // 如果当前位置有元素,则向右移动当前位于该位置的元素以及所有后续元素(将其索引加1)。  
    36 public void add(int index, E element) {  
    37    if (index > size || index < 0)  
    38        throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);  
    39    // 如果数组长度不足,将进行扩容。  
    40    ensureCapacity(size+1);  // Increments modCount!!  
    41    // 将 elementData中从Index位置开始、长度为size-index的元素,  
    42    // 拷贝到从下标为index+1位置开始的新的elementData数组中。  
    43    // 即将当前位于该位置的元素以及所有后续元素右移一个位置。  
    44    System.arraycopy(elementData, index, elementData, index + 1, size - index);  
    45    elementData[index] = element;  
    46    size++;  
    47 }    
    48 // 按照指定collection的迭代器所返回的元素顺序,将该collection中的所有元素添加到此列表的尾部。  
    49 public boolean addAll(Collection<? extends E> c) {  
    50    Object[] a = c.toArray();  
    51    int numNew = a.length;  
    52    ensureCapacity(size + numNew);  // Increments modCount  
    53    System.arraycopy(a, 0, elementData, size, numNew);  
    54    size += numNew;  
    55    return numNew != 0;  
    56 }    
    57 // 从指定的位置开始,将指定collection中的所有元素插入到此列表中。  
    58 public boolean addAll(int index, Collection<? extends E> c) {  
    59    if (index > size || index < 0)  
    60        throw new IndexOutOfBoundsException(  
    61            "Index: " + index + ", Size: " + size);  
    62  
    63    Object[] a = c.toArray();  
    64    int numNew = a.length;  
    65    ensureCapacity(size + numNew);  // Increments modCount  
    66  
    67    int numMoved = size - index;  
    68    if (numMoved > 0)  
    69        System.arraycopy(elementData, index, elementData, index + numNew, numMoved);  
    70  
    71    System.arraycopy(a, 0, elementData, index, numNew);  
    72    size += numNew;  
    73    return numNew != 0;  
       } 
    

    书上都说ArrayList是基于数组实现的,属性中也看到了数组,具体是怎么实现的呢?比如就这个添加元素的方法,如果数组大,则在将某个位置的值设置为指定元素即可,如果数组容量不够了呢?

    看到add(E e)中先调用了ensureCapacity(size+1)方法,之后将元素的索引赋给elementData[size],而后size自增。例如初次添加时,size为0,add将elementData[0]赋值为e,然后size设置为1(类似执行以下两条语句elementData[0]=e;size=1)。将元素的索引赋给elementData[size]不是会出现数组越界的情况吗?这里关键就在ensureCapacity(size+1)中了。

    元素的读取

    // 返回此列表中指定位置上的元素。  
     public E get(int index) {  
        RangeCheck(index);  
      
        return (E) elementData[index];  
      }
    

    元素的删除

    ArrayList提供了根据下标或者指定对象两种方式的删除功能。如下:

    • romove(int index):首先是检查范围,修改modCount,保留将要被移除的元素,将移除位置之后的元素向前挪动一个位置,将list末尾元素置空(null),返回被移除的元素。
    1 // 移除此列表中指定位置上的元素。  
     2  public E remove(int index) {  
     3     RangeCheck(index);  
     4   
     5     modCount++;  
     6     E oldValue = (E) elementData[index];  
     7   
     8     int numMoved = size - index - 1;  
     9     if (numMoved > 0)  
    10         System.arraycopy(elementData, index+1, elementData, index, numMoved);  
    11     elementData[--size] = null; // Let gc do its work  
    12   
    13     return oldValue;  
    14  }
    
    • remove(Object o):首先通过代码可以看到,当移除成功后返回true,否则返回false。remove(Object o)中通过遍历element寻找是否存在传入对象,一旦找到就调用fastRemove移除对象。为什么找到了元素就知道了index,不通过remove(index)来移除元素呢?因为fastRemove跳过了判断边界的处理,因为找到元素就相当于确定了index不会超过边界,而且fastRemove并不返回被移除的元素。
    1  // 移除此列表中首次出现的指定元素(如果存在)。这是应为ArrayList中允许存放重复的元素。  
     2  public boolean remove(Object o) {  
     3     // 由于ArrayList中允许存放null,因此下面通过两种情况来分别处理。  
     4     if (o == null) {  
     5         for (int index = 0; index < size; index++)  
     6             if (elementData[index] == null) {  
     7                 // 类似remove(int index),移除列表中指定位置上的元素。  
     8                 fastRemove(index);  
     9                 return true;  
    10             }  
    11     } else {  
    12         for (int index = 0; index < size; index++)  
    13             if (o.equals(elementData[index])) {  
    14                 fastRemove(index);  
    15                 return true;  
    16             }  
    17         }  
    18         return false;  
    19     } 
    20 } 
    

    下面是fastRemove的代码,基本和remove(index)一致。

    1 private void fastRemove(int index) {  
    2          modCount++;  
    3          int numMoved = size - index - 1;  
    4          if (numMoved > 0)  
    5              System.arraycopy(elementData, index+1, elementData, index,  
    6                               numMoved);  
    7          elementData[--size] = null; // Let gc do its work  
    8  }
    

    removeRange(int fromIndex,int toIndex):执行过程是将elementData从toIndex位置开始的元素向前移动到fromIndex,然后将toIndex位置之后的元素全部置空顺便修改size。

    这个方法是protected,及受保护的方法,为什么这个方法被定义为protected呢?

    这是一个解释,但是可能不容易看明白。http://stackoverflow.com/questions/2289183/why-is-javas-abstractlists-removerange-method-protected

    1 protected void removeRange(int fromIndex, int toIndex) {  
     2      modCount++;  
     3      int numMoved = size - toIndex;  
     4          System.arraycopy(elementData, toIndex, elementData, fromIndex,  
     5                           numMoved);  
     6    
     7      // Let gc do its work  
     8      int newSize = size - (toIndex-fromIndex);  
     9      while (size != newSize)  
    10          elementData[--size] = null;  
    11 } 
    

    调整数组容量ensureCapacity

    从上面介绍的向ArrayList中存储元素的代码中,我们看到,每当向数组中添加元素时,都要去检查添加后元素的个数是否会超出当前数组的长度,如果超出,数组将会进行扩容,以满足添加数据的需求。数组扩容通过一个公开的方法ensureCapacity(int minCapacity)来实现。在实际添加大量元素前,我也可以使用ensureCapacity来手动增加ArrayList实例的容量,以减少递增式再分配的数量。

    public void ensureCapacity(int minCapacity) {  
        modCount++;  
        int oldCapacity = elementData.length;  
        if (minCapacity > oldCapacity) {  
            Object oldData[] = elementData;  
            int newCapacity = (oldCapacity * 3)/2 + 1;  //增加50%+1
                if (newCapacity < minCapacity)  
                    newCapacity = minCapacity;  
          // minCapacity is usually close to size, so this is a win:  
          elementData = Arrays.copyOf(elementData, newCapacity);  
        }  
     }
    

    从上述代码中可以看出,数组进行扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组容量的增长大约是其原容量的1.5倍。这种操作的代价是很高的,因此在实际使用时,我们应该尽量避免数组容量的扩张。当我们可预知要保存的元素的多少时,要在构造ArrayList实例时,就指定其容量,以避免数组扩容的发生。或者根据实际需求,通过调用ensureCapacity方法来手动增加ArrayList实例的容量。

    Object oldData[] = elementData;//为什么要用到oldData[]
    乍一看来后面并没有用到关于oldData, 这句话显得多此一举!但是这是一个牵涉到内存管理的类, 所以要了解内部的问题。 而且为什么这一句还在if的内部,这跟elementData = Arrays.copyOf(elementData, newCapacity); 这句是有关系的,下面这句Arrays.copyOf的实现时新创建了newCapacity大小的内存,然后把老的elementData放入。好像也没有用到oldData,有什么问题呢。问题就在于旧的内存的引用是elementData, elementData指向了新的内存块,如果有一个局部变量oldData变量引用旧的内存块的话,在copy的过程中就会比较安全,因为这样证明这块老的内存依然有引用,分配内存的时候就不会被侵占掉,然后copy完成后这个局部变量的生命期也过去了,然后释放才是安全的。不然在copy的的时候万一新的内存或其他线程的分配内存侵占了这块老的内存,而copy还没有结束,这将是个严重的事情。

    关于ArrayList和Vector区别如下:

    • ArrayList在内存不够时默认是扩展50% + 1个,Vector是默认扩展1倍。
    • Vector提供indexOf(obj, start)接口,ArrayList没有。
    • Vector属于线程安全级别的,但是大多数情况下不使用Vector,因为线程安全需要更大的系统开销。

    ArrayList还给我们提供了将底层数组的容量调整为当前列表保存的实际元素的大小的功能。它可以通过trimToSize方法来实现。代码如下:

    127 public void trimToSize() {  
    128    modCount++;  
    129    int oldCapacity = elementData.length;  
    130    if (size < oldCapacity) {  
    131        elementData = Arrays.copyOf(elementData, size);  
    132    }  
        }
    

    由于elementData的长度会被拓展,size标记的是其中包含的元素的个数。所以会出现size很小但elementData.length很大的情况,将出现空间的浪费。trimToSize将返回一个新的数组给elementData,元素内容保持不变,length和size相同,节省空间。

    转为静态数组toArray

    ArrayList有两个转化为静态数组的toArray方法。

    第一个, 调用Arrays.copyOf将返回一个数组,数组内容是size个elementData的元素,即拷贝elementData从0至size-1位置的元素到新数组并返回。

    public Object[] toArray() {  
             return Arrays.copyOf(elementData, size);  
     } 
    

    第二个,如果传入数组的长度小于size,返回一个新的数组,大小为size,类型与传入数组相同。所传入数组长度与size相等,则将elementData复制到传入数组中并返回传入的数组。若传入数组长度大于size,除了复制elementData外,还将把返回数组的第size个元素置为空。

    public <T> T[] toArray(T[] a) {
            if (a.length < size)
                // Make a new array of a's runtime type, but my contents:
                return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        System.arraycopy(elementData, 0, a, 0, size);
            if (a.length > size)
                a[size] = null;
            return a;
        }
    

    Fail-Fast机制

    ArrayList也采用了快速失败的机制,通过记录modCount参数来实现。在面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险。具体介绍请参考这篇文章深入Java集合学习系列:HashMap的实现原理 中的Fail-Fast机制。

    总结

    1、注意其三个不同的构造方法。无参构造方法构造的ArrayList的容量默认为10,带有Collection参数的构造方法,将Collection转化为数组赋给ArrayList的实现数组elementData。

    2、注意扩充容量的方法ensureCapacity。ArrayList在每次增加元素(可能是1个,也可能是一组)时,都要调用该方法来确保足够的容量。当容量不足以容纳当前的元素个数时,就设置新的容量为旧的容量的1.5倍加1,如果设置后的新容量还不够,则直接新容量设置为传入的参数(也就是所需的容量),而后用Arrays.copyof()方法将元素拷贝到新的数组(详见下面的第3点)。从中可以看出,当容量不够时,每次增加元素,都要将原来的元素拷贝到一个新的数组中,非常之耗时,也因此建议在事先能确定元素数量的情况下,才使用ArrayList,否则建议使用LinkedList。

    3、ArrayList的实现中大量地调用了Arrays.copyof()和System.arraycopy()方法。我们有必要对这两个方法的实现做下深入的了解。

    首先来看Arrays.copyof()方法。它有很多个重载的方法,但实现思路都是一样的,我们来看泛型版本的源码:

    public static <T> T[] copyOf(T[] original, int newLength) {  
        return (T[]) copyOf(original, newLength, original.getClass());  
    }
    

    很明显调用了另一个copyof方法,该方法有三个参数,最后一个参数指明要转换的数据的类型,其源码如下:

    public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {  
        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));  
        return copy;  
    }
    

    这里可以很明显地看出,该方法实际上是在其内部又创建了一个长度为newlength的数组,调用System.arraycopy()方法,将原来数组中的元素复制到了新的数组中。

    下面来看System.arraycopy()方法。该方法被标记了native,调用了系统的C/C++代码,在JDK中是看不到的,但在openJDK中可以看到其源码。该函数实际上最终调用了C语言的memmove()函数,因此它可以保证同一个数组内元素的正确复制和移动,比一般的复制方法的实现效率要高很多,很适合用来批量处理数组。Java强烈推荐在复制大量数组元素时用该方法,以取得更高的效率。

    4、ArrayList基于数组实现,可以通过下标索引直接查找到指定位置的元素,因此查找效率高,但每次插入或删除元素,就要大量地移动元素,插入删除元素的效率低。

    5、在查找给定元素索引值等的方法中,源码都将该元素的值分为null和不为null两种情况处理,ArrayList中允许元素为null。

    展开全文
  • ArrayList底层实现和原理分析 今天是周末,没什么事就在家里看了一下源码,我习惯使用Jdk1.8,所以我的代码全都是基于Jdk1.8,好了下面是正文,今天来说说ArrayList的底层实现和源代码。 首先,集合ArrayList是List的...

    ArrayList底层实现和原理分析

    今天是周末,没什么事就在家里看了一下源码,我习惯使用Jdk1.8,所以我的代码全都是基于Jdk1.8,好了下面是正文,今天来说说ArrayList的底层实现和源代码。
    首先,集合ArrayList是List的实现类,List还有两个实现类LinkedList和Vector。
    先说说这几个实现类的区别吧:
    1.首先在底层实现上ArrayList和Vector是基于数组实现的,在源代码中都维护了一个数组用于对数组进行操作,LinkedList底层维护的是链表;
    2.类的继承路径不同,这里不多说继承路径了;
    3.ArrayList线程不安全;其余两种线程安全
    关于查询的速度:
    ArrayList的查询速度快,LinkedList的增删改速度快;Vector因为是线程安全的所以它的查询速度也是很慢的;(原因在源代码中解释)

    下面开始看代码:
    首先:ArrayList提供了三个构造方法:
    1.定义初始容量的集合

        /**
         * 定义了初始容量为10的Array List
         */
        public ArrayList() {
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
    
        /**
         * 可以自己定义创建集合的容量
         *
         */
        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);
            }
        }
    
        public ArrayList(Collection<? extends E> c) {
            elementData = c.toArray();
            if ((size = elementData.length) != 0) {
                // c.toArray might (incorrectly) not return Object[] (see 6260652)
                if (elementData.getClass() != Object[].class)
                    elementData = Arrays.copyOf(elementData, size, Object[].class);
            } else {
                // replace with empty array.
                this.elementData = EMPTY_ELEMENTDATA;
            }
        }
    

    以上就是三种ArrayList的构造方法逻辑简单,代码简明就不做解释了,给个总结,这三个构造方法就是初始化一个存储数据的容器,其核心就是一个数组,也就是上面的elementData。

    下面来看看ArrayList提供的方法:
    首先要先解释下ArrayList中的扩容方法:

    private void ensureCapacityInternal(int minCapacity) {
            ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
        }
    
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            }
            return minCapacity;
        }
    
    private void ensureExplicitCapacity(int minCapacity) {
            modCount++;
    
            // overflow-conscious code
            if (minCapacity - elementData.length > 0)
                grow(minCapacity);
        }
    
        private void grow(int minCapacity) {
            // 原数组长度
            int oldCapacity = elementData.length;
            //将数组长度扩容至原先的1.5倍>>1 右移一位相当于除以2, 左移一位相当于乘以2
            int newCapacity = oldCapacity + (oldCapacity >> 1);
            //新长度小于元素个数,则将新长度变为元素个数
            if (newCapacity - minCapacity < 0)
                newCapacity = minCapacity;
            //这是超限情形,即当新长度大于int最大值-8,则新长度为int最大值,hugeCapacity(minCapacity)实现不粘贴了,可以自己去源码中看
            if (newCapacity - MAX_ARRAY_SIZE > 0)
                newCapacity = hugeCapacity(minCapacity);
            // minCapacity is usually close to size, so this is a win:
            这一步将原数组中数据复制到新数组中去完成扩容
            elementData = Arrays.copyOf(elementData, newCapacity);
        }
    

    这三个方法为扩容方法,但是其核心为grow(int minCapacity),
    扩容操作发生在像集合中添加元素
    这时候会首先调用ensureCapacityInternal(int minCapacity) ,在这个方法里会进行调用calculateCapacity(Object[] elementData, int minCapacity)进行判断在进行调用
    ensureExplicitCapacity(int minCapacity)

    在calculateCapacity(Object[] elementData, int minCapacity)中如上面代码可以看到一个判断条件
    elementData ==DEFAULTCAPACITY_EMPTY_ELEMENTDATA
    在这个方法中主要是要解决一个问题就是确定最小容量,这么说大家可能有点迷,说得通俗点就是判断是不是第一次添加元素,如果是也就是说这个集合是空的那么也就是说这个集合的核心数组是空的,这时候就要看添加的元素个数和默认长度谁大了,谁大就取谁,不是第一次添加直接就返回添加的元素个数。
    那么好,现在就开始调用ensureExplicitCapacity(int minCapacity); 这个方法的作用是什么呢,很简单,就是判断集合是否需要扩容,判断的条件:
    minCapacity - elementData.length > 0
    minCapacity这个变量是之前比较出的最小容量,判断这个变量是否大于数组长度,如果大于则需要扩容,即调用扩容核心方法grow(int minCapacity);
    在这个方法中可以很清楚的看到,这个方法每一步在上面都有很详细的注解,不在这里多做解释,现在介绍几个核心方法。

    首先添加方法,添加提供了四种:
    1.这个是添加单个元素的,就是先扩容,再将元素添加至数组中,完成添加。

        public boolean add(E e) {
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            elementData[size++] = e;
            return true;
        }
    

    2.向指定位置添加一个元素(代码解释放在代码中)

        public void add(int index, E element) {
        	//判断index是否合法(是否超过数组长度是否大于0,不符合抛异常)
            rangeCheckForAdd(index);
    		//日常扩容没有什么问题
            ensureCapacityInternal(size + 1);  // Increments modCount!!
            //将index及其之后的元素向后移动移动一位
            System.arraycopy(elementData, index, elementData, index + 1,
                             size - index);
            //为index下标位置的赋值为需要加入的元素
            elementData[index] = element;
            size++;
        }
    

    3.像集合中添加一个指定集合

        public boolean addAll(Collection<? extends E> c) {
        	//先将传入的集合转化为Object数组
            Object[] a = c.toArray();
            //获得a数组的长度
            int numNew = a.length;
            //日常扩容没什么问题
            ensureCapacityInternal(size + numNew);  // Increments modCount
            //将a数组copy进扩容之后的数组
            System.arraycopy(a, 0, elementData, size, numNew);
            size += numNew;
            return numNew != 0;
        }
    

    4.向指定位置开始加入指定集合

        public boolean addAll(int index, Collection<? extends E> c) {
            rangeCheckForAdd(index);
    
            Object[] a = c.toArray();
            int numNew = a.length;
            ensureCapacityInternal(size + numNew);  // Increments modCount
    
            int numMoved = size - index;
            if (numMoved > 0)
                System.arraycopy(elementData, index, elementData, index + numNew,
                                 numMoved);
    
            System.arraycopy(a, 0, elementData, index, numNew);
            size += numNew;
            return numNew != 0;
        }
    

    这个方法与上面的并没有什么区别;

    现在来看删除操作:
    1.首先是删除单个元素的两个方法:

    public E remove(int index) {
    		//检查index是否合法(同上)
            rangeCheck(index);
    
            modCount++;
            //记录这个即将被删除的元素
            E oldValue = elementData(index);
    		
            int numMoved = size - index - 1;
            //判断是否是最后一位元素,如果是就不需要将后面的元素向后移了,直接将元素设置为null
            if (numMoved > 0)
                System.arraycopy(elementData, index+1, elementData, index,
                                 numMoved);
            //将元素设置为null
            elementData[--size] = null; // clear to let GC do its work
    
            return oldValue;
        }
    
    
    //删除单个对象
    public boolean remove(Object o) {
            if (o == null) {
            	//遍历数组
                for (int index = 0; index < size; index++)
                	查找对象
                    if (elementData[index] == null) {
                    	//删除第一个符合的元素
                        fastRemove(index);
                        return true;
                    }
            } else {
            	//遍历数组
                for (int index = 0; index < size; index++)
                	//查找对象
                    if (o.equals(elementData[index])) {
                    	//只删除第一个
                        fastRemove(index);
                        return true;
                    }
            }
            return false;
        }
    
    
    
    

    2.删除多个元素:

    public boolean removeAll(Collection<?> c) {
        // 参数为空校验
        Objects.requireNonNull(c);
        return batchRemove(c, false);
    }
    private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;
        boolean modified = false;
        try {
            // 遍历当前数组
            for (; r < size; r++)
                // 判断参数中是否包含当前元素
                if (c.contains(elementData[r]) == complement)
                    // 整理当前数组,w为已整理元素个数
                    elementData[w++] = elementData[r];
        } finally {
            // 只有在前面循环完,数组扩容才会r!=size
            if (r != size) {
                // 把w至r的元素干掉,r至新size的元素前移
                System.arraycopy(elementData, r,
                        elementData, w, size - r);
                // 设置w长度
                w += size - r;
            }
            // 参数跟当前数组不一样
            if (w != size) {
                // 从w开始清空
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                // 修改元素个数
                size = w;
                modified = true;
            }
        }
        return modified;
    }
    

    下面来看看“修改”操作
    1.将指定位置替换成指定元素
    public E set(int index, E element) {
    //检查是否合法
    rangeCheck(index);
    //记录需要替换的元素
    E oldValue = elementData(index);
    直接为数组赋值
    elementData[index] = element;
    return oldValue;
    }

    清空集合方法

        public void clear() {
            modCount++;
    		
            // clear to let GC do its work
            for (int i = 0; i < size; i++)
            	//遍历数组将每个元素置空
                elementData[i] = null;
    		//将长度变为0
            size = 0;
        }
    

    最后是转化为数组方法

    public Object[] toArray() {
        return Arrays.copyOf(elementData, size);
    }
    public <T> T[] toArray(T[] a) {
        // 判断长度是否够用
        if (a.length < size)
            // 长度不够,创建长度为size的数组,并拷贝
            return (T[]) Arrays.copyOf(elementData, size, a.getClass());
        // 长度不够用,直接拷贝
        System.arraycopy(elementData, 0, a, 0, size);
        if (a.length > size)
            a[size] = null;
        return a;
    }
    

    好了暂时总结到这,方法还有很多,基础方法懂了,其实都很简单;明天分享Map类的集合;再见各位。

    展开全文
  • 模拟ArrayList底层实现

    2016-02-07 10:43:00
    package chengbaoDemo; import java.util.ArrayList; import java.util.Arrays; import comman.Human;... * ArrayList 底层实现 */ public class MyArrayList { /** * The value is used fo...
  • ArrayList 底层实现核心点 1.集合底层使用数组实现的 2.为什么集合能存放无限大小?####数组扩容技术实现的 Arrays.copyOf: 实现数组的复制,返回复制后的数组。参数是被复制的数组和复制的长度。 返回一个新的数组...
  • import java.util.ArrayList; import java.util.List;... * 模拟ArrayList底层实现基础方法   * @author Mrzhang  *  * @param  */ public class MyArrayList { //基础数组长度设置  privat
  • 1.MyArrayList(ArrayList底层实现) public class MyArrayList { /** * 定义数组,保存数据 */ private Object[] objects = null; /** * 默认数组长度 */ private final int DEFAULT_LENGTH = 10; /**...
  • Java集合ArrayList 底层实现原理: 在Java中对于集合的使用是比较重要且普遍的,对于它们之间具体的关系图,如下所示: 常用的集合:List、Set、Map List集合 List是一个有序的,可重复的集合。这里的有序是指...
  • 最近听一个同事公司面试,问及java中ArrayList底层实现问题。对于一个搞java变成的人来说,这个问题真的应该算陌生。因为这是基础中的基础,不应该不知道,今天我就拿出点时间来叙述一下java中ArrayList的底层具体...
  • ArrayList底层实现问题

    2015-08-25 10:48:21
    求大神解答:为什么ArrayList底层使用Object数组Object[],而不是直接使用泛型数组E[]存储元素?
  • 模仿ArrayList底层实现

    2017-12-19 18:44:00
    制作一个简单的Arraylist底层执行实现: 我们需要有三个类:① ArrayList.java;② List.java;③ Test.java;以此做一个简单的模仿 第一步:ArrayList.java类 1 public class ArrayList implements List{...
  • ArrayList简介  ArrayList就是动态数组,,相当于Array的复杂版本,它提供了动态的增加和减少元素,实现了Collection和List接口,可以灵活的设置数组的大小。... ArrayList底层使用数组存储元素,默认数组大...

空空如也

1 2 3 4 5 ... 20
收藏数 5,173
精华内容 2,069
关键字:

arraylist底层实现