Java学习者论坛

 找回密码
 立即注册

QQ登录

只需一步,快速开始

手机号码,快捷登录

恭喜Java学习者论坛(https://www.javaxxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,购买链接:点击进入购买VIP会员
JAVA高级面试进阶视频教程Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程

Go语言视频零基础入门到精通

Java架构师3期(课件+源码)

Java开发全终端实战租房项目视频教程

SpringBoot2.X入门到高级使用教程

大数据培训第六期全套视频教程

深度学习(CNN RNN GAN)算法原理

Java亿级流量电商系统视频教程

互联网架构师视频教程

年薪50万Spark2.0从入门到精通

年薪50万!人工智能学习路线教程

年薪50万!大数据从入门到精通学习路线年薪50万!机器学习入门到精通视频教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程 MySQL入门到精通教程
查看: 290|回复: 0

[集合学习]ArrayList源码分析

[复制链接]
  • TA的每日心情
    开心
    2021-3-12 23:18
  • 签到天数: 2 天

    [LV.1]初来乍到

    发表于 2014-10-28 23:55:22 | 显示全部楼层 |阅读模式

    终于可以开始分析第一个具体的类,我们对ArrayList应该非常面熟了,不管你是否了解它是如何实现的, 但是我们到处都使用到它。
    声明如下:
    public class ArrayList extends AbstractList implements List, RandomAccess,       Cloneable, java.io.Serializable

    有关AbstractList:
          http://blog.csdn.net/treeroot/arcHive/2004/09/14/104743.aspx
    有关List:
    http://blog.csdn.net/treeroot/archive/2004/09/14/104638.aspx
    有关RandomAccess:
         http://blog.csdn.net/treeroot/archive/2004/09/14/104538.aspx
    有关Cloneable :
         http://blog.csdn.net/treeroot/archive/2004/09/07/96936.aspx
    有关java.io.Serializeable: 主要用于对象序列化。  
        private static final long serialVersionUID = 8683452581122892189L;
    版本控制
        private transient Object elementData[];
    内部结构,原来就是一个数组,这里不让它串行化。
        private int size;
    该列表的大小,也就是元素个数。
        public ArrayList(int initialCapacity) {
    super();
    if (initialCapacity < 0)
    throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);
    this.elementData = new Object[initialCapacity];
    }
    构造函数,初始化内部数组为指定大小,注意列表是空的。
        public ArrayList() {
    this(10);
    }
    默认初始话内部数组大小为10,为什么是10是没有理由的,可能比较合适吧。
        public ArrayList(Collection c) {
    size = c.size();
        // Allow 10% room for growth
           elementData =
              new Object[(int)Math.min((size*110L)/100,Integer.MAX_VALUE)];
    c.toArray(elementData);
    }
    通过一个集合来初始话该ArrayList,内部数组申请的空间比c大,主要是为了提高效率。注意到c.toArray(elementData) 方法的调用,这里肯定不会生成新的数组,如果用elementData=c.toArray()效率就差不少了。另外这里调用了Math的静态
    方法min来获得较小值。  
        public void trimToSize() {
    modCount++;
    int oldCapacity = elementData.length;
    if (size < oldCapacity) {
    Object oldData[] = elementData;
    elementData = new Object[size];
    System.arraycopy(oldData, 0, elementData, 0, size);
    }
    }
    这个方法去掉多余的空间,使内部数组的大小刚好等于ArrayList的size(),这个方法需要重新分配空间,而已需要一个数组拷贝过程(arraycopy是一个native方法,用的比较多),一般情况下这个方法很少被调用。
       
      
       
      
       
       

         
       

         
       
      
      
      
       
      
       public void ensureCapacity(int minCapacity) {
    modCount++;
    int oldCapacity = elementData.length;
    if (minCapacity > oldCapacity) {
    Object oldData[] = elementData;
    int newCapacity = (oldCapacity * 3)/2 + 1;
    if (newCapacity < minCapacity)
    newCapacity = minCapacity;
    elementData = new Object[newCapacity];
    System.arraycopy(oldData, 0, elementData, 0, size);
    }
    }
    这个方法来扩大ArrayList的容量,使它至少能容纳minCapacity个元素,如果数组容量大于该值,什么也不做。否则按某个
    算法(1.5倍加1)增加,如果不够minCapacity大的话,就设置为minCapacity。这个方法在add和addAll方法中都要调用,
    这里为什么设置为public呢?因为每次重新分配空间都是比较消耗时间的(new操作还要arrayCopy),如果能预计可能的大小
    的话,这个方法就有比较的灵活性。虽然该扩容算发已经比较好,但是还是可以通过自己的控制提高效率,这个方法为程序员
    带来的方便。
    eg1:
    ArrayList al=new ArrayList();
    for(int i=0;i<100;i++){
    Object obj=new Object();
    al.add(obj);
    }
    eg2(更高效):
    ArrayList al=new ArrayList(100);
    for(int i=0;i<100;i++){
    Object obj=new Object();
    al.add(obj);
    }
    或者
    ArrayList al=new ArrayList();
    al.ensureCapacity(100);
    for(int i=0;i<100;i++){
    Object obj=new Object();
    al.add(obj);
    } public int size() {
    return size;
    }
    返回大小 public boolean isEmpty() {
    return size == 0;
    }
    是否为空 public boolean contains(Object elem) {
    return indexOf(elem) >= 0;
    }
    是否包含指定元素,调用的是indexOf()方法。 public int indexOf(Object elem) {
    if (elem == null) {
    for (int i = 0; i < size; i++)
    if (elementData==null)
    return i;
    } else {
    for (int i = 0; i < size; i++)
    if (elem.equals(elementData))
    return i;
    }
    return -1;
    }
    这个方法遍历列表(数组0..size-1) public int lastIndexOf(Object elem) {
    if (elem == null) {
    for (int i = size-1; i >= 0; i--)
    if (elementData==null)
    return i;
    } else {
    for (int i = size-1; i >= 0; i--)
    if (elem.equals(elementData))
    return i;
    }
    return -1;
    }
    这个方法和上面的基本一样,顺序不一样而已。 public Object clone() {
    try {
    ArrayList v = (ArrayList)super.clone();
    v.elementData = new Object[size];
    System.arraycopy(elementData, 0, v.elementData, 0, size);
    v.modCount = 0;
    return v;
    } catch (CloneNotSupportedException e) {
    // this shouldn"t happen, since we are Cloneable
    throw new InternalError();
    }
    }
    覆盖Object中的clone()方法,实现clone,注意这里是一个浅拷贝,两个ArrayList中的数组中的元素
    是相同的,因为System.arraycopy就是浅拷贝。 public Object[] toArray() {
    Object[] result = new Object[size];
    System.arraycopy(elementData, 0, result, 0, size);
    return result;
    }
    返回ArrayList元素的一个数组,注意这里虽然生成了一个新的数组,但是数组元素和集合中的元素是共享的,
    Collection接口中说这个是安全的是不严格的,下面的例子演示了这个效果。
    eg1:
    ArrayList al=new ArrayList();
    al.add(new StringBuffer("hello"));
    Object[] a=al.toArray();
    StringBuffer sb=(StringBuffer)a[0];
    sb.append("changed"); //改变数组元素同样也改变了原来的ArrayList中的元素
    System.out.println(al.get(0));
    这里不要用String来代替StringBuffer,因为String是常量。 public Object[] toArray(Object a[]) {
    if (a.length < size)
    a = (Object[])java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size);
    System.arraycopy(elementData, 0, a, 0, size);
    if (a.length > size)
    a[size] = null;
    return a;
    }
    这个方法有可能不需要生成新的数组,注意到如果数组a容量过大,只在size处设置为null。 public Object get(int index) {
    RangeCheck(index);
    return elementData[index];
    }
    可以看随机访问效率是很高的,和数组的索引访问是一样的,方式设计到索引值都会先检查。 public Object set(int index, Object element) {
    RangeCheck(index);
    Object oldValue = elementData[index];
    elementData[index] = element;
    return oldValue;
    }
    更新指定位置的值,并访问原来的值。 public boolean add(Object o) {
    ensureCapacity(size + 1); // Increments modCount!!
    elementData[size++] = o;
    return true;
    }
    添加一个新的元素到末尾,前面说道新增方法都要先调用ensureCapacity方法,这里没有调用add(size,o)方法。 public void add(int index, Object element) {
    if (index > size || index < 0)
    throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
    ensureCapacity(size+1); // Increments modCount!!
    System.arraycopy(elementData, index, elementData, index + 1,size - index);
    elementData[index] = element;
    size++;
    }
    在指定位置插入元素,指定元素和后面的元素后移。 public Object remove(int index) {
    RangeCheck(index);
    modCount++;
    Object oldValue = elementData[index];
    int numMoved = size - index - 1;
    if (numMoved > 0)
    System.arraycopy(elementData, index+1, elementData, index,numMoved);
    elementData[--size] = null; // Let gc do its work
    return oldValue;
    }
    删除指定位置的元素,后面的元素前移,返回被删除的元素,这里要注意的elementData[--size]=null这条语句,
    如果不这样的话,就有可能造成内存泄露,因为该对象其实已经没有用,但是内部还有一个它的引用,如果不设置
    为null,GC就无法回收这个空间,积累多了就有可能造成内存泄露,这里只说有可能,而不是一定。 public void clear() {
    modCount++;
    // Let gc do its work
    for (int i = 0; i < size; i++)
    elementData = null;
    size = 0;
    }
    这段代码比较高效,这里也必须设置为空引用,理由同上。 public boolean addAll(Collection c) {
    modCount++;
    int numNew = c.size();
    ensureCapacity(size + numNew);
    Iterator e = c.iterator();
    for (int i=0; i< NUMNEW; i++)
    elementData[size++] = e.next();
    return numNew != 0;
    }
    添加集合c中的元素到ArrayList的末尾,添加成功返回true,如果集合c为空,返回false。 public boolean addAll(int index, Collection c) {
    if (index > size || index < 0)
    throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
    int numNew = c.size();
    ensureCapacity(size + numNew); // Increments modCount!!
    int numMoved = size - index;
    if (numMoved > 0)
    System.arraycopy(elementData, index, elementData, index + numNew,numMoved);
    Iterator e = c.iterator();
    for (int i=0; i< NUMNEW; i++) elementData[index++] = e.next();
    size += numNew;
    return numNew != 0;
    }
    在指定位置插入集合中的所有元素,和上面一个方法基本差不多,指定位置元素和以后的都要后移。 protected void removeRange(int fromIndex, int toIndex) {
    modCount++;
    int numMoved = size - toIndex;
    System.arraycopy(elementData, toIndex, elementData, fromIndex,numMoved);
    // Let gc do its work
    int newSize = size - (toIndex-fromIndex);
    while (size != newSize)
    elementData[--size] = null;
    }
    这是一个保护方法,删除指定位置fromIndex到toIndex的元素,包括前面不包括后面。 private void RangeCheck(int index) {
    if (index >= size || index < 0)
    throw new IndexOutOfBoundsException("Index: "+index+", Size: "+size);
    }
    不用解释。 private synchronized void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException{
    // Write out element count, and any hidden stuff
    s.defaultWriteObject();
    // Write out array length
    s.writeInt(elementData.length);
    // Write out all elements in the proper order.
    for (int i=0; i< SIZE; i++) s.writeObject(elementData);
    }
    private synchronized void readObject(java.io.ObjectInputStream s) throws java.io.IOException,ClassNotFoundException {
    // Read in size, and any hidden stuff
    s.defaultReadObject();
    // Read in array length and allocate array
    int arrayLength = s.readInt();
    elementData = new Object[arrayLength];
    // Read in all elements in the proper order.
    for (int i=0; i< SIZE; i++)
       elementData = s.readObject();
    }
    这两个方法是为了实现串行化而写的,这里要注意几点:
    1.这两个方法并不是java.io.Serializable中定义的方法,Serializable只是标记接口。
    2.串行化对象的时候会先检查该对象是否实现了这两个方法,如果有实现就调用他们,如果没有的化就调用默认方法。
    至于为什么可以调用该对象的private方法我也不清楚,这个问题确实比较奇怪,如果可以访问对象的private方法,那
    也太不安全了。
    3.因为elementData声明为transient,所以必须手动串行化化。

    总结:
    1.ArrayList的方法都没有同步,所以在多线程中是不安全的,必须自己同步,而且ArrayList很多方法声明对于多线程操作
    都没有规定,就是说结果不可预料。
    2.toArray()方法返回的是和原列表相同的对象,也就是说:
    arrayList.toArray()[0]==arrayList.get(0)返回的是true(假定arrayList不空)。
    3.clone()方法是一个浅拷贝。
    4.可以通过自己调用ensureCapacity()提高效率。
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|Java学习者论坛 ( 声明:本站资料整理自互联网,用于Java学习者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-6-3 23:56 , Processed in 0.382216 second(s), 46 queries .

    Powered by Discuz! X3.4

    © 2001-2017 Comsenz Inc.

    快速回复 返回顶部 返回列表