常见面试题:
ArrayList和LinkedList的区别
-
ArrayList是实现了基于动态数组的数据结构, LinkedList基于链表的数据结构
-
对于随机访问get和set,ArrayList优于LinkedList,因为ArrayList可以随机定位,而LinkedList要移动指针一步一步的移动到节点处
(举例:因为ArrayList的底层是动态数组,它属于一个对象,而LinkedList是链表,它是跟很多对象有关联的,所以, 你查询的话,数组就很快了,相对Linked而言,它联系着很多的对象,查询的时候,你要把他们都查出来,这个时候从性能和时间上,linkedList就不如ArrayList!) -
对于新增和删除操作add和remove,LinedList比较占优势,只需要对指针进行修改即可,而ArrayList要移动数据来填补被删除的对象的空间。
(ArrayList在添加和删除的时候,底层是创建一个新的数组,而LinkedList却只要修改一下指针就ok了)
ArrayList的底层实现
public class ArrayList<E> extends AbstractList<E>
ArrayList的父类是AbstractList类,具体父类、接口的关系,我在HashMap详解中有集合类的关系图。
首先,我们来看一下ArrayList的数据结构,
/**
* The array buffer into which the elements of the ArrayList are stored.
* The capacity of the ArrayList is the length of this array buffer. Any
* empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
* will be expanded to DEFAULT_CAPACITY when the first element is added.
*/
transient Object[] elementData; // non-private to simplify nested class access
/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
ArrayList的数据结构
ArrayList包含两个重要的属性:elementData和size。
- size
ArrayList包含的元素的个数。
- elementData
(1)Object数组(也就是常说的ArrayList的底层是数组),所以ArrayList是不能放基本数据的(这是和数组的一大区别)。
(2)使用transient修饰。如果用transient声明一个实例变量,当对象存储时,它的值不需要维持。换句话来说就是,用transient关键字标记的成员变量不参与序列化过程。但在实际的传输过程中,我们显然是可以获取到数组中的元素的,那么这是如何实现的呢?又为什么要要用transient修饰呢?我们先看看ArrayList的构造函数已经扩容原理。
ArrayList的容量
ArrayList有两个构造函数,public ArrayList()和public ArrayList(int initialCapacity)。
/**
* Constructs an empty list with the specified initial capacity.
*
* @param initialCapacity the initial capacity of the list
* @throws IllegalArgumentException if the specified initial capacity
* is negative
*/
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
如上述的源码所示:
ArrayList()的默认capacity为10,ArrayList(int initialCapacity)的capacity为initialCapacity。
PS:size是数组中的存储的元素的个数,而capacity则是数组的容量。
ArrayList的Add
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return <tt>true</tt> (as specified by {@link Collection#add})
*/
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
ArrayList添加元素的时候,先要进行ensureCapacityInternal(size + 1)操作:即判断capacity与size+1的大小,当capacity<size+1时,就会进行resize扩容,扩容时是创建一个容量为newCapacity = oldCapacity + (oldCapacity >> 1),即扩大为原来的1.5倍的数组,然后将原来的元素给复制到新数组,代码如下:
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);
}
elementData为什么用transient修饰
前面就留下这个疑问了,现在终于可以解答了。
在上面我们知道,capacity是大于等于size的,并且绝大多数时候是大于size的。所以如果直接序列化elementData数组,那么就会浪费(capacity-size)个元素的空间,特别是当capacity-size非常大的时候,这种浪费是非常不合算的。那么elementData不被序列号,传输时又是怎么读写的呢?ArrayList实现了writeObject(java.io.ObjectOutputStream s)和readObject(java.io.ObjectInputStream s)方法。这两个方法的作用具体见什么是writeObject 和readObject?可定制的序列化过程。
/**
* Save the state of the <tt>ArrayList</tt> instance to a stream (that
* is, serialize it).
*
* @serialData The length of the array backing the <tt>ArrayList</tt>
* instance is emitted (int), followed by all of its elements
* (each an <tt>Object</tt>) in the proper order.
*/
private void writeObject(java.io.ObjectOutputStream s)
throws java.io.IOException{
// Write out element count, and any hidden stuff
int expectedModCount = modCount;
s.defaultWriteObject();
// Write out size as capacity for behavioural compatibility with clone()
s.writeInt(size);
// Write out all elements in the proper order.
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
/**
* Reconstitute the <tt>ArrayList</tt> instance from a stream (that is,
* deserialize it).
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
elementData = EMPTY_ELEMENTDATA;
// Read in size, and any hidden stuff
s.defaultReadObject();
// Read in capacity
s.readInt(); // ignored
if (size > 0) {
// be like clone(), allocate array based upon size not capacity
ensureCapacityInternal(size);
Object[] a = elementData;
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
a[i] = s.readObject();
}
}
}
ArrayList的remove操作
public E remove(int index) {
rangeCheck(index);
checkForComodification();
E result = parent.remove(parentOffset + index);
this.modCount = parent.modCount;
this.size--;
return result;
}
如上面的源码所示,ArrayList的源码,只会改变size的值,但并不会自动改变capacity的值,但ArrayList提供了trimToSize()让我们自己控制。
public void trimToSize() {
modCount++;
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
ArrayList的其他方法就懒得讲了。
LinkedList
public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable
它的父类关系具体还是见我在HashMap详解中有集合类的关系图。
LinkedList的数据结构
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
如上面源码所示,LinkedList的底层实现是一个双向链表。
LinkedList的Add操作
LinkedList提供了三个添加元素的方法:
public void addFirst(E e);
public void addLast(E e);
public boolean add(E e);
其中,add方法是在链表尾部添加元素:
public boolean add(E e) {
linkLast(e);
return true;
}
LinkedList按下标获取元素
public E get(int index) {
checkElementIndex(index);
return node(index).item;
}
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;
}
}
其他的也没什么特殊的。