Java集合 List接口

List接口

特性

  1. List接口是Collection接口的子接口
  2. List集合类中元素有序(即添加顺序与取出顺序一致),且可以重复!
  3. List集合汇总的每个元素都有其对应的顺序索引,即支持索引
  4. List容器中的每个元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素

一些接口实现类

  1. AbstractList
  2. AbstractSequentialList
  3. ArrayList
  4. AttributeList
  5. CopyOnWriteArrayList
  6. LinkedList
  7. RoleList
  8. RoleUnreolvedList
  9. Stack
  10. Vector

常用方法

  1. add(int i,Object e)在i位置插入e元素
  2. boolean addAll(int i,Collection es)在i位置开始将es所有元素插入
  3. Object get(int i)获取指定i位置的元素
  4. int indexOf(Object o)返回o在集合中首次出现的位置
  5. int lastOf(Object o)返回o在集合中末次出现的位置
  6. Object remove(int i)移除指定i位置的元素,并返回该元素
  7. Object set(int i,Object e)将i位置的元素替换为e
  8. List subList(int from,int to)返回[from,to)区间的子集合

遍历方式

  1. 使用Iterator
  2. 使用增强for循环
  3. 使用普通for循环

ArrayList

注意事项

  1. ArrayList可以加入null,且可以加入多个
  2. ArrayLIst是由数组来实现数据存储的
  3. ArrayList基本等同于Vector,除了ArrayList是线程不安全(执行效率高),多线程下不建议使用ArrayList()
  4. 修改DEBUG的设置得以看见完整的数据

底层结构与源码分析

  1. ArrayList中维护了一个Object类型的数组elementData
transient Object[] elementData;//transient 表示瞬间,短暂的,表示该属性不会被序列化
  1. 当创建ArrayList对象时,如果使用的是无惨构造器,则初始化elementData容量为0,第1次添加,则扩容elementData为10,如果需要再次扩容,则扩容elementData为1.5倍
public ArrayList(){//无参构造初始化为一个空数组
    this.elementData = DEFAULTCAPACTIT_EMPTY_ELEMENTDATA;
}

//空数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

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

private int size;//size初始会为0

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));//minCapacity=1
}

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//如果为无参构造生成的空数组
        return Math.max(DEFAULT_CAPACITY, minCapacity);//max(10,1)->10
    }
    return minCapacity;//如果不是无参构造的空数组直接返回size+1
}

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

private void ensureExplicitCapacity(int minCapacity) {//minCapacity=10
    modCount++;//0++->1

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)//10-0>0 符合
        grow(minCapacity);
}

protected transient int modCount = 0;//列表在结构上被修改的次数 这里发现只要调用add就会导致modCount++

private void grow(int minCapacity) {//minCapacity=10
    // overflow-conscious code
    int oldCapacity = elementData.length;//oldCapacity=0
    int newCapacity = oldCapacity + (oldCapacity >> 1);//newCapacity=0+0/2=0
    if (newCapacity - minCapacity < 0)//0-10<0 符合
        newCapacity = minCapacity;//newCapacity=10
    if (newCapacity - MAX_ARRAY_SIZE > 0)//10-Max<0 不符合
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);//在保证数据不变的基础上 将数组扩容到10
}

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;//最大数组容量 挺大的

public boolean add(E e) {
    ensureCapacityInternal(size + 1); //扩容完毕 0—>10
    elementData[size++] = e;//将下一个位置的值设置为e
    return true;//添加完成
}

/*
总结:
	无参构造初始化一个空数组
	add添加新元素
	先检查数组是否为无参构造第一次创建的数组
		是:则设置大小为10
		不是:扩容为原本的1.5倍(向下取整),如果1.5倍扩容小于最少需要的空间则扩容为最少需要的空间大小
	将新的数据加入
*/
  1. 如果使用的是指定大小的构造器,则初始化elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];//创建指定大小的空数组
    } else if (initialCapacity == 0) {//如果传入的大小为0则相当于调用无参构造 见2
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

public boolean add(E e) {//同样调用add方法添加元素 
    ensureCapacityInternal(size + 1);  // 
    elementData[size++] = e;//无论如何size++
    return true;
}

private void ensureExplicitCapacity(int minCapacity) {//minCapacity=10
    modCount++;//0++->1

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)//如果initialCapacity不为0且size+1<capacity(容量不够)则直接走入grow(size+1)
        grow(minCapacity);
}

private void grow(int minCapacity) {//minCapacity=1
    // overflow-conscious code
    int oldCapacity = elementData.length;//oldCapacity=initialCapacity
    int newCapacity = oldCapacity + (oldCapacity >> 1);//newCapacity=1.5*initialCapacity 1.5呗扩容
    if (newCapacity - minCapacity < 0)//((1.5*initialCapacity)>=1) - (minCapacity=1) >= 0 不符合
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)//不符合
        newCapacity = hugeCapacity(minCapacity);
    // 设1.5*initialCapacity为1.5capacity 
    elementData = Arrays.copyOf(elementData, newCapacity);//在保证原数据不变的基础上 将数组扩容为
}
  1. 使用size()获取当前数据大小(用户使用到的空间,不是容器大小)时间复杂度O(1)
public int size() {
    return size;
}
  1. 发生扩容时就会进行数组copy,所以最好在一开始就设置合适的容量,减少扩容的次数提高效率
private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);//进行数组的复制
}
  1. remove(int i)移除元素(移除的不是最后一个元素)时需要将后面的所有元素向前移动,然后标记最后一个数据为null让GC回收,remove不会减少容量
public E remove(int index) {
    rangeCheck(index);

    modCount++;
    E oldValue = elementData(index);

    int numMoved = size - index - 1;//计算index后面的元素个数
    if (numMoved > 0)//如果index是最后一个元素就不需要复制粘贴了
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);//将index后面元素复制粘贴到index位置上(会导致最后那个元素还存在)
    elementData[--size] = null; //标记最后一个数据为null让GC回收

    return oldValue;
}
  1. indexOf(),contains(),remove(Object o)都是从左往右遍历数组 时间复杂度O(n)
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;
}

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;
}
  1. addAll(),removeAll(),retainAll()等多元素操作方法效率远高于多次add(),remove()单操作,因为只用进行一次扩容判断或扩容
public boolean addAll(Collection<? extends E> c) {
    Object[] a = c.toArray();
    int numNew = a.length;
    ensureCapacityInternal(size + numNew);  // 只调用了一次
    System.arraycopy(a, 0, elementData, size, numNew);
    size += numNew;
    return numNew != 0;
}
  1. 关于Arrays.asList()
List<Integer> list = Arrays.asList(1,2,3);

需要注意的是,这个方法返回的List,它的实现类并不是ArrayList,而是Arrays类的一个内部类,在这个内部类的实现中,内部用的的数组就是传入的数组,没有拷贝,也不会动态改变大小,所以对数组的修改也会反映到List中,对List调用add/remove方法会抛出异常。

要使用ArrayList完整的方法,应该新建一个ArrayList,如下所示:

List<Integer> list = new ArrayList<Integer>(Arrays.asList(1,2,3));

Vector

基本介绍

  1. Vector类的定义
public class Vector<E> extends AbstractList<E>
    implements List<E>,RandomAccess,Cloneable,Serializable
  1. Vector底层也是一个对象数组,protected Object[] elementData;
  2. Vector是线程同步的,即线程安全,Vector类的操作方法带有synchronized
  3. 在开发中,需要线程同步安全时,考虑使用Vector

Vector和ArrayList比较

底层结构

版本

线程安全(同步)效率

扩容倍数

ArrayList

可变数组

jdk1.2

不安全,效率高

有参构造(容量大于0)max(1.5倍,size+1)

其他情况:

1. 第一次10

2. 第二次及之后max(1.5倍,size+1)

Vector

可变数组

jdk1.0

安全,效率不高

有参构造(容量大于0)max(2倍,size+1)

其他情况:

1. 第一次10

2. 第二次及之后Max(2倍,size+1)

源码解析

1. 初始化

无参构造(10) 有参构造(指定大小、对应容器的真实数组大小)

public Vector(int initialCapacity, int capacityIncrement) {
    super();
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    this.elementData = new Object[initialCapacity];//有参构造初始化为指定大小容量
    this.capacityIncrement = capacityIncrement;
}

public Vector(int initialCapacity) {
    this(initialCapacity, 0);
}

public Vector() {/无参构造设置10大小的容量
    this(10);
}

public Vector(Collection<? extends E> c) {//
    elementData = c.toArray();
    elementCount = elementData.length;
    // c.toArray might (incorrectly) not return Object[] (see 6260652)
    if (elementData.getClass() != Object[].class)
        elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
}
2. 添加与扩容

设置了增量(大于0)则扩容到(szie+增量),否则扩容2倍

如果此扩容仍然无法满足最小容量需求则扩容到最小需求容量

public synchronized boolean add(E e) {
    modCount++;
    ensureCapacityHelper(elementCount + 1);//判断是否需要扩容
    elementData[elementCount++] = e;
    return true;
}

public synchronized void ensureCapacity(int minCapacity) {
    if (minCapacity > 0) {
        modCount++;
        ensureCapacityHelper(minCapacity);
    }
}

private void ensureCapacityHelper(int minCapacity) {
    if (minCapacity - elementData.length > 0)//如果当前容量足够大就不扩容
        grow(minCapacity);
}

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    //如果设置了增量 则扩容到(size+增量) 没有设置则直接扩容2倍
    int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
                                     capacityIncrement : oldCapacity);
    //如果新扩容容量仍然小于最小需要的容量则扩容到最小需要的容量
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}
3. cloce 克隆

克隆一个只保留真实数据的Vector,之前多出来没用的空间不再复制

public synchronized Object clone() {
    try {
        @SuppressWarnings("unchecked")
        Vector<E> v = (Vector<E>) super.clone();
        //只复制真实数据部分 多出来的空间丢弃
        v.elementData = Arrays.copyOf(elementData, elementCount);
        v.modCount = 0;
        return v;
    } catch (CloneNotSupportedException e) {
        throw new InternalError(e);
    }
}
4. toArray 获取对应对象的数组
  1. Object[] toArray() 获取Object[]类型的对象数组,直接创建一个新的只具有真实数据的Object数组返回
public synchronized Object[] toArray() {
    return Arrays.copyOf(elementData, elementCount);
}
  1. T[] toArray(T[] a) 如果a的空间够大,则将真实数据数组覆盖a数组中(从0开始到真数数据末尾)的数据,同时返回a(a中多出来的空间不作更改) 如果a的空间不足以装下真实数据数组,则返回一个新创建的只有真实数据且强制类型转换后的数组,a不改变(不复制任何数据到a)
@SuppressWarnings("unchecked")
public synchronized <T> T[] toArray(T[] a) {
    if (a.length < elementCount)
        return (T[]) Arrays.copyOf(elementData, elementCount, a.getClass());

    System.arraycopy(elementData, 0, a, 0, elementCount);

    if (a.length > elementCount)
        a[elementCount] = null;

    return a;
}
5. indexOf 获取对象的索引
1. int indexOf(Object o)

仍然调用的是 同步的 indexOf(o,0),所以也是同步的(从0开始遍历查找第一个出现的o的索引)

```java
public int indexOf(Object o) {
return indexOf(o, 0);
}
```

2. synchronized int indexOf(Object o,int index)
同步,从index位置开始遍历查找出第一个出现的o的索引

```java
public synchronized int indexOf(Object o, int index) {
if (o == null) {
for (int i = index ; i < elementCount ; i++)
if (elementData[i]==null)
return i;
} else {
for (int i = index ; i < elementCount ; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
```
6. removeAll等调用父类方法的方法
1. removeAll(Collection)[类似的有retainAll]{最终仍然调用的是自己的remove方法}
用于删除多个元素的方法,只有与传入的Collection对象中持有的元素匹配的元素会被删除

```java
public synchronized boolean removeAll(Collection<?> c) {
return super.removeAll(c);
}
```

1. 直接调用父类的removeAll()方法,并将传入的Collection对象传入进去

2. 向调用者返回删除元素的结果

2. 父类中的removeAll(Collection)
位于父类AbstractCollection中,用于删除与传入参数Collection对象中匹配的所有元素

```java
public boolean removeAll(Collection<?> c) {
Objects.requireNonNull(c);
boolean modified = false;
Iterator<?> it = iterator();
while (it.hasNext()) {
if (c.contains(it.next())) {
it.remove();
modified = true;
}
}
return modified;
}
```

1. 检查传入参数Collection对象

2. 定义局部变量,表示是否修改,默认值false

3. 调用iterator()方法获取迭代器对象,并由局部变量it负责保存此iterator()方法都是子类去实现,Vector中也实现了该方法,此方法会返回一个迭代器对象

4. 使用迭代器对象的方法进行遍历与删除
hasNext()方法用于判断是否有下一个元素,当第一次使用时,判断的是第一个元素
next()方法可以获取到一个元素,第一次使用时,获取到的是第一个元素
remove()方法可以删除一个元素[仍然调用的是自己的remove函数]

```java
public void remove() {
if (lastRet == -1)
throw new IllegalStateException();
synchronized (Vector.this) {
checkForComodification();
Vector.this.remove(lastRet);//仍然调用的是自己的remove函数
expectedModCount = modCount;
}
cursor = lastRet;
lastRet = -1;
}
```

每当删除一个元素(Vector中持有的元素与Collection中的某个元素相同),将是否修改的标志位modified赋值为true

5. 向调用者返回删除结果

LinkedList

介绍

  1. LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。
  2. LinkedList 实现 List 接口,能对它进行列表操作。
  3. LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。
  4. LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。
  5. LinkedList 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。
  6. LinkedList 是非同步的

底层操作机制

  1. LinkedList底层维护了一个双向链表 从first到last

first(0)

1

2

3

last(4)

prev

null

0

1

2

3

item

data0

data1

data2

data3

data4

next

1

2

3

4

null

  1. LinkedList中维护了两个属性first和last分别指向 首节点和尾结点
  2. 每个节点(Node对象),里面又维护了prev、next、item三个属性,其中通过prev指向前一个,通过next指向后一个节点。最终实现双向链表
private static class Node<E> {
    E item;
    Node<E> next;
    Node<E> prev;

    Node(Node<E> prev, E element, Node<E> next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}
  1. 所以LinkedList的元素的添加和删除,不是通过数组完成的,相对来说效率较高

API

//双向链表
(非公有方法)
void		  linkFirst(E a)				//在frist前链接一个元素
void		  linkLast(E a)					//在last后链接一个元素
void		  linkBefore(E e, Node<E> succ)	//在succ节点前链接一个元素
E			  unlinkFirst(Node<E> f)		//移除frist节点
E			  unlinkLast(Node<E> l)			//移除last节点
E			  unlink(Node<E> x)				//移除指定节点
Node<E>		  node(int index)				//从first向last遍历到index的节点
//List接口
boolean       add(E object)					//在last后链接一个节点
void          add(int location, E object)	//左链接location节点prev,右链接location节点
boolean       addAll(Collection<? extends E> collection)//在last后面添加多个元素
boolean       addAll(int location, Collection<? extends E> collection)//在指定索引放入多个元素,最左的节点链接location节点prev,最右的节点右链接location节点点
E             remove()						//移除first节点
E             remove(int location)			//移除从first到last指定位置的节点
boolean       remove(Object object)			//从first到last中遍历 移除第一个item为object的节点
void          clear()						//清空链表
E             set(int location, E object)	//将指定节点的item替换为object
int           size()						//获取链表长度
<T> T[]       toArray(T[] contents)			//生成一个T数组 从first到last排序的数组 如果contents容量够大则覆盖size大小的内容
Object[]      toArray()						//生成一个Object数组 从first到last排序的数组
boolean       contains(Object object)		//indexOf(o)!=null
E             get(int location)				//获取从first往last第location个节点的item
int           indexOf(Object object)		//从first到last寻找第一次item为object的节点的索引
int           lastIndexOf(Object object)	//从last到first寻找第一次item为object的节点的索引
ListIterator<E>  listIterator(int location)	//获取从first到last的ListIterator集合迭代器
//clonable接口
Object        clone()						//返回该LinkedList实例的一个浅拷贝(LinkedList和Node换了,但是item还是原本的item)
//双向列表
void          addFirst(E object)			//linkFirst
void          addLast(E object)				//linkLast
E             getFirst()					//first
E             getLast()						//last
E             removeFirst()					//unlinkFirst
boolean       removeFirstOccurrence(Object o)//remove(o) 移除从first到last第一次item为o的节点
E             removeLast()					//unlinkLast
boolean       removeLastOccurrence(Object o)//移除从last到first第一次item为o的节点
//双向队列 Dequeue
boolean       offer(E o)					//linkLast 
boolean       offerFirst(E e)				//linkFirst
boolean       offerLast(E e)				//linkLast
E             peek()  						//first
E             peekFirst()					//first
E             peekLast()					//last
E             poll()						//unlinkFirst
E             pollFirst()					//unlinkFirst
E             pollLast()					//unlinkLast
//栈  Stack
E             peek()    					//first
E             pop()							//unlinkFirst
void          push(E e)						//linkFirst

ArrayList和LinkedList比较

注意:都是线程不安全

底层结构

增删的效率

改查的效率

ArrayList

可变数组

较低,数组扩容

较高

LinkedList

双向链表

较高,通过链表追加

较低

如何选择ArrayList和LinkedList

  1. 如果改查操作多,选择ArrayList
  2. 如果增删操作多,选择LinkedList
  3. 一般来说,在程序中,80%-90%都是查询,因此大部分情况下回选择ArrayList

nkFirst E pollLast() //unlinkLast //栈 Stack E peek() //first E pop() //unlinkFirst void push(E e) //linkFirst

### [ArrayList和LinkedList比较]()

注意:都是线程不安全

|            | 底层结构 | 增删的效率         | 改查的效率 |
| ---------- | -------- | ------------------ | ---------- |
| ArrayList  | 可变数组 | 较低,数组扩容     | 较高       |
| LinkedList | 双向链表 | 较高,通过链表追加 | 较低       |

如何选择ArrayList和LinkedList

1. 如果改查操作多,选择ArrayList
2. 如果增删操作多,选择LinkedList
3. 一般来说,在程序中,80%-90%都是查询,因此大部分情况下回选择ArrayList