集合——实现类——ArrayList底层实现原理

ArrayList实现原理及源码分析
1、ArrayList是List接口的可变数组非同步实现,并允许包括null在内的所有元素。
2、底层使用数组实现
3、该集合是可变长度数组,数组扩容时,会将老数组中的元素重新拷贝一份到新的数组中,每次数组 容量增长大约是其容量的1.5倍,这种操作的代价很高。若是能预估到顶峰容量,可以设置一个足够大的量以避免数组容量以后的扩展。
4、采用了Fail-Fast机制,面对并发的修改时,迭代器很快就会完全失败,而不是冒着在将来某个不确定时间发生任意不确定行为的风险
remove方法会让下标到数组末尾的元素向前移动一个单位,并把最后一位的值置空,方便GC
5、add、remove操作对于ArrayList其运行时间是O(N),因为在它当中在前端进行添加或移除构造新数组是O(N)操作;get方法的调用为O(1)操作。要是使用一个增强的for循环,对于任意List的运行时间都是O(N),因为迭代器将有效地从一项到下一项推进。

类的继承关系

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

        
 说明:ArrayList继承AbstractList抽象父类,实现了List接口(规定了List的操作规范)、
            RandomAccess(可随机访问)、Cloneable(可拷贝)、Serializable(可序列化)。

类的属性

 public class ArrayList<E> extends AbstractList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable{
    
    // 版本号
     private static final long serialVersionUID = 8683452581122892189L;
     
    // 缺省容量
	private static final int DEFAULT_CAPACITY = 10;
	
    // 空对象数组
    private static final Object[] EMPTY_ELEMENTDATA = {};
    
    // 缺省空对象数组
	 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
	 
    // 元素数组
    transient Object[] elementData;
    
    // 实际元素大小,默认为0
    private int size;
    
    // 最大数组容量
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
    } 


        说明:类的属性中核心的属性为elementData,类型为Object[],用于存放实际元素,
           并且被标记为transient,也就意味着在序列化的时候,此字段是不会被序列化的。

类的构造函数

  1ArrayList(int)型构造函数 
   
    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);
        }
    }

     说明:initialCapacity初始化大小长度,指定elementData数组的大小,不允许初始化大小小于0,否则抛出异常。
  
2. ArrayList()型构造函数
      
public ArrayList() { 
    // 无参构造函数,设置元素数组为空 
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

    说明:当未指定初始化大小时,会给elementData赋值为空集合。
 3. ArrayList(Collection<? extends E>)型构造函数 

 public ArrayList(Collection<? extends E> c) { // 集合参数构造函数
    elementData = c.toArray(); // 转化为数组
    if ((size = elementData.length) != 0) { // 参数为非空集合
        if (elementData.getClass() != Object[].class) // 是否成功转化为Object类型数组
            elementData = Arrays.copyOf(elementData, size, Object[].class); // 不为Object数组的话就进行复制
    } else { // 集合大小为空,则设置元素数组为空
        this.elementData = EMPTY_ELEMENTDATA;
    }
} 
 说明:当传递的参数为集合类型时,会把集合类型转化为数组类型,并赋值给elementData。
 
 elementData.getClass() != Object[].class 
 解释:
 public class UserTest {
    public static void main(String[] args) {
        User[] users = new User[]{
                new User(1, "admin", "admin@qq.com"),
                new User(2, "maco", "maco@qq.com"),
                new User(3, "kitty", "kitty@163.com")
        };

        Object[] objects = users;
        System.out.println(objects.getClass());     // class [Ltest.User;
        if (objects.getClass() != Object[].class) {
            objects = Arrays.copyOf(objects, objects.length, Object[].class);  
        }
        System.out.println(objects.getClass());  // class [Ljava.lang.Object;
    }
}

核心函数分析

1、add函数

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

    说明:在add函数我们发现还有其他的函数ensureCapacityInternal,
       此函数可以理解为确保elementData数组有合适的大小。
   ensureCapacityInternal的具体函数如下:
       
   private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { // 判断元素数组是否为空数组
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); // 取较大值
        }
        
        ensureExplicitCapacity(minCapacity);
    }

    说明:在ensureCapacityInternal函数中我们又发现了ensureExplicitCapacity函数,这个函数也是为了确保elemenData
    数组有合适的大小。
   ensureExplicitCapacity的具体函数如下:
      
 private void ensureExplicitCapacity(int minCapacity) {
    // 结构性修改加1
    modCount++;
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

  说明:在ensureExplicitCapacity函数我们又发现了grow函数,grow函数才会对数组进行扩容,ensureCapacityInternal、ensureExplicitCapacity都只是过程,最后完成实际扩容操作还是得看grow函数,
       grow函数的具体函数如下 :
   private void grow(int minCapacity) {
                 int oldCapacity = elementData.length; // 旧容量
                 int newCapacity = oldCapacity + (oldCapacity >> 1); // 新容量为旧容量的1.5倍
              if (newCapacity - minCapacity < 0) // 新容量小于参数指定容量,修改新容量
                   newCapacity = minCapacity;
           	  if (newCapacity - MAX_ARRAY_SIZE > 0) // 新容量大于最大容量
                  newCapacity = hugeCapacity(minCapacity); // 指定新容量
            // 拷贝扩容
              elementData = Arrays.copyOf(elementData, newCapacity);
    }  
    
 private static int hugeCapacity(int paramInt){
			if (paramInt < 0) {
			  throw new OutOfMemoryError();
			}
	return paramInt > 2147483639 ? Integer.MAX_VALUE : 2147483639;
	}

2. set函数

public E set(int index, E element) {
     		   // 检验索引是否合法
        		rangeCheck(index);
        		// 旧值
       			 E oldValue = elementData(index);
      		  // 赋新值
      		  elementData[index] = element;
       		 // 返回旧值
       		 return oldValue;
    } 

    说明:设定指定下标索引的元素

3. get函数

public E get(int index) {
    // 检验索引是否合法
    rangeCheck(index);

    return elementData(index);
}
    
    说明:get函数会检查索引值是否合法(只检查是否大于size,而没有检查是否小于0),值得注意的是,
    在get函数中存在element函数,element函数用于返回具体的元素,具体函数如下
   E elementData(int index) {
        return (E) elementData[index];
    }

 说明:返回的值都经过了向下转型(Object -> E),这些是对我们应用程序屏蔽的小细节。

4. remove函数

public E remove(int index) {
    // 检查索引是否合法
    rangeCheck(index);
    
    modCount++;
    E oldValue = elementData(index);
    // 需要移动的元素的个数
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    // 赋值为空,有利于进行GC
    elementData[--size] = null; 
    // 返回旧值
    return oldValue;
}
说明:remove函数用户移除指定下标的元素,此时会把指定下标到数组末尾的元素向前移动一个单位,
    并且会把数组最后一个元素设置为null,这样是为了方便之后将整个数组不被使用时,会被GC,
    可以作为小的技巧使用。

5. indexOf和lastIndexOf函数

// 从首开始查找数组里面是否存在指定元素
    public int indexOf(Object o) {
        if (o == null) { // 查找的元素为空
            for (int i = 0; i < size; i++) // 遍历数组,找到第一个为空的元素,返回下标
                if (elementData[i]==null)
                    return i;
        } else { // 查找的元素不为空
            for (int i = 0; i < size; i++) // 遍历数组,找到第一个和指定元素相等的元素,返回下标
                if (o.equals(elementData[i]))
                    return i;
        } 
        // 没有找到,返回空
        return -1;
    }

    说明:从头开始查找与指定元素相等的元素,注意,是可以查找null元素的,意味着ArrayList中可以存放null元素的。与此函数对应的lastIndexOf,表示从尾部开始查找。
   
 public int lastIndexOf(Object o) {
        if (o == null) {
            for (int i = size-1; i >= 0; i--)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = size-1; i >= 0; i--)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值