一、List接口
1、List接口
List接口是Collection接口的子接口,其中
-
List集合类中元素有序(即添加顺序和取出顺序一致)、且可重复
-
List集合中每个元素都有其对应的顺序索引,即支持索引
-
List容器中的元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素
2、List接口常用方法
list接口继承了Collcetion接口,包含Collcetion中的所有方法。
方法 | 功能描述 |
void add(int index,Object element) | 将元素elemet插入到List集合的index处 |
boolean addAll(int index,Collection c) | 将集合c所包含的所有元素都插入到List集合的index处 |
Object get(int index) | 返回集合index索引处的元素 |
int indexOf(Object o) | 返回兑现那个o在List集合中第一次出现的位置索引 |
int lastIndexOf(Object o) | 返回对象o在list集合中最后一次出现的位置索引 |
Object remove(int index) | 返回并删除index索引处的元素 |
Object set(int index,Object element) | 将index索引处的元素替换成element对象,返回被替换的旧元素 |
List subList(int forIndex,int toIndex) | 返回从索引fromIndex(包含)到索引toIndex(不包含)处所有集合元素组成的子集合 |
void replaceAll(UnaryOperator operator) | 根据operator指定的计算机规则重新设置List集合的所有元素 |
void sort(Comparator c) | 根据Comparator参数对List集合的元素排序 |
此外,list还定义了两种方法
-
get(int int index):获得指定索引位置的元素
-
set(int inedx,Object obj):缉拿该集合中指定索引位置的对象修改为指定的对象
3、List三种遍历方式
(1)、使用Iterator
Iterator iterator = collection.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
}
(2)、使用增强for
for (Object o : collection) {
}
(3)、使用普通for
for (int i=0;i<list.size();i++){
Object object = list.get(i);
System.out.println(object);
}
4、ArrayList
(1)简介
实现了可变的数组,允许保存所有元素,包括null,并可以根据索引位置对集合进行快速的随机访问;缺点是向指定的索引位置插入对象或删除对象的速度较慢
优点: 底层数据结构是数组,查询快,增删慢。
缺点: 线程不安全,效率高
(2)、ArrayList扩容机制
ArrayList中维护了一个Object类型的数据elementData;当创建ArrayList对象时,如果使用的是无参构造器,则初始elementData容量为0,第一次添加时,扩容elementData为10,如需要再次扩容,则扩容elementData为原来的1.5倍;如果使用的是指定大小的构造器,则初始elementData容量为指定大小,如果需要扩容,则直接扩容elementData为原来的1.5倍
public static void main(String[] args) {
List list = new ArrayList();
for (int i=1;i<=5;i++){
list.add(i);
}
for (int i=5;i<=10;i++){
list.add(i);
}
list.add(100);
list.add(200);
list.add(null);
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
Object obj = iterator.next();
System.out.println("List="+obj);
}
}
在执行for循环时,会把int装箱为Integer
点开ArrayList()方法:
调用此构造函数,返回了一个空的数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA,点开此数组可看到长度为0
ArrayList()中主要的几个变量
//定义一个空元素数据
private static final Object[] EMPTY_ELEMENTDATA = {};
//也是一个空数组,跟上边的空数组不同之处在于,这个是在默认构造器时返回的,扩容时需要用到这个作判断
final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/**
*transient用来表示一个域不是该对象序行化的一部分,当一个对象被序行化的时候,transient修饰的变量的值是不包括在序行化的表示中的。
* ArrayList在序列化的时候会调用writeObject,直接将size和element写入ObjectOutputStream;反序列化时调用readObject,从ObjectInputStream获取size和element,再恢复到elementData。
* elementData是一个缓存数组,它通常会预留一些容量,等容量不足时再扩充容量,那么有些空间可能就没有实际存储元素,采用上诉的方式来实现序列化时,就可以保证只序列化实际存储的那些元素,而不是整个数组,从而节省空间和时间。
*/
transient Object[] elementData;
//数组的长度,此参数是数组中实际的参数,区别于elementData.length
private int size;
ArrayList有三个构造函数,不同的构造函数会影响后边的扩容机制判断:
1.默认的无参构造
调用此构造函数,返回了一个空的数组DEFAULTCAPACITY_EMPTY_ELEMENTDATA,此数组长度为0.
2.给定初始容量的构造函数
构造一个具有指定长度的空数组,当initialCapacity为0时,返回EMPTY_ELEMENTDATA
3.包含特定集合元素的构造函数
把传入的集合转换为数组,然后通过Arrays.copyOf方法把集合中的元素拷贝到elementData中。同样,若传入的集合长度为0,返回EMPTY_ELEMENTDATA
(3)、ArrayList扩容原理
扩容开始于集合添加元素方法,添加元素有两种方法
其中两个方法都调用了ensureCapacityInternal(size + 1)方法,把数组长度加1以确保能存下下一个数据,此方法会先判断elementData是不是一个空数组,如果是则赋值给他一个最小容量,最小容量则为 DEFAULT_CAPACITY 和 minCapacity 的最大值
可以看到DEFAULT_CAPACITY为10,minCapacity为1
若创建ArrayList时调用的是无参构造ArrayList(),此方法会返回DEFAULT_CAPACITY(值为10)和minCapacity的最大值,因此,最终会返回固定值10;若创建ArrayList时调用了有参构造ArrayList(6),则此方法会返回1,注意这个
minCapacity变量只是第一次调用add方法时值为1,此后的调用需要根据实际的数组长度size+1。
然后调用ensureExplicitCapacity方法,
modCount++用来记录当前集合被修改的次数,此用到了快速失败机制:当多个线程对同一个集合进行操作的时候,某线程访问集合的过程中,该集合的内容被其他线程所改变(即其它线程通过add、remove、clear等方法,改变了modCount的值),这时,就会抛出ConcurrentModificationException异常,产生fail-fast事件。
最小容量减去数组实际的大小,如果大于零则会调用grow方法进行扩容,比如当前容量为10,数组实际大小为0,则无法进行添加操作。即如果ArrayList给定了特定初始容量,则此处需要根据实际情况确定是否调用grow方法,即有可能不需要扩容。如果没有指定初始容量,第一次调用add则此处一定需要调用grow方法。
int newCapacity = oldCapacity + (oldCapacity >> 1)意为原数据大小加原先数组大小的1.5倍,oldCapacity为原来的容量,(oldCapacity >> 1)右移一位,即除以2
当第一次进行添加时,oldCapacity为0,所以进入if判断,判断newCapacity如果小于传入的minCapacity,则直接让newCapacity等于minCapacity,即不需要扩容计算(当无参构造时,elementData.length为0,所以oldCapacity也为0,minCapacity为10,因此最终newCapacity为10)。
然后判断newCapacity是否大于设定的MAX_ARRAY_SIZE,此处如果大于,则调用hugeCapacity方法
Arrays.copyOf即为数组的复制,把newCapacity复制给elementData
5、Vector
(1)、简介
底层数据结构是数组,查询快,增删慢,线程安全,效率低,可以存储重复元素
protected Object[] elementData;
优点: 底层数据结构是数组,查询快,增删慢。
缺点: 线程安全,效率低
(2)、Vector扩容机制
public static void main(String[] args) {
Vector vector = new Vector();
for (int i=0;i<5;i++){
vector.add(i);
}
}
点击Vector()方法,如果是无参的,默认大小为10
在执行for循环时,会把int装箱为Integer
添加数据到Vector集合,和ArrayList一样有修改计数器
确定是否需要扩容
按实际情况进行扩容,
当第一次进行添加时,oldCapacity为10,capacityIncrement为0,所以直接让newCapacity等于oldCapacity+oldCapacity,即为原来的两倍。
6、LinkedList
(1)、简介
LinkedList采用双向链表结构保存对象。优点是便于像集合中插入和删除对象;但对于随机访问集合中的对象,使用LinkedList类实现List集合的效率较低
优点: 底层数据结构是链表,查询慢,增删快。
缺点: 线程不安全,效率高
(2)、简单双向链表实现
public class LinkedMethod {
public static void main(String[] args) {
Node jack = new Node("jack");
Node tom = new Node("tom");
Node jun = new Node("jun");
jack.next=tom;
tom.next=jun;
jun.pre=tom;
tom.pre=jack;
//头尾结点
Node first = jack;
Node last = jun;
System.out.println("==从头到尾进行遍历==");
while(true){
if(first==null){
break;
}
//输出first信息
System.out.println(first);
first = first.next;
}
System.out.println("==从尾到头进行遍历==");
while(true){
if(last==null){
break;
}
//输出first信息
System.out.println(last);
last = last.pre;
}
}
}
class Node {
//Node节点对象
public Object item;
//指向后一个节点
public Node next;
//pre指向前一个节点
public Node pre;
public Node(Object name) {
this.item = name;
}
public String toString() {
return "Node name=" + item;
}
}
(3)、LinkedList底层源码
add()方法
public static void main(String[] args) {
LinkedList linkedList = new LinkedList();
for (int i=1;i<=5;i++){
linkedList.add(i);
}
System.out.print("LinkedList="+linkedList);
}
创建一个空数组
依旧是对int类型的装箱
add()方法
将新的结点加入到双向链表的最后;
new Node<>(l, e, null)意为上一个节点、本体、下一个节点;
当第一次添加时,此时last为空,first也为空,但都指向item,item为1;
当第二次添加元素时,要创建一个新的节点,此时l指向前一个元素,下一个结点指向新节点