简介:java集合框架List常见基础面试题
考点:list的基础知识掌握情况,对应实现的区别、线程安全、使用场景。
问:Vector和ArrayList、LinkedList联系和区别?分别的使用场景?
答:
(1)线程安全:
ArrayList:底层是数组实现,线程不安全,查询和修改快,但是增加和删除慢。
LinkedList:底层是双向链表,线程不安全,查询和修改慢,新增和删除快。
Vector:底层是数组实现,线程安全的,使用了synchronized加锁。
(2)使用场景:
Vector:很少使用
添加和删除的场景多用LinkedList
查询和修改多用ArrayList
注释:
ArrayList简介
//初始化空数据,所以ArrayList的底层实现是数据
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//线程安全与不安全在于看有没有使用锁机制,add没有使用锁,所以是不安全的
public boolean add(E e) {
//确保容量
ensureCapacityInternal(size + 1); // Increments modCount!!
//数据尾部添加新值
elementData[size++] = e;
return true;
}
数组的查询、修改是根据下标操作所以快,但是添加和删除,需要移动元素所以比较慢,比如数组长度10000,添加或删除下标为200的值,下标200以后的元素就需要移动很多所以比较慢。
LinkedList简介
//双向链表,有链表头,链表尾
/**
* first用于记录链表头部第一个节点元素的信息
*/
transient Node<E> first;
/**
* last用于记录链表尾部最后一个节点元素的信息
*/
transient Node<E> last;
//这个Node内部私有类存储着一个节点的关键信息
private static class Node<E> {
//item当前节点的元素
E item;
//next代表当前节点元素的下一个节点元素
Node<E> next;
//prev代表当前节点元素的上一个节点元素
Node<E> prev;
//Node的构造方法
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
//整个添加过程中,系统只做了两件事情,添加一个新的节点,然后保存该节点和前节点的引用关系
public boolean add(E e) {
linkLast(e);
return true;
}
/**
* Links e as last element.
*/
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
看上面的源码可以知道,当LinkedList添加一个元素时,会默认的往LinkedList最后一个节点后添加,
具体步骤为:
①获得最后一个节点last作为当前节点l
②获得最后一个节点last作为当前节点l
③用当前节点l、添加参数e、null创建一个新的Node对象
④将新创建的Node对象链接到最后节点,也就是last
⑤如果当前的LinkedList为空,那么添加的node就是first,也是last
⑥当前LinkedList的size+1,表示结构改变次数的对象modCount+1
整个添加过程中,系统只做了两件事情,添加一个新的节点,然后保存该节点和前节点的引用关系。所以新增快。
//删除
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
E unlink(Node<E> x) {
// assert x != null;
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
LinkedList的删除,会根据传递的参数进行null判断,null和非null的处理不一样,对于null使用==判断,对非null值使用.equals(Object o)判断。
LinkedList的删除,是改变节点之间的引用关系,当前节点的prev和next和当前链表中的其他元素不存在任何
联系。所以删除比较快。
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
private void checkElementIndex(int index) {
if (!isElementIndex(index))
throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
}
private boolean isElementIndex(int index) {
return index >= 0 && index < size;
}
Node<E> node(int index) {
// assert isElementIndex(index);
if (index < (size >> 1)) {
Node<E> x = first;
for (int i = 0; i < index; i++)
x = x.next;
return x;
} else {
Node<E> x = last;
for (int i = size - 1; i > index; i--)
x = x.prev;
return x;
}
}
通过阅读源码,我们也可以解读出LinkedList的查询逻辑
①根据传入的index去判断是否为LinkedList中的元素,判断逻辑为index是否在0和size之间,如果在则调用node(index)方法,否则抛出IndexOutOfBoundsException;
②调用node(index)方法,将size右移1位,即size/2,判断传入的size在LinkedList的前半部分还是后半部分
如果在前半部分,即index < size/2,则从fisrt节点开始遍历匹配
如果在后半部分,即index > size/2,则从last节点开始遍历匹配
可以看出,如果LinkedList链表size越大,则遍历的时间越长,查询所需的时间也越长。所以查询比较慢。
Vector简介
public Vector() {
this(10);
}
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
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 synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
通过阅读源码,可知Vector的初始化是数组实现。并且add等方法使用了synchronized 关键字修饰,所以是线程安全的
//线程安全与不安全在于看有没有使用锁机制,add没有使用锁,所以是不安全的
public boolean add(E e) {
//确保容量
ensureCapacityInternal(size + 1); // Increments modCount!!
//数据尾部添加新值
elementData[size++] = e;
return true;
}
数组的查询、修改是根据下标操作所以快,但是添加和删除,需要移动元素所以比较慢,比如数组长度10000,
添加或删除下标为200的值,下标200以后的元素就需要移动很多所以比较慢。
问:如果需要保证ArrayList的线程安全需要怎么做?有几种方法?
考点:List的掌握情况
答:有3种。
- 使用synchronized同步所有ArrayList方法,自己写一个包装类,继承ArrayList,根据业务一般是add/update/remove加锁.
- List objects = Collections.synchronizedList(new ArrayList<>());
方法其实底层也是在集合的所有方法之上加上了synchronized
static class SynchronizedList<E>
extends SynchronizedCollection<E>
implements List<E> {
private static final long serialVersionUID = -7754090372962971524L;
final List<E> list;
SynchronizedList(List<E> list) {
super(list);
this.list = list;
}
SynchronizedList(List<E> list, Object mutex) {
super(list, mutex);
this.list = list;
}
public boolean equals(Object o) {
if (this == o)
return true;
synchronized (mutex) {return list.equals(o);}
}
public int hashCode() {
synchronized (mutex) {return list.hashCode();}
}
public E get(int index) {
synchronized (mutex) {return list.get(index);}
}
public E set(int index, E element) {
synchronized (mutex) {return list.set(index, element);}
}
public void add(int index, E element) {
synchronized (mutex) {list.add(index, element);}
}
public E remove(int index) {
synchronized (mutex) {return list.remove(index);}
}
public int indexOf(Object o) {
synchronized (mutex) {return list.indexOf(o);}
}
public int lastIndexOf(Object o) {
synchronized (mutex) {return list.lastIndexOf(o);}
}
public boolean addAll(int index, Collection<? extends E> c) {
synchronized (mutex) {return list.addAll(index, c);}
}
public ListIterator<E> listIterator() {
return list.listIterator(); // Must be manually synched by user
}
public ListIterator<E> listIterator(int index) {
return list.listIterator(index); // Must be manually synched by user
}
public List<E> subList(int fromIndex, int toIndex) {
synchronized (mutex) {
return new SynchronizedList<>(list.subList(fromIndex, toIndex),
mutex);
}
}
@Override
public void replaceAll(UnaryOperator<E> operator) {
synchronized (mutex) {list.replaceAll(operator);}
}
@Override
public void sort(Comparator<? super E> c) {
synchronized (mutex) {list.sort(c);}
}
/**
* SynchronizedRandomAccessList instances are serialized as
* SynchronizedList instances to allow them to be deserialized
* in pre-1.4 JREs (which do not have SynchronizedRandomAccessList).
* This method inverts the transformation. As a beneficial
* side-effect, it also grafts the RandomAccess marker onto
* SynchronizedList instances that were serialized in pre-1.4 JREs.
*
* Note: Unfortunately, SynchronizedRandomAccessList instances
* serialized in 1.4.1 and deserialized in 1.4 will become
* SynchronizedList instances, as this method was missing in 1.4.
*/
private Object readResolve() {
return (list instanceof RandomAccess
? new SynchronizedRandomAccessList<>(list)
: this);
}
}
- CopyOnWriteArrayList copyOnWriteArrayList = new CopyOnWriteArrayList();使用ReentrantLock加锁.
追问:了解CopyOnWriteArrayList吗?和Collections.synchronizedList实现线程安全有什么区别,使用场景是什么?
答:
- CopyOnWriteArrayList:执行修改操作,会拷贝一份新的数据(add/set/remove),开销比较大,修改后会将原来的集合指向新的集合,使用ReentrantLock来保证多个线程安全。
场景:适合于读多写少的场景,(读操作不需要加锁,直接获取。删除和添加需要加锁) - Collections.synchronizedList:线程安全是因为每个方法都加了synchronized同步锁.
场景:写操作比CopyOnWriteArrayList好,但是读操作不如CopyOnWriteArrayList适合,写多读少的业务场景。
追问2:CopyOnWriteArrayList的设计思想是什么?有什么缺点?
设计思想:读写分离+最终一致。缺点:内存占用问题,由于写时复制,内存里同时存再两个对象占用的内存,如果对象大则容易发生YongGC和FullGC。
注释:Copy On Write也是一种重要的思想,简称COW,在写少读多的情况下使用,如果不是写少读多的场景,
使用CopyOnWriteArrayList 开销比较大,每次更新操作(add/set/remove)都会做一次数组copy。
什么是COW?
如果多个用户同时访问相同资源,他们会获取到相同的指针指向相同的资源,直到某个用户修改资源的内容时,
系统才会真正复制一份专用的副本给该用户,而其它用户所见的最初的资源仍然保持不变。有点是如果用户没有修
改该资源,就不会有副本被创建,因此多个用户只是读取操作时可以共享同一份资源。
CopyOnWriteArrayList的特点:
①CopyOnWriteArrayList是线程安全的容器(相对于ArrayList),底层通过复制数组的方式实现.
②CopyOnWriteArrayList在遍历的时候不会抛出ConcurrentModificationException异常,并且遍历的
时候不用额外加锁.
③元素可以为null
追杀问:CopyOnWriteArrayList如何做到并发下遍历容器不发生异常?
通过查看源码iterator方法,返回COWIterator类.COWIterator中
private final Object[] snapshot;数组,根据源码可知这个数组是保存CopyOnWriteArrayList中
数据的数组.所以在迭代过程中修改原来集合的数据不会影响当前迭代器遍历.因为操作的不是同一个数组,
所以CopyOnWriteArrayList也不能保证数据的实时一致性.