Java集合 List接口
List接口
特性
- List接口是Collection接口的子接口
- List集合类中元素有序(即添加顺序与取出顺序一致),且可以重复!
- List集合汇总的每个元素都有其对应的顺序索引,即支持索引
- List容器中的每个元素都对应一个整数型的序号记载其在容器中的位置,可以根据序号存取容器中的元素
一些接口实现类
- AbstractList
- AbstractSequentialList
- ArrayList
- AttributeList
- CopyOnWriteArrayList
- LinkedList
- RoleList
- RoleUnreolvedList
- Stack
- Vector
常用方法
- add(int i,Object e)在i位置插入e元素
- boolean addAll(int i,Collection es)在i位置开始将es所有元素插入
- Object get(int i)获取指定i位置的元素
- int indexOf(Object o)返回o在集合中首次出现的位置
- int lastOf(Object o)返回o在集合中末次出现的位置
- Object remove(int i)移除指定i位置的元素,并返回该元素
- Object set(int i,Object e)将i位置的元素替换为e
- List subList(int from,int to)返回[from,to)区间的子集合
遍历方式
- 使用Iterator
- 使用增强for循环
- 使用普通for循环
ArrayList
注意事项
- ArrayList可以加入null,且可以加入多个
- ArrayLIst是由数组来实现数据存储的
- ArrayList基本等同于Vector,除了ArrayList是线程不安全(执行效率高),多线程下不建议使用ArrayList()
- 修改DEBUG的设置得以看见完整的数据
底层结构与源码分析
- ArrayList中维护了一个Object类型的数组elementData
transient Object[] elementData;//transient 表示瞬间,短暂的,表示该属性不会被序列化
- 当创建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倍扩容小于最少需要的空间则扩容为最少需要的空间大小
将新的数据加入
*/
- 如果使用的是指定大小的构造器,则初始化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);//在保证原数据不变的基础上 将数组扩容为
}
- 使用size()获取当前数据大小(用户使用到的空间,不是容器大小)时间复杂度O(1)
public int size() {
return size;
}
- 发生扩容时就会进行数组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);//进行数组的复制
}
- 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;
}
- 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;
}
- 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;
}
- 关于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
基本介绍
- Vector类的定义
public class Vector<E> extends AbstractList<E>
implements List<E>,RandomAccess,Cloneable,Serializable
- Vector底层也是一个对象数组,protected Object[] elementData;
- Vector是线程同步的,即线程安全,Vector类的操作方法带有synchronized
- 在开发中,需要线程同步安全时,考虑使用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 获取对应对象的数组
- Object[] toArray() 获取Object[]类型的对象数组,直接创建一个新的只具有真实数据的Object数组返回
public synchronized Object[] toArray() {
return Arrays.copyOf(elementData, elementCount);
}
- 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
介绍
- LinkedList 是一个继承于AbstractSequentialList的双向链表。它也可以被当作堆栈、队列或双端队列进行操作。
- LinkedList 实现 List 接口,能对它进行列表操作。
- LinkedList 实现 Deque 接口,即能将LinkedList当作双端队列使用。
- LinkedList 实现了Cloneable接口,即覆盖了函数clone(),能克隆。
- LinkedList 实现java.io.Serializable接口,这意味着LinkedList支持序列化,能通过序列化去传输。
- LinkedList 是非同步的
底层操作机制
- 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 |
- LinkedList中维护了两个属性first和last分别指向 首节点和尾结点
- 每个节点(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;
}
}
- 所以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
- 如果改查操作多,选择ArrayList
- 如果增删操作多,选择LinkedList
- 一般来说,在程序中,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