- List集合中元素有序(添加顺序和取出顺序一致),可以重复
- List集合中的每个元素都有整数下标对应,即可以索引
- 其实现类常用的有三种:ArrayList 、LinkedList、Vector
public class trestt {
public static void main(String[] args) {
List list = new ArrayList();
list.add(10);
list.add("String");
list.add(true);
list.add(true);
list.add(true);
list.add(true);
//get获取对应下表的元素
list.get(0);
//indexOf从头查找对应元素第一次出现位置的下标
list.indexOf(10);
//lastIndexOf从尾查找对应元素第一次出现位置的下标
list.lastIndexOf(10);
//set修改对应下标的元素
list.set(2,1);
//subList截取,包左不包右边
List list1 = list.subList(1,3);
}
}
ArrayList 、LinkedList、Vector的三种遍历方式
List list = new ArrayList;
//迭代器
Iterator iterator = list.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next);
}
//增强for循环
for (Object o :list) {
System.out.println(o);
}
//普通for循环
for (int i = 0; i < list.size(); i++) {
System.out.println(list.get(i));
}
ArrayList
- 可以存放所有类型的元素,包括空
- 底层是由数组来实现的
- 是线程不安全的,效率高
ArrayList底层分析
- ArrayList中维护的是一个Object类型的数组——elementData
- 使用无参构造器,一开始elementData的容量为0,添加一个元素后容量变为10,不够之后按1.5倍扩容
- 使用指定构造器,一开始elementData的容量就为指定容量,不够后不够之后按1.5倍扩容
1.使用无参构造器时,底层分析
//现有如下一段程序
public class trestt {
public static void main(String[] args) {
List list = new ArrayList();//创建一个list
for (int i = 1; i <10 ; i++) {
list.add(i);//进行10次添加
}
}
}
对程序进行DeBug,下列皆为源码
//第一步:初始化
public ArrayList() {
//进入无参构造器,这里将elementData数组初始化为空,并且初始化modCount:被修改次数
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//第二步:装箱
//因为elementData数组是一个Object类型,所以添加时需要对元素进行装箱
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
//第三步:扩容
public boolean add(E e) {
//1.进入该方法,传入一个最小容量也就是1,因为size是0
ensureCapacityInternal(size + 1);
//11.将元素放入扩容后的数组当中
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
//2.将最小容量和elementData传入calculateCapacity方法,
// 并将返回值传入ensureExplicitCapacity方法
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));//5.这里将10传入
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//3.在这里判断数组是不是为空,不会空就返回最小容量
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
//4.当前数组为空,进入方法,这里DEFAULT_CAPACITY=10
// max对这里DEFAULT_CAPACITY和最小容量进行比较,选出最多并返回
return Math.max(DEFAULT_CAPACITY, minCapacity);//这里返回10
}
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;//修改次数记录
if (minCapacity - elementData.length > 0)
//6.如果最小容量减去数组的长度大于0,就说明数组容量不够,就进入grow进行扩容
// 例如现在,最小容量为10,数组长度为0,所以10-0大于1,需要扩容
grow(minCapacity);
}
*重点*
private void grow(int minCapacity) {//接收到最小容量10
//7.将数组长度进行记录,oldCapacity = 0
int oldCapacity = elementData.length;
//8.这里将会进行1.5倍扩容,0右移1位是1,所以这里newCapacity = 0+1 = 1
int newCapacity = oldCapacity + (oldCapacity >> 1);
//9.用新容量减去最小容量,如果小于零说明扩容后的容量没有最小容量大,
// 所以将最小容量赋值给新容量,
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;//这里newCapacity = 10;
//将新容量-MAX_ARRAY_SIZE。如果大于0,将会进行溢出处理,此处小于0,就直接过了
if (newCapacity - MAX_ARRAY_SIZE > 0)
//溢出处理:首先判断最小容量是否小于0,
//如果小于0,就抛出内存不足异常;
//否则判断最小容量是否大于MAX_ARRAY_SIZE,是则返回一个更大的容量,否则返回MAX_ARRAY_SIZE
newCapacity = hugeCapacity(minCapacity);
//10.将旧数组的元素进行拷贝和扩容,赋给新数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
2.使用条件构造器时,底层分析
//现有如下一段程序
public class trestt {
public static void main(String[] args) {
List list = new ArrayList(5);//创建一个list,初始化容量为5
for (int i = 1; i <10 ; i++) {
list.add(i);//进行10次添加
}
}
}
对程序进行DeBug,下列皆为源码
//第一步:初始化
public ArrayList(int initialCapacity) {
//首先对初始容量进行判断
//如果大于0,就创建一个Object类型容量为initialCapacity的数组elementData
//如果等于0,就讲elementData初始化为空数组
//如果小于0,就抛出异常
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
//第二步:装箱
//因为elementData数组是一个Object类型,所以添加时需要对元素进行装箱
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
//第三步:扩容大部分与无参类似
//不同点在于一开始添加元素的时候
private static int calculateCapacity(Object[] elementData, int minCapacity) {
//在这里,elementData并不为空,所以不会将容量扩充为10
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
//而是返回定义好的容量
return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//到该方法时,最小容量-数组长度小于零,所以在一开始的时候并不会调用grow去进行扩容
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
//当容量不够时就会和无参一样进行1.5倍扩容
Vector
- Vector的底层也是一个对象数组,elementData
- Vector是线程同步的,即线程安全的,有synchronized属性
- 线程安全,但是效率不高
Vector底层分析
- Vector中维护的是一个Object类型的数组——elementData
- 使用无参构造器,一开始调用有参构造器并赋值10,不够之后按2倍扩容
- 使用指定构造器,一开始elementData的容量就为指定容量,不够后不够之后按2倍扩容
//现有如下一段程序
public class trestt {
public static void main(String[] args) {
//List list = new Vector();
List list = new Vector(1);
for (int i = 1; i <10 ; i++) {
list.add(i);
}
}
}
//第一步:创建对象
//1.在创建对象时,使用有参构造器时
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
//2.使用无参构造器时底层调用与有参基本相同
//就是在一开始时调用有参构造器,并初始化10的容量
public Vector() {
this(10);//初始化容量为10
}
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
之后使用无参讲解
//第二步:初始化
public Vector(int initialCapacity, int capacityIncrement) {
//初始化modCount:被修改次数
super();
//如果初始容量小于0,就抛出异常
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
//否则,就创建一个Object类型容量为initialCapacity的数组elementData
//容量增量赋值为0
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
//第三步:装箱
//因为elementData数组是一个Object类型,所以添加时需要对元素进行装箱
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
//第四步:扩容
public synchronized boolean add(E e) {
modCount++;//修改增量加加
//1.判断是否扩容,这里elementCount=10
ensureCapacityHelper(elementCount + 1);
//7.将元素放入扩容后的数组当中
elementData[elementCount++] = e;
return true;
}
private void ensureCapacityHelper(int minCapacity) {
//2.如果最小容量-数组长度大于0
// 就说明数组容量不够,需要扩容
// 此时最小容量等于11,数组长度等于10,11-10大于0所以需要扩容
if (minCapacity - elementData.length > 0)
//进入方法
grow(minCapacity);
}
private void grow(int minCapacity) {
//3.将数组长度赋值给oldCapacity = 10
int oldCapacity = elementData.length;
//4.如果扩容增量大于0,capacityIncrement未指定的情况下为0,所以newCapacity = 2倍oldCapacity = 20
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
//5.判断新容量-最小容量是否小于0,此时20-11大于0,所以跳过
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//此处时溢出处理,内存不足问题
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
//6.将旧数组的元素进行拷贝和扩容,赋给新数组
elementData = Arrays.copyOf(elementData, newCapacity);
}
需要注意:capacityIncrement(容量增量),当他不为0时,扩容不一定是以2倍扩容
capacityIncrement是在有参构造器中是可以指定的
LinkedList
1.LinkedList的底层实现了双向链表和双端队列特点
2.可以添加任意元素,可以重复,可以为null
3.线程不安全
LinkedList底层分析
- LinkedList底层维护的是一个双向链表
- LinkedList中维护了两个属性first和last,分别指向链表的首和尾
- 每个节点,又维护了prev、next、item三个属性,next指向下一个节点,prev指向上一个节点;
- 所以LinkedList的添加和删除,不是通过数组实现的,效率较高
//模拟双向链表
public class trestt {
public static void main(String[] args) {
//创建三个节点
Node jacke = new Node("jacke");
Node tom = new Node("tom");
Node kave = new Node("kave");
//创建头结点和尾结点
Node first = jacke;
Node last = kave;
//将节点之间首尾连接
jacke.next = tom;
tom.next = kave;
kave.prve = tom;
tom.prve = jacke;
//循环遍历
while (true){
if(first==null){
break;
}
System.out.println(first);
first = first.next;
}
}
}
//创建node类
class Node{
public Object item;//元素
public Node next;//指向下一个节点
public Node prve;//指向上一个节点
public Node(Object item) {
this.item = item;
}
@Override
public String toString() {
return "item=" + item ;
}
}
对LinkedList的增的结构分析
添加元素
//现有如下一段代码
public class trestt {
public static void main(String[] args) {
List list = new LinkedList();
list.add(1);//断点DeBug
list.add(2);
}
}
对程序进行DeBug,下列皆为源码
//第一步:装箱
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
//第二步:添加
public boolean add(E e) {
linkLast(e);//1.进入linkLast方法
return true;
}
void linkLast(E e) {
//2.创建一个节点存放last,此时last为null
final Node<E> l = last;
//3.创建一个新节点,存放元素e,此时next和prev都为null
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
//4.如果l等于空,那么这个节点即是头结点也是尾节点
if (l == null)
first = newNode;
else//当第二次添加的时候,l里存放的是上一个节点的位置,
//所以l不等于空,说明前面有节点,这里只需要把尾节点指向newNode
l.next = newNode;
size++;
modCount++;
}
List的选择
| 底层结构 | 增删效率 | 改查效率 | 线程问题 | |
|---|---|---|---|---|
| ArrayList | elementData数组 | 低,需要扩容 | 高,可以根据下标查询 | 线程不安全 |
| Vector | elementData数组 | 低,需要扩容 | 高,可以根据下标查询 | 线程安全 |
| LinkedList | Node节点,双向链表 | 高,可以直接插入 | 低,需要从头查询 | 线程不安全 |
根据不同的业务场景进行选择使用。