您的位置:首页 > 理论基础 > 数据结构算法

[Java8 Collection源码+算法+数据结构]-List(二)

2016-04-08 19:10 453 查看
本人大二学生党,最近研究JDK源码,顺便复习一下数据结构与算法的知识,所以就想写这些系列文章。这是[Java Collection源码+算法+数据结构]系列的第二篇。

上篇文章中,了解了
Map
的基本原理,现在我们来了解
List
家族。

简述

An ordered collection (also known as a sequence). The user of this interface has precise control over where in the list each element is inserted. The user can access elements by their integer index (position in the list), and search for elements in the list.

List
就是一个列表,可以存储重复值,可以存储null。且
List
接口继承了
Collection
接口,下面看看
Collection


The root interface in the collection hierarchy. A collection represents a group of objects, known as its elements. Some collections allow duplicate elements and others do not. Some are ordered and others unordered. The JDK does not provide any direct implementations of this interface: it provides implementations of more specific subinterfaces like Set and List. This interface is typically used to pass collections around and manipulate them where maximum generality is desired.

Collection
是类集框架的root,
List
Set
都是继承
Collection
的,但是
Collection
接口本身没什么卵用。。所以还是继续回到
List
里面把。

ArrayList

ArrayList
是我们用的最多的一个实现类,下面一起走进去看看源码

要点1:

/**
* The size of the ArrayList (the number of elements it contains).
*
* @serial
*/
private int size;
/**
* 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


可以看到
List
采用数组存储数据,就和
Array
相对应咯,然后
size
属性是存储元素的个数(真正的有多少个元素,而不是elementData的大小,因为elementData.length为elementData占用了多少的内存块,可以有些没有存储对象,所以size表示真正的元素的个数)

要点2:

/**
* Default initial capacity.
*/
private static final int DEFAULT_CAPACITY = 10;


List
Map
一样(
Map
默认16, 2<<4),也是有默认的长度的,不够的时候就会扩容。怎么扩容呢?在每此向
List
里面添加元素的时候,就会去检查是否超出了容量。

public boolean add(E e) {
ensureCapacityInternal(size + 1);  // Increments modCount!!
elementData[size++] = e;
return true;
}
private void ensureCapacityInternal(int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
}

ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {
modCount++;

// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
/**
* Increases the capacity to ensure that it can hold at least the
* number of elements specified by the minimum capacity argument.
*
* @param minCapacity the desired minimum capacity
*/
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);
}


最终在
grow
里面可以明确的看到却是扩容了,

int newCapacity = oldCapacity + (oldCapacity >> 1);
也就是扩大了1.5倍(那么思考一哈:
HashMap
一次扩容多少呢?)。

但是扩容了就有一个问题—–会有多余的空间,而这些空间却没有存储对象。如果当内存不足的时候,就是一个麻烦的事啦,别怕。

/**
* Trims the capacity of this <tt>ArrayList</tt> instance to be the
* list's current size.  An application can use this operation to minimize
* the storage of an <tt>ArrayList</tt> instance.
*/
public void trimToSize() {
modCount++;
//真正的元素的个数 < elementData的大小
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
//在这里调用Array.copyOf(T[] original, int newLength)之后会进行说明
}
}


这样就会把那些多余的空间给释放掉咯。

要点3:

那么
List
怎么添加数据,修改数据,删除数据呢?

下面进入代码里面看看:

//先检查是否index越界,然后想数组一样直接取
public E get(int index) {
rangeCheck(index);

return elementData(index);
}
//修改数据
//先检查是否index越界,然后存进去返回oldValue
public E set(int index, E element) {
rangeCheck(index);

E oldValue = elementData(index);
elementData[index] = element;
return oldValue;
}
//首先检查容量是否足够,然后再存进去
public boolean add(E e) {
ensureCapacityInternal(size + 1);  // Increments modCount!!
elementData[size++] = e;
return true;
}
public void add(int index, E element) {
rangeCheckForAdd(index);

ensureCapacityInternal(size + 1);  // Increments modCount!!
System.arraycopy(elementData, index, elementData, index + 1,
size - index);
elementData[index] = element;
size++;
}

public E remove(int index) {
rangeCheck(index);

modCount++;
E oldValue = elementData(index);

int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work

return oldValue;

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;
}


上面的代码应该都不难,只有有一个函数需要注意:

System.arraycopy()
Arrays.copyOf()
。下面进去看一哈:

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
@SuppressWarnings("unchecked")
T[] copy = ((Object)newType == (Object)Object[].class)
? (T[]) new Object[newLength]
: (T[]) Array.newInstance(newType.getComponentType(), newLength);
System.arraycopy(original, 0, copy, 0,
Math.min(original.length, newLength));
return copy;
}

/* @param      src      the source array.
* @param      srcPos   starting position in the source array.
* @param      dest     the destination array.
* @param      destPos  starting position in the destination data.
* @param      length   the number of array elements to be copied. */
public static native void arraycopy(Object src,  int  srcPos,
Object dest, int destPos,
int length);


copyOf
方法里面我们可以看到重新创建了一个
copy
数组,然后再调用
arraycopy
方法。
arraycopy
方法还是很好理解的,是一个native方法。
copyOf
方法可以看成
arraycopy
的一个封装。所以我们只需要了解
copyOf
方法。下面看看怎么用的:

@Test
public void testCopy() {
int[] arr = {1, 2, 3, 4, 5};

int[] copied = Arrays.copyOf(arr, 10); //产生了一个新的数组
System.out.println(Arrays.toString(copied));

copied = Arrays.copyOf(arr, 3);
System.out.println(Arrays.toString(copied));
}


输出:

[1, 2, 3, 4, 5, 0, 0, 0, 0, 0]
[1, 2, 3]


LinkedList

LinkedList
其实是一个双向链表,但是由于实现了
Deque
接口,可以看作队列,也可以看出栈。

public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable


下面看看队列是怎么实现的:

Queue


-
offer()
入队列

-
poll()
出队列,然后删除对首元素

-
peek()
取对首元素,但不删除对收元素

-
element()
取对首元素,与
peek()
的区别就是
element()
不能返回null

Stack


-
push()
压栈

-
pop()
出栈(删除)

-
peek()
出栈(不删除)

-
element()
出栈(不删除,而且不能为null)

下面一起看看这些方法:

public E peek() {
final Node<E> f = first;
return (f == null) ? null : f.item;
}
public E element() {
return getFirst();
}
public E poll() {
final Node<E> f = first;
return (f == null) ? null : unlinkFirst(f);
}
public boolean offer(E e) {
return add(e);
}
public void push(E e) {
addFirst(e);
}
public E pop() {
return removeFirst();
}


还是调用的其他方法:

public E removeFirst() {
final Node<E> f = first;
if (f == null)
throw new NoSuchElementException();
return unlinkFirst(f);
}
public E removeLast() {
final Node<E> l = last;
if (l == null)
throw new NoSuchElementException();
return unlinkLast(l);
}
/**
* Unlinks non-null node x.
*/
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;
}

/**
* Links e as first element.
*/
private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}

/**
* 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++;
}


到头还是2个方法,在链表的头部和链表的尾部进行操作,没什么好说的啦。

比较

首先看看
ArrayList
的签名:

public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable


哦豁!
RandomAccess
是什么鬼?

/**
* Marker interface used by <tt>List</tt> implementations to indicate that
* they support fast (generally constant time) random access.  The primary
* purpose of this interface is to allow generic algorithms to alter their
* behavior to provide good performance when applied to either random or
* sequential access lists.
**/
public interface RandomAccess {
}


这是一个标记接口,和
Serializable
一样,实现这个接口代表可以实现fast (generally constant time) random access。主要目的就是帮助算法改进其行为。

if(list instanceof RandomAccess){
for (int i=0, n=list.size(); i++)
list.get(i);
}
else{
for (Iterator i=list.iterator(); i.hasNext(); )
i.next();
}


对于实现
RandomAccess
接口的,上面的遍历比下面的快,所以可以通过
instanceof
关键字来判断使用那种方法遍历。

参考文献

所以对于
ArrayList
使用for循环变量最快

LinkedList
使用foreach或者iterator遍历快

HashMap
使用
EntrySet
+foreach遍历最快啦)

邮箱:19063632072@qq.com
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  java List Set