Java集合类— List学习笔记
参考资料:中文版(Google) java8 API - List
Java集合框架中List
占用举足轻重的地步,想必每一个java开发都有使用过它。
该文主要介绍List
集合(实现)和它相关的工具类,并不对其他集合类深入讨论,只与List
集合做对比。
List集合特点
1、有序集合,插入的元素是有先后顺序的
2、允许重复的元素
3、listIterator()
迭代器,允许元件插入、更换、双向访问,listIterator(int index)
从列表中的指定位置开始的列表迭代器。
List集合与Set集合
参考资料:中文版(Google) java8 API - Set
List集合与Set集合作为java
集合框架的顶层接口它们都继承了 Collection
,所以它们有Collection
集合拥有的全部特性,比如它们都是可以迭代的Iterator
。
List集合:元素有序可重复,先插入的在前面后插入的在后面,当然也可在指定下标位置查询get(int index)
、插入add(int index, E element)
、替换set(int index, E element)
、删除remove(int index)
。
Set集合:元素无序不可重复,所有方法都是从父接口Collection
继承类,没有自己特有的方法,由于不可重复,可以用它去除集合中重复元素。
List集合实现类
List
只是一个顶层集合接口,具体的工作还要交给它的实现类来实现。
ArrayList
参考资料:ArrayList
1、基于java数组实现的,内部是一个Object[]
组。
2、由于内部是数组,所以根据下标检索get(int index)
和替换set(int index, E element)
元素很快。
3、添加元素时,数组长度不足以存放新的元素时,需要扩容,将原有数组复制到新的数组中。
//扩容源码,
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//增加原有容量的一半
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
4、插入元素时,需要将当期下标位置之后的元素整体向后移动一个位置。
5、删除元素时,移除指定下标位置元素,将之后的元素整体向前移动一个位置。
6、非线程安全、不支持多线程操作,线程安全CopyOnWriteArrayList
综上所述:
1、ArrayList
更适合检索,不适合频繁的添加、插入、删除。
2、创建ArrayList
时,尽量指定一个初始大小ArrayList(int initialCapacity)
与实际添加元素数量接近(大于等于)。
LinkedList
参考资料:LinkedList
1、双链表实现了List
和Deque
接口,内部是Node<E>
链表。
2、对于检索get(int index)
和替换set(int index, E element)
操作,需要遍历双向链表,效率较低。
3、添加元素时,支持链表头部(addFirst(E e)
)和尾部(addFirst(E e)
、add(E e)
)添加,效率很高
4、插入元素时,先找到下标所在元素位置这个过程需要遍历链表,然后打开链表插入元素,效率比头尾添加低。
5、删除元素时,效率由高到低:
removeFirst()
/removeLast()
/remove()
(删除头元素等价于removeFirst()
),头尾删除不需要遍历链表
remove(int index)
/remove(Object o)
,遍历链表找到元素后删除。
综上所述:LinkedList
更适合添加和插入(相比ArrayList
),根据下标检索不如ArrayList
。
Vector
参考资料:Vector
1、与ArrayList
相同内部使用了数组进行存储。
2、 在ArrayList
基础上增加了线程安全特性(方法添加synchronized
),数组扩容有所不同Vector
添加一个扩容成员变量capacityIncrement
。
//数组扩容
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
//如果capacityIncrement>0扩容capacityIncrement大小,否则扩容oldCapacity大小
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);
}
综上所述:Vector
就是线程安全的ArrayList
。
CopyOnWriteArrayList
参考资料:CopyOnWriteArrayList
1、一个线程安全的变体的ArrayList
,内部也是用数组实现的。
2、所有可变操作( add
, set
,等等),需要复制一个数组,在这个数组上进行操作。比如 add(E e)
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
//原有的数组
Object[] elements = getArray();
int len = elements.length;
//复制后的新数组
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
//替换原数组
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
3、对于迭代器Iterator<E>
和 ListIterator<E>
不进行变更操作会跑出UnsupportedOperationException
异常,因为迭代器使用的是它内部数组实现的,并不是数组副本。
综上所述:
1、多线程共享同一List
集合。
2、当遍历操作大大超过可变操作。
小节
本小节介绍了List
集合一些常用的实现类,它们内部数据存储结构和使用场景。
Collections#synchronizedList
参考资料:Collections
1、返回由指定列表支持的同步(线程安全)List
。
2、使用委派模式,委派给传入的List
,在其基础上添加一个同步锁。如:get(int index)
public E get(int index) {
synchronized (mutex) {return list.get(index);}
}
锁对象可以自己制定synchronizedList(List<T> list, Object mutex)
,默认是this(Collections.SynchronizedRandomAccessList
或Collections.SynchronizedList
)对象。
3、有两种实现
Collections.SynchronizedRandomAccessList
:实现随机访问接口RandomAccess
,如果传入list
实现了该接口
Collections.SynchronizedList
:不支持随机访问。
4、与Vector
的区别
Vector
基于数组实现的线程安全类。
方法同步,锁对象是this
。
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
迭代器方法iterator()
和listIterator()
有同步锁。
Collections#synchronizedList
内部没有任何具体实现,所有方法加上同步锁后依赖于传入的List
集合。
只要是实现List
接口就可以使用该方法,具有很好的扩展性,
代码块同步锁,可以自己指定锁对象。
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
迭代器方法iterator()
和listIterator()
没有添加同步锁,所以在使用它们的时候需要手动加锁。
RandomAccess随机访问接口
参考资料:RandomAccess
List
实现类使用该接口标记,说明它支持快速随机访问。下面的两个循环,第一个比第二个循环运行得更快,就认为这个List
实现类具有快速随机访问,从而可以用RandomAccess
标记该类。
for (int i=0, n=list.size(); i < n; i++)
list.get(i);
for (Iterator i=list.iterator(); i.hasNext(); )
i.next();
List
实现类ArrayList
和LinkedList
, 前者使用数组实现它就具有快速随机访问,后者使用链表实现具有顺序访问而不具备快速随机访问,所以在使用fori
(第一个)或forEach
(第二个)循环时最好使用list instanceof RandomAccess
判断一下改类是否支持快速随机访问,如果数据量很大访问速度将得到很明显的提升。
Arrays#asList
1、把数组转换成List
集合。
2、使用内部Arrays.ArrayList
实现的,它继承了AbstractList
,增删改抛出UnsupportedOperationException
异常。
3、可以将返回的List
转换成ArrayList
集合,这样就可以进行增删改了。
总结
本章介绍了与List
集合相关的工具类(Arrays#asList
、Collections#synchronizedList
)和一些常用的实现类有线程和非线程安全的,数据结构有所不同(链表和数组),根据具体的使用场景选用不同的实现类。