Java集合之ArrayList源码详解(总结篇)

版权声明: https://blog.csdn.net/m0_37984616/article/details/80713364

集合之ArrayList篇

简介:

ArrayList就是动态数组,实现了List,RandomAccess(快速随机访问),Cloneable(克隆),Serializable(序列化)接口。

public classArrayList<E>

extendsAbstractList<E>

implementsList<E>, RandomAccess, Cloneable, Serializable

ArrayList使用方法及源码解析

1.       ArrayList的构造方法:

      

   // 用指定的初始容量构造一个空列表
   public ArrayList(int initialCapacity) {

        if (initialCapacity > 0) {

            this.elementData = new Object[initialCapacity];

        } elseif (initialCapacity == 0) {

            this.elementData = EMPTY_ELEMENTDATA;

        } else {

            thrownew IllegalArgumentException("Illegal Capacity: "+ initialCapacity);

        }

    }

    // 构造一个初始容量为10的空列表。

    public ArrayList() {

        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;

    }


    // 按照集合的迭代器返回的顺序构造一个包含指定集合元素的列表。

    public ArrayList(Collection<? extends E> c) {

        elementData = c.toArray();

        if ((size = elementData.length) != 0) {

            // c.toArray可能(不正确)不返回Object []

            if (elementData.getClass() != Object[].class)

                elementData = Arrays.copyOf(elementData, size, Object[].class);

        } else {

            //用空数组替换。

            this.elementData = EMPTY_ELEMENTDATA;

        }

    }

通过上面的构造方法可以很清楚的了解,默认情况下,ArrayList会生成一个Object类型的数组;也可以使用第一个构造方法来初始化数组的大小;还可以向ArrayList中传入一个Collection类型的容器,将容器数组化并赋值给这个ArrayList。

举例说明:

package com.boco;

import java.util.ArrayList;

public class Test12 {

    public static void main(String[] args) {
        ArrayList list1 = new ArrayList();
        list1.add("abcd1");
        ArrayList list2 = new ArrayList(12);
        list2.add("abcd2");
        ArrayList list3 = new ArrayList(list1);
        list3.add("abcd3");
        // 后面会讲这个forEach的用法,这里先不讲解
        list3.forEach(System.out::println);
    }

}

输出:
abcd1
abcd3

add方法 

add方法主要有四种:

add(Object e):再末尾添加一个元素

add(int index,Object element):在指定位置添加一个元素

addAll(int index , Collection c):在指定位置添加Collection元素,原位置的元素后移

addAll( Collection c):将Collection元素添加到集合的尾部

2.       add(Ee) 方法

 

public boolean add(E e) {

        ensureCapacityInternal(size + 1);  // 增加  modCount!!

        elementData[size++] = e;

        return true;

}

首先,第一步进行增量操作,让后进入ensureCapacityInternal()方法,该方法主要是判断是否要扩容

private void ensureCapacityInternal(int minCapacity) {

        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));

    }
 

 再看calculateCapacity() 方法,返回值为DEFAULT_CAPACITY(长度是10)size+1大的值;

private staticint calculateCapacity(Object[] elementData, int minCapacity) {

    //如果说数组没有长度的话,需要进行长度赋值

        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {

            return Math.max(DEFAULT_CAPACITY, minCapacity);

        }

        return minCapacity;

    } 

在回头看ensureExplicitCapacity()这个方法

private void ensureExplicitCapacity(int minCapacity) {

        modCount++;

        // overflow-conscious code

        if (minCapacity - elementData.length > 0)

            grow(minCapacity);

    }

modCount++,修改次数加1,(主要作用是迭代器遍历初始时会得到这个数,如果遍历时候这个数改变了,就会抛异常,这就是快速失败策略),之后比较新增之后的长度和现有数组长度,当新增长度大的时候就应该进行扩容操作,咱们接着看grow()方法

   

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

    }

首先,数组会扩容原长度的1.5倍,再比较扩容后的长度和上面返回的值(取默认长度10和size+1中大的)比较,取大的值①的代码;如果长度大于Integer.MAX_VALUE – 8 抛异常②处代码。之后进行数组拷贝,new一个新的长度的数组,赋值。由于数组扩容时候也会浪费一定的时间,所以如果可以确定数组长度可以直接赋值1.5倍的长度。

Arrays的copyOf()方法传回的数组是新的数组对象,所以您改变传回数组中的元素值,也不会影响原来的数组。第二个变量指定要建立的新数组长度。

 3.       add(intindex, E element)方法

 

public void add(intindex, E element) {

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

        System.arraycopy(elementData, index, elementData, index + 1,

                         size - index);

        elementData[index] = element;

        size++;

    }

 首先,rangeCheckForAdd(index) 进行检查index是否大于数组长度或者小于0,是抛异常;

其次,ensureCapacityInternal(size+ 1);判断数组是否需要扩容;

最后,复制index位置之后的元素到index+1处,把element插入到index位置,数组长度加1

 4.       addAll(intindex , Collection c)

 

  public boolean addAll(int index, Collection<? extends E> c) {

                 rangeCheckForAdd(index);
 
                 Object[] a = c.toArray();

                 int numNew = a.length;

                 ensureCapacityInternal(size + numNew);  // Increments modCount
 
                 int numMoved = size - index;

                 if (numMoved > 0)
    
                 System.arraycopy(elementData, index, elementData, index + numNew, numMoved);

                 System.arraycopy(a, 0, elementData, index, numNew);

                 size += numNew;

                 return numNew != 0;

 }

这里面主要的两句

System.arraycopy(elementData, index,elementData, index + numNew,numMoved);

如果不是在末尾插入,先把原数组index位置之后的数据复制到index+Collection.size 长度之后。

System.arraycopy(a, 0, elementData, index,numNew);

把Collection里的元素复制到index位置

 5.       remove(intindex)

删除指定位置的元素

 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;

}

先判断index是否小于0或大于集合长度;

之后判断是否是数组最后一位,不是,复制数组index之后的元素到index位置,最后一位置为null,返回删除元素的值;

 

6.       remove(Objecto)

 

 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;

             }

                 

原理:

判断传入的元素是否是null,是的话遍历数组,找到null删除,调用fastRemove方法复制index之后的元素到index位置;

不是的话遍历数组,找到和o相等的元素,删除方式同上,如果找不到返回false。

 7.       removeAll(Collection<?> c)

public boolean removeAll(Collection<?> c) {

                 Objects.requireNonNull(c);

                 return batchRemove(c, false);

             }               

 private boolean batchRemove(Collection<?> c, boolean complement) {

                 final Object[] elementData = this.elementData;

                 int r = 0, w = 0;

                 boolean modified = false;

                 try {

                     for (; r < size; r++)

                         if (c.contains(elementData[r]) == complement)

                             elementData[w++] = elementData[r];

                 } finally {

                     // Preserve behavioral compatibility with AbstractCollection,

                     // even if c.contains() throws.

                     if (r != size) {

                         System.arraycopy(elementData, r,

                                          elementData, w,

                                          size - r);

                         w += size - r;

                     }

                     if (w != size) {

                         // clear to let GC do its work

                         for (int i = w; i < size; i++)

                             elementData[i] = null;

                         modCount += size - w;

                         size = w;

                         modified = true;

                     }

                 }

                 return modified;

             }

                 

首先对集合中的对象数组进行了一次复制,然后将数组中的元素依次与collection进行对比。try{}块的结果就是得到了一个数组,数组前w位元素或与collection相同(complement=true的时候)或不同(complement=false)。

下面来看finally,先来看第二个if{}代码块,它的作用就是把数组中w以后的元素全部变为null值,让gc来回收。

现在回到finally的第一个if中,看条件(r != size),似乎永远不会满足这个条件吧。上面的for循环一直r++啊,可是别忘了,c.contains(elementData[r])这句话是有可能抛出异常的,如果一旦类型不匹配,就会抛出异常 进入finally中。

这个方法,如果没有删除任何数据,那么将会返回false。

8.       set(intindex ,Object element)

  public E set(int index, E element) {

                 rangeCheck(index);

                 E oldValue = elementData(index);

                 elementData[index] = element;

                 return oldValue;

             }

set方法比较简单,主要是修改数组某个长度的元素,代码也比较简单,这里不再多说。

 9.       get(intindex)

 public E get(int index) {

                 rangeCheck(index);

 

                 return elementData(index);

             }

get方法查找集合某个位置的元素,也比较简单,直接定位到数组位置即可。

 10.     其他的一些方法

 indexoOf(Object o):返回列表中第一次出现指定元素的索引,如果此列表不包含元素,则返回-1;

原理:从0开始遍历数组元素。

lastIndexOf(Object o):返回列表中最后一次出现的指定元素*的索引,如果此列表不包含该元素,则返回-1。

原理:从后往前遍历,同上。

clone():克隆操作,它是浅复制。

toArray():以适当的顺序(从第一个元素到最后一个元素)返回包含此列表中所有元素的数组。

subList():该方法返回的原集合的视图,对返回后的值进行操作,原集合也会改变(后面我们在说这个);生成子列表之后,对原列表进行操作,子列表视图会报异常,所以最好把原列表通过Collections.unmodifiableList方法设成只读状态

 11.     遍历ArrayList方式
 
主要有3种,for循环,foreach,Iterator方式

     

   Iterator it = list.iterator();

        while(it.hasNext()){

            System.out.println(it.next());

        }
  
       // it.forEachRemaining(o -> System.out.println(o));

 12.     其他注意事项

 Arrays.asList(data) 返回值为Arrays工具类中的一个内部类,是一个静私有的内部类,没有添加,删除的方法,只能读操作。

Comparable接口可以作为实现类的默认排序法,Comparator接口是一个类的扩展排序工具。

前者应该比较固定,和一个具体类相绑定,而后者比较灵活,它可以被用于各个需要比较功能的类使用。可以说前者属于 “静态绑定”,而后者可以 “动态绑定”

ArrayList线程不安全,可以使用Collections.synchronizedList(newArrayList());解决多线程并发访问集合时候线程安全问题。

利用Iterator遍历集合时候不要对集合进行修改,原因是集合中有个参数modCount,这个参数是记录集合操作次数的如修改,删除,当遍历时候会判断这个参数和最开始遍历时候的值是否相等,不相等直接抛异常(hashNext==true啥时候才进行判断)。

ArrayList实现了RandomAccess接口(随机存取接口),标志ArrayList是一个可以随机存取的列表,RandomAccess和Cloneable,Serializable一样,都是标志性接口,不需要任何的实现,只是表明具有某种特质。

Java中的foreach是Iterator的变形用法,对于ArrayList而言,需要创建一个迭代器容器,屏蔽内部细节,对外提供hasNext,next等方法 ,ArrayList实现RandomAccess接口,表明元素之间没有联系,但迭代器需要建立一个强制互相知晓的关系,如上一个元素可以判断是否还有下一个元素,这就是foreach遍历耗时原因,所以ArrayList直接用get(i)遍历最快,而LinkedList用foreach遍历较好。

总结:

1.     ArrayList是基于数组实现的,是一个动态数组,其容量能自动增长,类似于C语言中的动态申请内存,动态增长内存。

2.     ArrayList不是线程安全的,只能用在单线程环境下,多线程环境下可以考虑用Collections.synchronizedList(List l)函数返回一个线程安全的ArrayList类,也可以使用concurrent并发包下的CopyOnWriteArrayList类。

3.     ArrayList实现了Serializable接口,因此它支持序列化,能够通过序列化传输,实现了RandomAccess接口,支持快速随机访问,实际上就是通过下标序号进行快速访问,实现了Cloneable接口,能被克隆。

4.     由于是数组结构,所以ArrayList具有增删慢,查找快的特点。

Java中ArrayList和LinkedList区别


1.ArrayList是实现了基于动态数组的数据结构,LinkedList基于链表的数据结构。 
2.对于随机访问get和set,ArrayList觉得优于LinkedList,因为LinkedList要移动指针。 
3.对于新增和删除操作add和remove,LinedList比较占优势,因为ArrayList要移动数据

Serializable作用?

  • 想把的内存中的对象状态保存到一个文件中或者数据库中时候
  • 想把对象通过网络进行传播的时候

猜你喜欢

转载自blog.csdn.net/m0_37984616/article/details/80713364