程序员应该吃透的集合List

一:先看看集合框架接口图

        (图片来源于网络)java

 

       从图中能够看到List实现了Collection接口。git

二:Collection接口是什么?

      在java类库中,Collection接口是集合类的基本接口,这个接口有两个基本的方法:github

      

public interface Collection<E> extends Iterable<E>
{
    boolean add(E element);
    Iterator<E> iterator();
    ...
}

 

  add方法用于向集合中添加元素。若是添加元素确实改变了集合就返回true,若是集合没有发生改变就返回false。列如,向一个   集合中添加一个已经存在的对象,这个添加请求就没有效果会返回false,由于Set集合不容许重复对象。从代码中能够看到,       Collection接口还包括了一个iterator()方法,返回类型为Iterator接口对象,那么什么Iterator接口?面试

  Collection接口中的其它方法:数组

  

//添加方法:
add(Object o) //添加指定元素
addAll(Collection c) //添加指定集合
//删除方法:
remove(Object o) //删除指定元素
removeAll(Collection c) //输出两个集合的交集
retainAll(Collection c) //保留两个集合的交集
clear() //清空集合
//查询方法:
size() //集合中的有效元素个数
toArray() //将集合中的元素转换成Object类型数组
//判断方法:
isEmpty() //判断是否为空
equals(Object o) //判断是否与指定元素相同
contains(Object o) //判断是否包含指定元素
containsAll(Collection c) //判断是否包含指定集合

  List接口中的方法安全

//添加方法:
add(int index, Object o) //向指定位置添加元素
addAll(int index, Collection c) //向指定位置添加集合
//删除方法
remove(int index) //删除指定元素
//查询方法:
get(int index) //获取指定位置的元素
indexOf(Object o) //获取指定元素的位置
lastIndexOf(Object o) //获取指定元素最后一次出现的位置
//修改方法:
subList(int fromIndex, int toIndex) //截取子集合从fromIndex到toIndex,要头不要尾
set(int index, Object o) //修改指定位置的元素

三:迭代器

    翻看Iterator接口源码网络

    

public interface Iterator<E> {
   
    boolean hasNext();
    E next();
    default void remove() {
        throw new UnsupportedOperationException("remove");
    }
    default void forEachRemaining(Consumer<? super E> action) {
        Objects.requireNonNull(action);
        while (hasNext())
            action.accept(next());
    }
}

  很明显的能够看到,这个Iterator接口就像当于链表中的结点,只不过在C语言里结点里的指针在java中变成了对象的引用。那么 咱们就应该知道,经过反复的调用nest()就能够逐个的访问集合中的每一个元素,可是当到达了集合的末尾,nest方法将抛出一个NoSuchElementException。所以,每次都用next方法前都应该调用hasNext方法进行判断。hasNext方法的做用是判断对象是否还有下一个元素,有就返回true,不然返回false。remove方法会删除上次调用next方法时返回的元素。就像是删除一个元素以前先看下它是颇有必要的,remove方法就是按照这个理念设计的。举一个访问集合中全部元素的案例:数据结构

Collection<String> s = new ArrayList<String>();
		s.add("xiaohong");
		s.add("xionming");
		s.add("wanger");
	
		Iterator<String> iterator = s.iterator();
		while(iterator.hasNext()) {
			
			String element = iterator.next();
			System.out.println(element);
		}

在Java SE8版本中,新加入了for each循环遍历,编译器简单地将“for each”循环翻译为带有迭代器的循环。多线程

for (String string : s)
 {
    System.out.println(string);
		
 }

显然,经过"for each"遍历使得代码更加简洁,全部实现了Iterable接口的对象均可以使用"for each"循环。Collection接口扩展了Iterable接口。并发

 

四:具体的实现类

1.AbstractList

  若是实现了Collection接口的每个类都要实现它的全部方法,那么将是一件很烦的事情。此时,AbstractList应运而生。

它将基础的iterator抽象化,其它的方法给实现了,此时一个具体的集合类就能够扩展AbstractList类,而且只需提供Iterator方法,固然若是不满意AbstractList类实现的方法也能够在子类重写它的方法。

 2.ArrayList

ArrayList 的底层是数组队列,至关于动态数组。与 Java 中的数组相比,它的容量能动态增加。在添加大量元素前,应用程序可使用ensureCapacity操做来增长 ArrayList 实例的容量。这能够减小递增式再分配的数量。

它继承于 AbstractList,实现了 List, RandomAccess, Cloneable, java.io.Serializable 这些接口。

在咱们学数据结构的时候就知道了线性表的顺序存储,插入删除元素的时间复杂度为O(n),求表长以及增长元素,取第 i 元素的时间复杂度为O(1)

  • ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
  • ArrayList 实现了RandomAccess 接口, RandomAccess 是一个标志接口,代表实现这个这个接口的 List 集合是支持快速随机访问的。在 ArrayList 中,咱们便可以经过元素的序号快速获取元素对象,这就是快速随机访问。
  • ArrayList 实现了Cloneable 接口,即覆盖了函数 clone(),能被克隆。
  • ArrayList 实现java.io.Serializable 接口,这意味着ArrayList支持序列化,能经过序列化去传输。

  和 Vector 不一样,ArrayList 中的操做不是线程安全的!因此,建议在单线程中才使用 ArrayList,而在多线程中能够选择 Vector 或者 CopyOnWriteArrayList。

ArrayList中特有的方法

ensureCapacity(int minCapactiy) //判断当前数组中的元素个数是否大于指定的minCapacity
trimToSize() //修改数组容量为当前数组有效元素个数

3.LinkedList

     LinkedList是一个实现了List接口和Deque接口的双端链表。 LinkedList底层的链表结构使它支持高效的插入和删除操做,另外它实现了Deque接口,使得LinkedList类也具备队列的特性; LinkedList不是线程安全的,若是想使LinkedList变成线程安全的,能够调用静态类Collections类中的synchronizedList方法.。

须要提一下的是,LinkedList类中有一个ListIterator<E> listIterator方法,listIterator接口中包含一个add方法:

public interface ListIterator<E> extends Iterator<E> {

    boolean hasNext();
    E next();
    boolean hasPrevious();
    void set(E e);
    void add(E e);
}

 

由于链表是一个有序的集合,每一个对象的位置就显得十分重要。LinkedList中的add方法只能将对象添加到链表尾部,而常常却要将对象添加到链表的中间,迭代器就是用于描述集合中的位置的,因此这种依赖位置的方法就交由迭代器来完成。由于Set集合是无序的,因此在Iterator接口中就没有add方法,而是扩展了一个LinkIterator接口来实现。

值得一提的是:你们都知道,链表是不支持快速随机访问的。若是要查看链表中的第n个元素,就必须从头开始,越过n-1个元素,没有捷径可走,但尽管如此,LinkedList仍是提供了一个用来访问某个特定元素的get方法,固然这个方法的效率并不高,若是在使用这个方法,那么可能对于所要解决的问题使用了错误的数据结构。LinkedList类中get方法所谓的随机访问都是须要从列表的头部开始搜索,效率极低。使用链表的惟一理由是尽量的减小在链表中间插入或删除元素所付出的代价。

LinkedList中特有的方法

//查询方法:
getFirst() //获取集合中的第一个元素
getLast() //获取集合中的最后一个元素
//添加方法:
addFirst(Object o) //在集合的第一个位置添加指定元素
addLast(Object o) //在集合的最后一个位置添加指定元素
//删除方法:
removeFirst() //删除集合中的第一个元素
removeLast() //删除集合中的最后一个元素

 下面程序简单的建立了两个链表,将它们合并在一块儿,而后从第二个链表中每隔一个元素删除一个元素,最后测试removeAll()方 法 : 

package listdemo;

import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

public class LinkedListTest {

	public static void main(String[] args) {
		
		List<String> a = new LinkedList<>();
		a.add("aaa");
		a.add("bbb");
		a.add("eee");
		
		List<String> b = new LinkedList<>();
		b.add("AAA");
		b.add("BBB");
		b.add("EEE");
		
		ListIterator<String> aIter = a.listIterator();
		ListIterator<String> bIter = b.listIterator();
		
		//a集合合并b集合
		
		while(bIter.hasNext()) {
			
			if(aIter.hasNext())
				 aIter.next();
			aIter.add(bIter.next());
		}
		
		System.out.println(a);
		
		//从b链表中每间隔一个元素删除一个元素
		
		while(bIter.hasNext()) {
			
			bIter.next();//跳过一个元素
			if(bIter.hasNext()) {
				
				bIter.next();
				bIter.remove();//先查后删
			}
		}
		
		System.out.println(b);
		
		//测试删除全部
		a.removeAll(a);
		System.out.println(a);
		
	}
}

五:最后来看看ArrayList与LinkedList的区别

  • 1. 是否保证线程安全: ArrayListLinkedList 都是不一样步的,也就是不保证线程安全;

  • 2. 底层数据结构: Arraylist 底层使用的是 Object 数组LinkedList 底层使用的是 双向链表 数据结构(JDK1.6以前为循环链表,JDK1.7取消了循环。注意双向链表和双向循环链表的区别,下面有介绍到!)

  • 3. 插入和删除是否受元素位置的影响:ArrayList 采用数组存储,因此插入和删除元素的时间复杂度受元素位置的影响。 好比:执行add(E e) 方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种状况时间复杂度就是O(1)。可是若是要在指定位置 i 插入和删除元素的话(add(int index, E element) )时间复杂度就为 O(n-i)。由于在进行上述操做的时候集合中第 i 和第 i 个元素以后的(n-i)个元素都要执行向后位/向前移一位的操做。 ② LinkedList 采用链表存储,因此插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1)而数组为近似 O(n)。

  • 4. 是否支持快速随机访问: LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是经过元素的序号快速获取元素对象(对应于get(int index) 方法)。

  •         5. 内存空间占用: ArrayList的空 间浪费主要体如今在list列表的结尾会预留必定的容量空间,而LinkedList的空间花费则体   如今它的每个元素都须要消耗比ArrayList更多的空间(由于要存放直接后继和直接前驱以及数据)。

 

推荐阅读:

参考引用:

  1.  java核心思想
  2. Java集合框架常见面试题
  3. Java中的Collection集合接口经常使用方法