Java学习之ArrayList原理剖析

        ArrayList底层是基于数组实现的,其封装的各种方法:Add、remove、get、set等,其本质就是对数组的基本操作。

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

        ArrayList继承自AbstractList类,实现了RandomAccess、Cloneable、Serializable接口。其实查看RandomAccess接口: 

public interface RandomAccess {
}

       惊奇的发现里面是空的,那这样写的意义是什么?它只是作为一个标识,表示ArrayList支持随机访问操作。正如上所述,ArrayList是基于数组实现的,而数组天然的支持随机访问,时间复杂度是O(1),故而RandomAccess接口只是标识,并不是说因为ArrayList实现了随机访问接口才具有随机访问功能。

较为重要的两个属性:

//用于存储元素
transient Object[] elementData;
//ArrayList的大小
private int size;

       后面的方法基本都是在对elementData和size两个属性进行操作,elementData是一个存储Object对象的数组,从这里可以看出来ArrayList就是Object数组,与一般的数组的区别就是:

        1. ArrayList存储对象可以是任意类型的元素,例如一个类的对象;而一般的数组,如 int[] a,只能是存储int类型的元素。

        2. 一般的数组大小固定,而ArrayList支持动态扩展,可以在Add 元素的过程中自动增加。

ArrayList的三个构造方法:

    //指定元素个数的构造
    //指定大小若为0,集合为空
    //指定大小若小于0,则抛出异常
    //否则正常构造
    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);
        }
    }

   //无参构造,默认集合大小为10,元素为空
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

    //Collection的构造,以集合c为基础构造ArrayList对象,将c中的元素复制给新的ArrayList
    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;
        }
    }

一些常调用的方法:

Add(往list中添加元素):

   //将新元素添加在末尾
    public boolean add(E e) {
        //判断数组大小是否充足,不够的话进行扩充
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }

   //将新元素添加到指定位置
    public void add(int index, E element) {
        rangeCheckForAdd(index);

        ensureCapacityInternal(size + 1);  // Increments modCount!!

        //进行大量元素移动操作适合使用System.arraycopy,其底层调用C语言的元素移动的函数
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

       与一般数组添加元素操作时一样,先判断一下数组大小是否足够,然后插入到相应位置。如若插入到数组末尾,只需将元素放入后,size+1;如若插入到指定位置,需要将其后到元素都往后挪一个位置后再插入当前元素。由此可以知道,ArrayList做插入、删除时,时间复杂度为O(n),不如LinkedList,这就是基本的数组与链表之间的优缺点。

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);
        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;
    }


    //自我调用方法,快速删除
    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
    }

      类比于一般数组,要删除一个指定下标元素时,只需将其后的元素依次往前移动一个位置,让最后一个元素为空;要删除指定元素时,需要从头到尾便利一遍,删除与之相等到元素,删除操作也是将其后元素依次往前移动一个位置。这里还写了一个fastRemove,与remove相比只是少了越界判断,因为在调用fastRemove方法时已经确保了一定存在,不会发生越界异常。

      其他方法类比于一般数组思考即可!!!

     动态增长容量的函数:

    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        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:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

         newCapacity = oldCapacity + (oldCapacity >> 1); 将空间扩展到原大小的1.5倍。其扩展原理就是重新开辟一块newCapacity大小的存储空间,将原elementData中的元素复制到新的空间中,elementData指向新的空间地址。这和它底层数据结构有关,我们都知道数组是顺序存储的,首先要确保有足够的连续存储单元。此处和链表就不同了,链表不要求顺序存储,只是需要一个指针指向下一个存储单元,当然,由于指针的存储,链表存储所浪费的存储单元就多了。

一些常见问题: 

Arraylist 与 LinkedList 的区别:

          其本质就是数据结构中 顺序存储 和 链式存储 之间的关系。

           1.  底层数据结构:ArrayList底层是Object数组;LinkedList 底层是双向链表数据结构。

           2. 插入、删除时间复杂度:ArrayList底层是数组,插入、删除操作时间复杂度受元素位置的影响,如若插入到末尾add(E e)时间复杂度为O(1),如若插入到指定位置add(int index, E element)时间复杂度为O(n),因为需要大量的移动元素; 而LinkedList底层是链表,所以插入、删除时间复杂度不受元素位置的影响,时间复杂度为O(1)。

           3. ArrayList支持随机访问,通过下标就能访问元素,时间复杂度为O(1);而LinkedList需要从头到尾遍历一遍链表才能找到元素位置,时间复杂度为O(n)。

           4. ArrayList空间不足时支持动态扩展,扩大大原来的1.5倍;而LinkedList没有扩展的概念。

           5. 内存空间使用情况:ArrayList会在末尾预留一定的空间,造成空间浪费;而LinkedList每个节点都需要存储其前趋和后继指针,所浪费的空间多于ArrayList。

           6. 同: Arraylist 和 LinkedList 都不保证线程安全。(线程安全:学过操作系统都知道,多线程同步可能会造成死锁,Arraylist和LinkedList并没有提供避免死锁的机制。)

ArrayList 与 Vector 区别 :

           1. ArrayList和Vector其实质都是Object数组,空间不足时都支持动态增长,ArrayList增长为原来的1.5倍,而Vector增长为原来的2倍。

           2. ArrayList不保证线程安全;而Vector提供同步机制,保证线程安全,但时间开销大,因为每次加锁解锁需要耗费时间。

           3. Vector现在已经不常用了。

ArrayList常用三种遍历方法:

           1. 普通的for循环。调用ArrayList.get方法,for循环依次读出。

           2. foreach方法。

//没有用泛型,需要进行强制类型转化
//通过for each方法访问集合元素
//Object取出遍历对象
public void testForEach() {
	for(Object obj : studentList) {
		Student stu = (Student) obj;
		System.out.println(stu.id+":"+stu.name);
	}
}

//泛型 For Each写法:
public void testForEach() {
	for(Student stu :students) {
		System.out.println(stu.id+":"+ stu.name);
	}
}

         3.迭代器访问方法。

通过迭代器来遍历List
public void testIterator() {
	Iterator it = studentList.iterator();
	while (it.hasNext()) {
		Student stu = (Student) it.next();
		System.out.println(stu.id+":"+stu.name);
	}
}
  • 实现RandomAccess接口的list,优先选择普通的for循环,其次是foreach。
  • 未实现RandomAccess接口的list,优先选择iterator遍历。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值