Android回看——ArrayList详解,源码解析

我们了解了[数组](Android回看——数组 - 简书 (jianshu.com)
),这就看看我们使用的数组结构:ArrayList。

ArrayList基本上是Android中最常使用的集合类型了,从名字也可以看出来它是一个数组列表,是用一个数组存储操作我们需要的数据。

基本参数

    /**默认初始容量*/ 
    private static final int DEFAULT_CAPACITY = 10;  

   /**用于空实例的共享空数组实例*/  
   private static final Object[] EMPTY_ELEMENTDATA = {};   

  /**共享的空数组实例,用于默认大小的空实例。 我们将此与EMPTY_ELEMENTDATA区别开来,以了解添加第一个元素时需要添加多少*/  
   private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};   

  /**存储元素的数组,在添加第一个元素时将会扩充至10*/  
   transient Object[] elementData;   

  /**ArrayList内的元素个数*/ 
    private int size;

可以看到ArrayList内定义了两个空数组,和一个值为10的参数(注释表示初始容量,但其实是分情况的),数组长度不一定是等于size的,size是实际元素个数的数量。

构造函数

第一种:带初始容量的构造函数

/**      * 构造一个具有指定初始容量的空列表      *      * @param  initialCapacity  列表的初始容量      */  
   public ArrayList(int initialCapacity) {     
    if (initialCapacity > 0) {//如果初始容量大于0   
          this.elementData = new Object[initialCapacity];//elementData赋值为一个初始容量为initialCapacity的Object数组  
       } else if (initialCapacity == 0) {//如果初始容量等于0         
    this.elementData = EMPTY_ELEMENTDATA;//elementData赋值为空实例数组     
    } else {      
       throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);     
    }   
  }

第二种:不带参的构造函数

  /**      * Constructs an empty list with an initial capacity of ten.      * 构造一个初始容量为10的空列表      */   
 public ArrayList() {    
     this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;//赋值为已创建的空数组 
    }

注意,这里源码注释是初始值为10的空数组,实际上给的是一个之前定义的空数组,那这里?岂不是注释错了?

其实以前老版本的JDK是一个大小为10的空数组的,现在修改为空数组,注释却没有修改。像极了平时你修改了代码却不改注释,让同事看得一脸懵逼

第三种:带集合的构造函数

    public ArrayList(Collection<? extends E> c) { 
        elementData = c.toArray();//先转换为数组,再赋值给elementData    
     if ((size = elementData.length) != 0) {//size赋值为elementData的长度、并判断如果长度不为0 
//如果出现elementData类型异常,拷贝一个新的elementData       
      if (elementData.getClass() != Object[].class) 
                elementData = Arrays.copyOf(elementData, size, Object[].class);        
 } else {        
     // 替换为空数组     
        this.elementData = EMPTY_ELEMENTDATA;    
     }    
 }

这里可以看到,不管是哪一种构造函数,在没有设置初始容量的时候,或者设置的初始容量为0的时候,都给数据持有对象elementData赋值为了空数组。

添加

添加有几种方法,我们主要看添加一个元素

/** * Appends the specified element to the end of this list. * * @param e element to be appended to this list * @return <tt>true</tt> (as specified by {@link Collection#add}) */ 
public boolean add(E e) {       
ensureCapacityInternal(size + 1); // Increments modCount!!  
 elementData[size++] = e;  
     return true; 
}

看起来其中的处理基本都在ensureCapacityInternal方法中,进去看看

计算容量

private void ensureCapacityInternal(int minCapacity) {   
   //如果当前数组等于空数组 
  if (elementData == *DEFAULTCAPACITY_EMPTY_ELEMENTDATA*) {         
      //最小容量等于默认初始容量(10)和传入的size+1中的最大值  
     minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
 }    
    ensureExplicitCapacity(minCapacity); 
}

此时就可以看到内部已经开始修改容量了,如果是空数组并且是第一次添加,最小容量就变成10,其他情况下在ensureExplicitCapacity中处理,minCapacity = size+1

让我们接着看ensureExplicitCapacity

ensureExplicitCapacity

private void ensureExplicitCapacity(int minCapacity) {       modCount++; // overflow-conscious code   //如果minCapacity大于当前的容量   if (minCapacity - elementData.length > 0)           grow(minCapacity); }

这里modCount暂时不用管它,是一个对数组进行更改的技术,用于纠错;

grow方法,看名字也应该知道,它计算完新的容量后终于要开始扩容了

扩容

private void grow(int minCapacity) { 
   // overflow-conscious code
 int oldCapacity = elementData.length;  
     //定义一个新的容量,它的值为老值的1.5倍 
  int newCapacity = oldCapacity + (oldCapacity >> 1);  
 //如果新的容量小于最小容量 
if (newCapacity - minCapacity < 0) 
     newCapacity = minCapacity;   
  //如果新的容量大于数组最大容量
 if (newCapacity - *MAX_ARRAY_SIZE* > 0)     newCapacity = *hugeCapacity*(minCapacity);
 // minCapacity is usually close to size, so this is a win: 
  //数组等于copy的新数组,更改了它的容量
   elementData = Arrays.*copyOf*(elementData, newCapacity); 
} 
//数组最大容量
 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; 
//int最大值 
public static final int MAX_VALUE = 2147483647; 
//返回最大值MAX_VALUE(因为上面以及判断了newCapacity>MAX_ARRAY_SIZE)
 private static int hugeCapacity(int minCapacity) {    
  if (minCapacity < 0)
 // overflow     
       throw new OutOfMemoryError();    
  return (minCapacity > MAX_ARRAY_SIZE) ?  Integer.MAX_VALUE : MAX_ARRAY_SIZE; 
}

从grow方法中可以看到数组是如何扩容的,它是通过各种判断后获取了新的容量大小,然后copy出来的,也知道了数组容量的最大值是Int的最大值。

当这个方法调用完之后,再调用了

elementData[size++] = e;

实现了一个元素的添加。

其他添加方法

现在我们来看看其他的添加方法

public void add(int index, E element) { 
   //先对index做限制,超出限制抛出异常  
 if (index > size || index < 0)      
    throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
  //依然用了这个方法   
ensureCapacityInternal(size + 1);  
  //将index之后的元素向后移动   
System.*arraycopy*(elementData, index, elementData, index + 1, size - index);   
//最后index位置放上我们要插入的元素   
elementData[index] = element; size++;
 }

删除

先看看第一种删除方式,删除某一位置元素:

机翻注释:移除此列表中指定位置的元素。 将任何后续元素向左移动(从它们的索引中减去一个)。

public E remove(int index) {    
  if (index >= size)     
   throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); 
 modCount++; 
 //拿到要删除的对象   
E oldValue = (E) elementData[index]; 
 //计算要移动的数据数量(也就是后面将要移动的数据)
   int numMoved = size - index - 1;  
  if (numMoved > 0) 
 //拷贝移动数据    
 System.arraycopy(elementData, index+1, elementData, index,numMoved);
  //将最后一位置空 
  elementData[--size] = null; 
// clear to let GC do its work  
//返回已删除的对象  
 return oldValue; 
}

再看第二种删除方式,删除某一元素:

机翻注释:从此列表中删除第一次出现的指定元素(如果存在)。 如果列表不包含该元素,则它保持不变。 更正式地,删除具有最低索引i的元素,使得(o==null ? get(i)==null : o.equals(get(i))) (如果这样的元素存在)。 如果此列表包含指定的元素(或等效地,如果此列表因调用而更改),则返回true

public boolean remove(Object o) {
 if (o == null) { 
//如果元素是null,则删除从列表开始第一个找到的 == null的元素,返回true 
for (int index = 0; index < size; index++) 
if (elementData[index] == null) { 
fastRemove(index);
 return true;
 }
 } else { 
//如果不为空,则删除从列表开始第一个找到的符合equals()函数的元素,返回true 
for (int index = 0; index < size; index++)
 if (o.equals(elementData[index])) { 
fastRemove(index); 
return true;
 }
 }
 //若没找到,返回false
 return false; 
}

我们再看看具体的删除逻辑是怎么样的,都在fastRemove(index)函数里面

private void fastRemove(int index) { 
modCount++;
 int numMoved = size - index - 1; 
if (numMoved > 0) 
  System.*arraycopy*(elementData, index+1, elementData, index, numMoved); 
elementData[--size] = null;// clear to let GC do its work
 }

逻辑操作和第一种删除方法是一样的,找到它的index位置进行删除、移动元素

到这里基本就结束了,为什么不讲更多的比如addAll(),clear()这些?
因为逻辑上基本是差不多的,懂了上面的,其他的方法看一眼就清楚了。

自己写一篇文章收获还是蛮多的,自己看一遍一会儿就看完了,懂也感觉自己懂了,但是写的时候发现其中细节地方还有不明白的,推荐有时间的也可以自己写一写,对自己帮助不少。

说完了ArrayList,下一个要不讲讲它兄弟HashMap或者离不开它的RecyclerView吧。不过时间不确定,因为本来我打算这篇文章连看源码+文章几个小时怎么也够了的,结果也硬生生拖了两个月写完2333

因为是从笔记里粘贴过来的,所以格式有点乱,以后会在这里写的;如有错误,欢迎指正

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335

推荐阅读更多精彩内容