likes
comments
collection
share

浅看Android 中ArrayList源码

作者站长头像
站长
· 阅读数 4

重新翻了下Java看到泛型,描述的仍然是那么抽象。其实泛型这个东西没那么神秘,用的多了接触的多了,自然就理解了,但真要说个一二三也不一定能说的上来,真要说的话无非就是提一下泛型使用中的一些约束,或者定义泛型时的一些继承派生规则,通配符什么的。

总的来说,还是要去使用中理解泛型,Java和Android中大量用到泛型,比如HashMap和ArrayList,当然还有很多也没办法都列举出来。正好想到这里就去看看ArrayList的源码吧。

一、ArrayList构造函数

泛型的好处,就是我们通过定义一个泛型类ArrayList,那么这个类只需要写一次就可以实现通用,比如我们可以

ArrayList<String> string1 = new ArrayList<String>();

这样就定义了一个字符串类型的ArrayList,同样我们可以传其他的对象进去,比如在Android中我们可以用它来装Activity,装Window都可以,这就是因为ArrayList是通过泛型的方式来定义的。我们在使用ArrayList的时候会先new出来一个对象,所以先看下源码中它的构造函数

/libcore/ojluni/src/main/java/java/util/ArrayList.java

164      public ArrayList(int initialCapacity) {
             //根据输入的int来决定数组的长度
165          if (initialCapacity > 0) {
166              this.elementData = new Object[initialCapacity];
167          } else if (initialCapacity == 0) {
168              this.elementData = EMPTY_ELEMENTDATA;
169          } else {
170              throw new IllegalArgumentException("Illegal Capacity: "+
171                                                 initialCapacity);
172          }
173      }
174  
175      /**
176       * Constructs an empty list with an initial capacity of ten.
177       */
178      public ArrayList() {
             //无参构造器,直接给定一个默认数组长度
179          this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
180      }
181  
182      /**
183       * Constructs a list containing the elements of the specified
184       * collection, in the order they are returned by the collection's
185       * iterator.
186       *
187       * @param c the collection whose elements are to be placed into this list
188       * @throws NullPointerException if the specified collection is null
189       */
190      public ArrayList(Collection<? extends E> c) {
             //将传进来的集合变成数组
191          elementData = c.toArray();
192          if ((size = elementData.length) != 0) {
193              // c.toArray might (incorrectly) not return Object[] (see 6260652)
194              if (elementData.getClass() != Object[].class)
195                  elementData = Arrays.copyOf(elementData, size, Object[].class);
196          } else {
197              // replace with empty array.
198              this.elementData = EMPTY_ELEMENTDATA;
199          }
200      }

这里构造函数有三种,分成有参和无参两类,有参构造函数又分成ArrayList(int initialCapacity);和ArrayList(Collection<? extends E> c);构造函数比较简单,就是初始化一下数组的长度。当然这是一个泛型类

119  public class ArrayList<E> extends AbstractList<E>
120          implements List<E>, RandomAccess, Cloneable, java.io.Serializable

作为泛型类里面的普通方法,它的对象就是传入的泛型。数据结构产生的目的就是维护和管理数据,那么其本质就是增删改查,接下来看看ArrarList的增删改查是怎么做的

二、ArrayList增删改查

先来看下add是怎么做的,ArrayList中重构了两种add()函数

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


481      public void add(int index, E element) {
482          if (index > size || index < 0)
483              throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
484  
485          ensureCapacityInternal(size + 1);  // Increments modCount!!
486          System.arraycopy(elementData, index, elementData, index + 1,
487                           size - index);
488          elementData[index] = element;
489          size++;
490      }

这两个add,分别是向数组中添加一个对象,和向指定Index中添加一个对象,那么如果指定的这个Index中已经有元素了呢?通过看源码我们很容易发现,在Index已经存在元素的情况下去add,实际上是用当前值覆盖旧值的操作,那么也就相当于同时实现增和改的功能了。

看这两个函数结尾处,就是把要添加的元素放到指定下标的位置,然后给数组的size增加。在这之前需要注意一个函数ensureCapacityInternal(size + 1)

236      private void ensureCapacityInternal(int minCapacity) {
237          if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
238              minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
239          }
240  
241          ensureExplicitCapacity(minCapacity);
242      }
243  
244      private void ensureExplicitCapacity(int minCapacity) {
245          modCount++;
246  
247          // overflow-conscious code
248          if (minCapacity - elementData.length > 0)
249              grow(minCapacity);
250      }

最终它会调用到grow(),我们在使用ArrayList时不用考虑它的长度,因为我们知道ArrayList是会自动扩容的,原理就在这里的grow()。

267          // overflow-conscious code
268          int oldCapacity = elementData.length;
269          int newCapacity = oldCapacity + (oldCapacity >> 1);
270          if (newCapacity - minCapacity < 0)
271              newCapacity = minCapacity;
272          if (newCapacity - MAX_ARRAY_SIZE > 0)
273              newCapacity = hugeCapacity(minCapacity);
274          // minCapacity is usually close to size, so this is a win:
275          elementData = Arrays.copyOf(elementData, newCapacity);
276      }

在grow()当中,也是一些基础的判断和异常筛查,当我们正常扩容走到这里的时候,这里新的数组容量newCapacity和旧容量oldCapacity的关系是什么呢?

int newCapacity = oldCapacity + (oldCapacity >> 1);

在二进制中,数字向右移动一位相当于除以2,所以可以说新数组的容量是旧数组容量的1.5倍。然后通过一个Arrays.copyOf()方法,将旧数组在添加到新数组当中。所以增的方法很简单,就是先判断是否需要扩容,如果需要的话就扩容到1.5倍然后将数据添加到对应下标的位置即可。需要注意到这里有个modCount

244      private void ensureExplicitCapacity(int minCapacity) {
245          modCount++;

这里的modCount作用就相当于信号量,是用来提醒并发操作导致的异常,后面有很多地方会检查modCount,如果检查异常会报错

765          if (modCount != expectedModCount) {
766              throw new ConcurrentModificationException();
767          }

看下remove操作

871          public void remove() {
872              if (lastRet < 0)
873                  throw new IllegalStateException();
874              if (modCount != expectedModCount)
875                  throw new ConcurrentModificationException();
876  
877              try {
                     可以看到如果直接空参调remove的话是会移除最后一个元素
878                  ArrayList.this.remove(lastRet);
879                  cursor = lastRet;
880                  lastRet = -1;
881                  expectedModCount = modCount;
882                  limit--;
883              } catch (IndexOutOfBoundsException ex) {
884                  throw new ConcurrentModificationException();
885              }
886          }

1071          public E remove(int index) {
                  //根据下标调用remove移除指定元素
1072              if (index < 0 || index >= this.size)
1073                  throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
1074              if (ArrayList.this.modCount != this.modCount)
1075                  throw new ConcurrentModificationException();
                  //用的是父类的remove方法
1076              E result = parent.remove(parentOffset + index);
1077              this.modCount = parent.modCount;
1078              this.size--;
1079              return result;
1080          }

这里再往下看实际上remove也是调到了Cpp层的,在移除对应坐标元素之后,再返回新的数组。 数组的查询实际上也是很简单的

435      public E get(int index) {
436          if (index >= size)
437              throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
438  
439          return (E) elementData[index];
440      }

直接返回这个下标的对象就好了。这里的泛型E就是在初始化的时候数组中的对象类型。

在了解ArrayList的增删改查之后,就可以理解ArrayList中一个比较细节的地方,我们在初始化数组的时候,会用一个elementData来表示数组中的元素,这里是使用transient限定符来修饰的,这个限定符比较少见。

141      /**
142       * The array buffer into which the elements of the ArrayList are stored.
143       * The capacity of the ArrayList is the length of this array buffer. Any
144       * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
145       * will be expanded to DEFAULT_CAPACITY when the first element is added.
146       */
147      // Android-note: Also accessed from java.util.Collections
148      transient Object[] elementData; // non-private to simplify nested class access

对于transient 修饰的成员变量,在类的实例对象的序列化处理过程中会被忽略。 因此,transient变量不会贯穿对象的序列化和反序列化,生命周期仅存于调用者的内存中而不会写到磁盘里进行持久化。怎么去理解呢,实际上序列化就是把一个java对象转换成字节的形式,然后这个字节就可以被底层使用,用来储存或者是通信传输之类的,当然会占用一定的内存。那么基于对ArrayList源码的分析,首先ArrayList是一个不定长的数组,它的容量是不确定的,并且还会自己扩容,但是扩容之后并没有一个自动裁剪多余容量的机制,所以ArrayList中是可能出现数组很大,其中有用的元素却很少的情况。为了避免为过多的null值序列化造成的内存浪费,这里通过transient来修饰ArrayList中的元素变量,不参与Java的序列化,这样就避免无用功,可以节约内存。

当然ArrayList自己也有一套序列化机制,它还是可以保存起来也可以进行数据传输滴,不过那个就太底层了,我们从Android的角度不必去考虑,只是知道有这么回事就可以了。

转载自:https://juejin.cn/post/7170659798705242120
评论
请登录