java集合(三)--List接口及其实现类
一、定义:
public interface List<E> extends Collection<E>
通过观察List接口的定义发现其继承得是Collection接口,所以继承了Collection接口中的所有方法:
(1)Collection接口常用方法
1. public boolean add(E e); //添加元素到集合
2. public boolean addAll(Collection<? extends E> c); //存放一个集合
3. public boolean contains(Object o); //查找集合中的元素
4. public boolean isEmpty(); //判断一个集合是否为空
5. public boolean remove(Object 0);//删除一个集合中的元素
6. public int size();//返回集合的长度
(2)List接口拓展了Collection接口中的方法
1. public E get(int index); //根据索引取得元素
2. public E set(int index,E element);//替换元素,index为要替换元素下标 element为要替换元素
3. public ListIterator<E> listIterator() List //List自己的迭代器
二、List的特点和实现类
1、特点:
有序的,允许重复元素。顺序可以是自然排序或按对象加入到集合的顺序排序。因为List,所以它的对象可以被索引。ListIterator接口提供了迭代列表中元素的方法。抽象的List可以被随机的、通过数组、通过链接表或通过双向链接表进行访问。
2、list本身是一个接口,如果想要使用一个接口则可以使用该接口的实现类完成,重点实现类:ArrayList
、LinkedList
、Vector
(1)ArrayList : 底层数据结构是数组,允许对元素进行快速随机访问,但是向List中间插入与移除元素的速度很慢。线程不安全
(2)LinkedList : 底层数据结构是链表。线程不安全,对顺序访问进行了优化,向List中间插入与删除快。随机访问则相对较慢。
(3)Vector: 底层数据结构是数组。线程安全。随机访问快,向List中间插入与删除慢。
三、ArrayList
1、简介:java.util Class ArrayList<E>
ArrayList 是一个数组队列,相当于动态数组。线程不安全。
- ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
- ArrayList 实现了RandmoAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在ArrayList中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。
- ArrayList 实现了Cloneable接口,即覆盖了函数clone(),能被克隆。
- ArrayList 实现java.io.Serializable接口,这意味着ArrayList支持序列化,能通过序列化去传输。
ArrayList包含了两个重要的对象:elementData 和 size。
(1) 、elementData 是"Object[]类型的数组",它保存了添加到ArrayList中的元素。实际上,elementData是个动态数组,我们能通过构造函数 ArrayList(int initialCapacity)来执行它的初始容量为initialCapacity;如果通过不含参数的构造函数ArrayList()来创建ArrayList,则elementData的容量默认是10。elementData数组的大小会根据ArrayList容量的增长而动态的增长。
(2) 、size 则是动态数组的实际大小。
2、ArrayList的三个构造函数
// 默认构造函数,ArrayList的默认容量大小是10。
ArrayList()
// capacity是ArrayList的默认容量大小。当由于增加数据导致容量不足时,容量会添加上一次容量大小的一半。
ArrayList(int capacity)
// 创建一个包含collection的ArrayList
ArrayList(Collection<? extends E> collection)
3、关于ArrayList的常用方法,可以查看API
4、ArrayList的三种遍历方法
public class ArrayListTest {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(5);
list.add(6);
list.add(7);
list.add(8);
list.add(9);
list.add(10);
//通过迭代器遍历
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()) {
System.out.print(iterator.next()+" ");
}
System.out.println("\n--------------------------");
//for循环遍历
for (Integer integer : list) {
System.out.print(integer+" ");
}
System.out.println("\n--------------------------");
//随机访问,通过list.get(i)获得索引值去遍历。
for(int i=0;i<list.size();i++) {
System.out.print(list.get(i)+" ");
}
}
}
5、ArrayList是线程不安全的,可以看add()方法看出
public boolean add(E e) {
ensureCapacityInternal(size + 1); //确保容量是否充足
elementData[size++] = e; //将元素添加至数组
return true;
}
从这一步,elementData[size++] = e; 我们知道add()添加元素的时候是分两步走的:
(1)elementData[size] = e;
(2)size++;
假设有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停(线程A仅仅完成了步骤1,Size没有自增),线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,此时 Size 仍然等于 0 ,所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值。 那好,现在我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置 0,而 Size 却等于 2。这就是“线程不安全”了。
四、LinkedList
1、简介
LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。
- LinkedList 实现 List 接口,能对它进行队列操作。
- LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。
- LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。
- LinkedList 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。
- LinkedList 是非同步的,即线程不安全的。
LinkedList的本质是双向链表。
(1)、 LinkedList继承于AbstractSequentialList,并且实现了Dequeue接口。
(2) 、LinkedList包含两个重要的成员:header 和 size。
header是双向链表的表头,它是双向链表节点所对应的类Entry的实例。Entry中包含成员变量: previous, next, element。其中, previous是该节点的上一个节点,next是该节点的下一个节点,element是该节点所包含的值。
size是双向链表中节点的个数。
2、LinkedList构造函数
// 默认构造函数
LinkedList()
// 创建一个LinkedList,保护Collection中的全部元素。
LinkedList(Collection<? extends E> collection)
先介绍一下AbstractSequentialList。毕竟,LinkedList是AbstractSequentialList的子类。
AbstractSequentialList 实现了get(int index)、set(int index, E element)、add(int index, E element) 和 remove(int index)这些函数。这些接口都是随机访问List的,LinkedList是双向链表;既然它继承于AbstractSequentialList,就相当于已经实现了上面这些接口。
3、LinkedList的原理
(1) LinkedList 实际上是通过双向链表去实现的。
它包含一个非常重要的内部类:Entry。Entry是双向链表节点所对应的数据结构,它包括的属性有:当前节点所包含的值,上一个节点,下一个节点。
(2) 从LinkedList的实现方式中可以发现,它不存在LinkedList容量不足的问题。
(3) LinkedList的克隆函数,即是将全部元素克隆到一个新的LinkedList对象中。
(4) LinkedList实现java.io.Serializable。当写入到输出流时,先写入“容量”,再依次写入“每一个节点保护的值”;当读出输入流时,先读取“容量”,再依次读取“每一个元素”。
(5) 由于LinkedList实现了Deque,而Deque接口定义了在双端队列两端访问元素的方法。提供插入、移除和检查元素的方法。每种方法都存在两种形式:一种形式在操作失败时抛出异常,另一种形式返回一个特殊值(null 或 false,具体取决于操作)。
总结起来如下表:
(头部操作) (尾部操作)
抛出异常 特殊值 抛出异常 特殊值
插入 addFirst(e) offerFirst(e) addLast(e) offerLast(e)
移除 removeFirst() pollFirst() removeLast() pollLast()
检查 getFirst() peekFirst() getLast() peekLast()
(6) 、LinkedList可以作为FIFO(先进先出)的队列,即用Linkedlist实现FIFO的队列时,下表的方法等价:
队列方法 等效方法
add(e) addLast(e)
offer(e) offerLast(e)
remove() removeFirst()
poll() pollFirst()
element() getFirst()
peek() peekFirst()
(7)、 LinkedList可以作为LIFO(后进先出)的栈,即用LinkedList作为LIFO的栈时,下表的方法等价:
栈方法 等效方法
push(e) addFirst(e)
pop() removeFirst()
peek() peekFirst(
4、双线链表如何和索引值联系起来
LinkedList实际上是通过双向链表去实现的。既然是双向链表,那么它的顺序访问会非常高效,而随机访问效率比较低。既然LinkedList是通过双向链表的,但是它也实现了List接口{也就是说,它实现了get(int location)、remove(int location)等“根据索引值来获取、删除节点的函数”}。LinkedList是如何实现List的这些接口的,如何将“双向链表和索引值联系起来的”?实际原理非常简单,它就是通过一个计数索引值来实现的。例如,当我们调用get(int location)时,首先会比较“location”和“双向链表长度的1/2”;若前者大,则从链表头开始往后查找,直到location位置;否则,从链表末尾开始先前查找,直到location位置。
5、LinkedList遍历方式:LinkedList支持多种遍历方式。不要采用随机访问的方式去遍历LinkedList(低效),而采用逐个遍历的方式。
举例三种:
package ListTest;
import java.util.ArrayList;
import java.util.Iterator;
public class ArrayListTest {
public static void main(String[] args) {
ArrayList<Integer> list = new ArrayList<Integer>();
list.add(5);
list.add(6);
list.add(7);
list.add(8);
list.add(9);
list.add(10);
//通过迭代器遍历
Iterator<Integer> iterator = list.iterator();
while(iterator.hasNext()) {
System.out.print(iterator.next()+" ");
}
System.out.println("\n--------------------------");
//for循环遍历
for (Integer integer : list) {
System.out.print(integer+" ");
}
System.out.println("\n--------------------------");
//随机访问,通过list.get(i)获得索引值去遍历,不建议
for(int i=0;i<list.size();i++) {
System.out.print(list.get(i)+" ");
}
}
}
五、Vector
1、简介
Vector 是矢量队列,底层是数组。它是JDK1.0版本添加的类。继承于AbstractList,实现了List, RandomAccess, Cloneable这些接口。
- Vector 继承了AbstractList,实现了List;所以,它是一个队列,支持相关的添加、删除、修改、遍历等功能。
- Vector 实现了RandmoAccess接口,即提供了随机访问功能。RandmoAccess是java中用来被List实现,为List提供快速访问功能的。在Vector中,我们即可以通过元素的序号快速获取元素对象;这就是快速随机访问。
- Vector 实现了Cloneable接口,即实现clone()函数。它能被克隆。
- Vector中的操作是线程安全的。因为Vector的方法前加了synchronized 关键字,所以效率不高。
2、构造函数:Vector共有4个构造函数
// 默认构造函数
Vector()
// capacity是Vector的默认容量大小。当由于增加数据导致容量增加时,每次容量会增加一倍。
Vector(int capacity)
// capacity是Vector的默认容量大小,capacityIncrement是每次Vector容量增加时的增量值。
Vector(int capacity, int capacityIncrement)
// 创建一个包含collection的Vector
Vector(Collection<? extends E> collection)
3、Vector原理
(1)、 Vector实际上是通过一个数组去保存数据的。当我们构造Vecotr时;若使用默认构造函数,则Vector的默认容量大小是10。
(2)、 当Vector容量不足以容纳全部元素时,Vector的容量会增加。若容量增加系数 >0,则将容量的值增加“容量增加系数”;否则,将容量大小增加一倍。
(3)、 Vector的克隆函数,即是将全部元素克隆到一个数组中。
4、遍历方式
Vector支持4种遍历方式。建议使用下面的第二种去遍历Vector,因为效率问题。
(1) 第一种,通过迭代器遍历。即通过Iterator去遍历。
Integer value = null;
int size = vec.size();
for (int i=0; i<size; i++) {
value = (Integer)vec.get(i);
}
(2) 第二种,随机访问,通过索引值去遍历。
由于Vector实现了RandomAccess接口,它支持通过索引值去随机访问元素。
Integer value = null;
int size = vec.size();
for (int i=0; i<size; i++) {
value = (Integer)vec.get(i);
}
(03) 第三种,另一种for循环。如下:
Integer value = null;
for (Integer integ:vec) {
value = integ;
}
(04) 第四种,Enumeration遍历。如下:
Integer value = null;
Enumeration enu = vec.elements();
while (enu.hasMoreElements()) {
value = (Integer)enu.nextElement();
}
借鉴博客:https://www.cnblogs.com/efforts-will-be-lucky/p/7052672.html
https://www.cnblogs.com/skywang12345/tag/%E9%9B%86%E5%90%88/