java集合框架--------ArrayList

版权声明:原创 https://blog.csdn.net/rambler_designer/article/details/89478808

前言:

java中我们经常会用到List,Map,Set,Vector这些集合框架,却没有认真学习过,借这篇博客,认真复习一下

ArrayList--->实现了List接口的一个常用类

类的声明:

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

看一下ArrayList源码中如何定义的ArrayList

Resizable-array implementation of the List interface.  Implements
all optional list operations, and permits all elements, including
null.  In addition to implementing theList interface,
this class provides methods to manipulate the size of the array that is
used internally to store the list.  (This class is roughly equivalent to
Vector, except that it is unsynchronized.)

 ArrayList是实现了List接口的可变数组,可以存放任何类型的数据,包括null值存在,
 这个类除了实现List接口,还提供管理数组大小的方法
 粗略的说,除了ArrayList不是线程同步的,和Vector基本相同

其实ArrayList就是一种数据结构,下面是我提炼的ArrayList的结构

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
   /**
     * 实现序列化接口(Seriablizable)必须默认声明一个long类型serialVersionUID
     */
    private static final long serialVersionUID = 8683452581122892189L;

    /**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     * 集合的数据元素
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * The size of the ArrayList (the number of elements it contains).
     * 集合的长度
     * @serial
     */
    private int size;

    //下面是构造方法以及其他方法
}

也就是说所谓的ArrayList,实质上也就是两部分组成的,一个是存储集合元素的一个对象数组elementData[],另一个就是这个集合的长度,或者说大小,可以通过.size()方法获取集合的长度

除此之外,ArrayList还定了一个几个变量

//  默认的集合的容量为10
private static final int DEFAULT_CAPACITY = 10;

//  当创建一个初识容量的ArrayLIst时,当输入的值为0时,数据元素由这个集合赋值
private static final Object[] EMPTY_ELEMENTDATA = {};

//  采用默认的空的构造方法时,数据元素由这个集合赋值
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

构造方法:

ArrayList共有三种构造方法,下面逐个分析

  • 创建一个长度为0的一个ArrayList

源码中定义

/**
  * Shared empty array instance used for default sized empty instances. We
  * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
  * first element is added.
  */
 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

/**
  * Constructs an empty list with an initial capacity of ten.
  */
 public ArrayList() {
     this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
 }

首先ArrayList定义了一个DEFAULTCAPACITY_EMPTY_ELEMENTDATA对象数组,且初始值为{},也就是空,所以创建的集合的长度为这个对象数组的长度,也就是0 

  • 根据Collection创建一个ArrayList

源码定义

/**
  * Constructs a list containing the elements of the specified
  * collection, in the order they are returned by the collection's
  * iterator.
  *
  * @param c the collection whose elements are to be placed into this list
  * @throws NullPointerException if the specified collection is null
  */
 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;
     }
 }

 首先Collection集合调用toArray方法返回一个对象数组,并赋值给ArrayList的元素集合elementData,然后将对象数组elementData的长度赋值给ArrayList的size属性,并判断size是否为0,如果为0,说明传入的Collection的长度为0,name直接退出if判断,执行下面的,将空的对象数组赋值给elementData,,如果长度不为空,此时elementData应该是Collection通过toArray方法转化成的Object数组

思考一个问题,既然toArray方法返回的是Object[] ,name条件elementData.getClass() != Object[].class永远不成立,为啥要考虑这种情况,眼大漏神,,没看到备注那句话,toArray方法也许不一定返回Object[],具体看了一下Collection类的toArray方法是被AbstractCollection类实现,看了一下API,这个方法确实返回Object[],具体原因,日后在添加吧

下面是个例子

Collection<String> collection = new LinkedList<String>();
collection.add("张三");
collection.add("李四");
ArrayList<String> arrayList = new ArrayList<String>(collection);
  • 创建一个指定长度的可变数组

源码定义

 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大于0,直接创建一个指定容量的对象数组即可

如果initialCapacity等于0 ,将空的对象数组赋值给elementData

如果initialCapacity小于0,则抛出IllegalArgumentException异常

ArrayList<String> arrayList = new ArrayList<String>(5);

创建的指定长度虽然为5,但是此时如果未插入数据而直接输出arrayList的长度,结果为0,而如果插入6组数据,在输出ArrayList的长度,则为6,也就是说,虽然以5创建的数组,但是实质上还是可变长的数组,因为在add方法中扩充了容量,详情见下面add的源码分析

常用方法

一、添加元素

  • public boolean add(E e)

当创建了一个空的ArrayList时,在向这个集合中添加元素是,函数调用关系如下

add() ---> ensureCapacityInternal() --> ensureExplicitCapacity( calculateCapacity() ) --> grow()

紫色部分是calculateCapacity()方法的返回值作为参数,大家根据这个函数调用关系看一下源码,我弄在一块,方便大家看

直接在备注中针对源码讲解吧

ArrayList<String> list = new ArrayList<String>();
list.add("Something");

/*
 * ArrayList的add方法,在这个方法中首先调用ensureCapacityInternal()方法,将集合当前大小加一作为参数,也就是0+1=1
 * 【先看完底下代码分析,在看这里】
 * 当ensureCapacityInternal方法执行结束后,也就是扩充完ArrayList的容量之后,进行赋值操作
 * elementData[size++],size此时为0,也就是elementData[0] =e;同时将size++,变为1
 * 完成add操作,返回true
 */
public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

/*
 * 此时参数minCapacity的值为size+1,也就是传递过来的1,,然后调用ensureExplicitCapacity
 * 首先调用calculateCapacity,然后将返回值作为参数
 * 因此先分析一下calculateCapacity这个方法
 */
private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

/*
 * 将数据元素elementData和minCapacity作为参数,elementData为空对象数组,minCapacity是1
 * 第一层判断,由于ArrayList是采用的默认的空的构造方法
 * 因此此时elementData就是DEFAULTCAPACITY_EMPTY_ELEMENTDATA
 * 不太清楚的可以看一下上面构造方法中默认的空的构造方法的分析
 * 因此返回的是Math.max()这个方法,也就是返回的DEFAULT_CAPACITY,和minCapacity的最大值
 * 再来看一下这个DEFAULT_CAPACITY,发现默认创建的数组的长度为10,因此返回的值为10
 * 【之前看API还以为写错了,怎么创建的空的长度为10,看过源码才清楚】
 */
private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
}

/*
 * 在ensureCapacityInternal方法中调用过这个方法,此时minCapacity应该是calculateCapacity返回的10
 * modCount类似于下标,初识为0,此时变为1
 * protected transient int modCount = 0;这是源码中声明的,作为依据
 * minCapacity为10,elementData还是空的对象数组,因此长度为0,满足if条件
 * minCapacity - elementData.length=10>0因此调用grow方法,参数是10
 */
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

/*
 * minCapacity为10
 * 定义两个参数,oldCapacity初识值为集合的长度也就是0
 *                      newCapacity初始值为0+(0>>1),>>表示右移,也就是除以二,因此值也是0
 * newCapacity - minCapacity = -10,满足第一个if条件,因此newCapacity此时为10
 * newCapacity - MAX_ARRAY_SIZE不满足,因此MAX_ARRAY_SIZE是最大整数-8,总之是一个很大的数
 * 调用Arrays类的copyOf方法给elementData赋值, 但elementData此时为空数组
 * 默认值为null,也就是说此时elementData是一个容量为10,元素均为null的一个对象数组
 */
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);
}

 

  • public void add(int index,E element)

ArrayList的索引从0开始,当前add方法,是将元素插入到index索引处,同时将原来元素全部向后偏移一个单位,举个例子

import java.util.ArrayList;
import java.util.List;

public class ArrayListDemo {
	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		list.add("第一个");
		list.add("第二个");
		list.add("第三个");
		System.out.println("增加前:");
		show(list);
		list.add(1,"新节点");
		System.out.println("增加后:");
		show(list);
	}
	/**
	 * 定义一个遍历显示的方法
	 */
	public static void show(List<String> list) {
		for(int i=0; i<list.size(); i++) {
			System.out.println("        "+list.get(i));
		}
	}
}

首先创建一个List集合,然后通过普通add方法,插入三个节点,(先备注那一句),然后测试效果,定义了一个静态方法,遍历显示,结果如下

增加前:
        第一个
        第二个
        第三个
增加后:
        第一个
        新节点
        第二个
        第三个

List集合从0开始,索引为1的正好为"第二个",因此"新节点",会插入到"第一个"和"第二个"之间

将指定的元素插入此列表中的指定位置。向右移动当前位于该位置的元素(如果有)以及所有后续元素(将其索引加 1)

  • public boolean addAll(Collection<? extends E> collection)

按照指定 collection 的迭代器所返回的元素顺序,将该 collection 中的所有元素添加到此列表的尾部。如果正在进行此操作时修改指定的 collection ,那么此操作的行为是不确定的。(这意味着如果指定的 collection 是此列表且此列表是非空的,那么此调用的行为是不确定的).这是API中进行的解释

通俗点来说就是,将一个Collection(Collection的子类,如ArrayList或者其他实现类的集合)追加到ArrayList集合的后面,并且在这个过程中不可修改将作为参数传递过来的Collection,

import java.util.ArrayList;
import java.util.List;

public class ArrayListDemo {
	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		list.add("第一个");
		list.add("第二个");
		list.add("第三个");
		System.out.println("添加前:");
		show(list);
		List<String> temp = new ArrayList<String>();
		temp.add("添加项1");
		temp.add("添加项2");
		System.out.println("操作的结果"+list.addAll(temp));
		System.out.println("添加后:");
		show(list);
	}
	/**
	 * 定义一个遍历显示的方法
	 */
	public static void show(List<String> list) {
		for(int i=0; i<list.size(); i++) {
			System.out.println("        "+list.get(i));
		}
	}
}

运行结果

添加前:
        第一个
        第二个
        第三个
操作的结果true
添加后:
        第一个
        第二个
        第三个
        添加项1
        添加项2

  • public boolean addAll(int index,Collection<? extends E> collection)

从指定的位置开始,将指定 collection 中的所有元素插入到此列表中。向右移动当前位于该位置的元素(如果有)以及所有后续元素(增加其索引)。新元素将按照指定 collection 的迭代器所返回的元素顺序出现在列表中

其中index是插入集合中首个元素的索引,collection是插入的集合

System.out.println("操作的结果"+list.addAll(1,temp));

这个和上一个addAll差不多,只需要将参数修改一下,因此不给出全部代码了

以下是运行结果:

添加前:
        第一个
        第二个
        第三个
操作的结果true
添加后:
        第一个
        添加项1
        添加项2
        第二个
        第三个

二、移除元素

  • public E remove(int index)

从List集合中移除指定索引的元素,并返回移除的元素,例子

import java.util.ArrayList;
import java.util.List;

public class ArrayListDemo {
	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		list.add("第一个");
		list.add("第二个");
		list.add("第三个");
		System.out.println("移除前:");
		show(list);
		String string = list.remove(2);
		System.out.println("移除后:");
		show(list);
		System.out.println("移除节点: " + string);
	}

	/**
	 * 定义一个遍历显示的方法
	 */
	public static void show(List<String> list) {
		for (int i = 0; i < list.size(); i++) {
			System.out.println("        "+list.get(i));
		}
	}
}

结果:

移除前:
        第一个
        第二个
        第三个
移除后:
        第一个
        第二个
移除节点: 第三个

实现原理:先将索引位置处元素移除,然后索引位置后面的所有元素索引减小1

  • public boolean remove(Object o)
    

移除此列表中首次出现的指定元素(如果存在)。如果列表不包含此元素,则列表不做改动。更确切地讲,移除满足 (o==null ? get(i)==null : o.equals(get(i))) 的最低索引的元素(如果存在此类元素)。如果列表中包含指定的元素,则返回 true(或者等同于这种情况:如果列表由于调用而发生更改,则返回 true),引用自API,借此复习一下三目运算符

三目运算符

如a > b ? a : b这种形式,如果a=2,b=1,则这个表达式结果为a,也就是2,反之则为b,也就是1

在如,o==null ? get(i)==null : o.equals(get(i))这种形式

首先判断o是不是null,如果是null,则返回为get(i)==null,如果不是null,则返回o.equals(get(i))

也就是说如果你移除null元素,就会去遍历所有元素,如果匹配到null元素,则移除这个元素,并且返回true

如果你移除的节点不是null,则会遍历所有元素,待删除节点调用equal(Object类的方法)判断是否与集合中元素相同,如果相同则移除节点,同时返回true

具体看这个例子

import java.util.ArrayList;
import java.util.List;

public class ArrayListDemo {
	public static void main(String[] args) {
		List<String> list = new ArrayList<String>();
		list.add("第一个");
		list.add("第二个");
		list.add("第三个");
		list.add(null);
		System.out.println("移除前:");
		show(list);
		System.out.println("正常移除: "+list.remove("第一个"));
		System.out.println("移除空: "+list.remove(null));
		System.out.println("移除空: "+list.remove("第"));
		System.out.println("移除后:");
		show(list);
	}
	/**
	 * 定义一个遍历显示的方法
	 */
	public static void show(List<String> list) {
		for(int i=0; i<list.size(); i++) {
			System.out.println("        "+list.get(i));
		}
	}
}

运行结果:

移除前:
        第一个
        第二个
        第三个
        null
正常移除: true
移除空: true
移除空: false
增加后:
        第二个
        第三个

  • public void clear()

移除列表中的所有元素,方法返回后,列表将为空

这个比较简单,就不演示了,大家看看代码吧

List<String> list = new ArrayList<String>();
list.add("第一个");
list.add("第二个");
list.add("第三个");
list.clear();
  • protected void removeRange(int fromIndex,int toIndex)

移除列表中索引在 fromIndex(包括)和 toIndex(不包括)之间的所有元素。向左移动所有后续元素(减小其索引)。此调用将列表缩短了 (toIndex - fromIndex) 个元素。(如果 toIndex==fromIndex,则此操作无效。)

方法为啥是protected ???? 也就是说只有其子类才能访问这个方法,这是为啥??

日后补充吧

猜你喜欢

转载自blog.csdn.net/rambler_designer/article/details/89478808
今日推荐