回顾

Java集合主要分为两个体系结构,Collection和Map。这篇博客主要介绍Collection子接口List下的三个经常使用的实现类:ArrayList、Vector和LinkedList。

详细内容参见《Java基础——集合》

先看下关系图:

1、ArrayList

这是List最常用的实现类,想一想为什么他最常用?

Array,在java中意为“数组”。猜想ArrayList和数组应该关系很密切,其实ArrayList可以看作是一个可以改变大小的数组。

举个简单的例子吧,看下他的使用:

       ArrayList<String> list1 = new ArrayList<>();
list1.add("a");
list1.add("b");
list1.add("c");
list1.set(2, "d");
Iterator<String> iter = list1.iterator();
while (iter.hasNext()) {
System.out.println(iter.next());
}

之后,我们就从源码看看他是如何设计的吧。

1)构造函数

提示:

默认ArrayList长度为10;

用于保存数据的elementData本身就是个Object[];

ArrayList提供了三种构造函数,具体逻辑可以看下面的代码。

public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{ //默认容量为10
private static final int DEFAULT_CAPACITY = 10; private static final Object[] EMPTY_ELEMENTDATA = {}; //调用无参数构造函数时,给一个空数据
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //保存元素的数据
transient Object[] elementData; //1、无参数构造函数,默认空数组
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//2、给定初始容量的构造函数
public ArrayList(int initialCapacity) {
//大于0时,创建一个Object数据,长度为传入的容量值
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
//等于0时,给一个空数组
this.elementData = EMPTY_ELEMENTDATA;
} else {
//其他抛出容量不合法异常
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
} //3、给定集合对象的构造函数
public ArrayList(Collection<? extends E> c) {
//放入数组
elementData = c.toArray();
//数组长度不等于0时,将进行复制
if ((size = elementData.length) != 0) {
// c.toArray might (incorrectly) not return Object[] (see 6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
否则,返回空数组
this.elementData = EMPTY_ELEMENTDATA;
}
} }

2)动态扩充

在添加元素时,会涉及到扩充的问题,其中核心方法是grow()。

提示:

“>>” 右移运算符:

在二进制中比较容易理解,这里不是重点,大致相当于值的二分之一。因此,有的书上会说ArrayList扩容一次会增加原来的一半,就是从这里看出来的。

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

2、Vector

Vector和ArrayList几乎完全相同,直接看源码就知道了,瞬间有种剽窃的赶脚……

但其实不是的,Vector是比ArrayList出现的要早的,也正是因为这样,很多方法都比较陈旧了,并不推荐适用男。

这里贴了一小部分代码,和ArrayList几乎一样,就不占用空间了,有兴趣可以去java.util.Vector里面翻一翻。

public class Vector<E>
extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{ protected Object[] elementData;
protected int elementCount;
public Vector() {
this(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;
}

当然Vector也有一些自己的特点,随便拿几个方法来看看。他的方法中几乎都用到了synchronized关键字,开销会比较大,而且运行会比较慢。

 public synchronized void copyInto(Object[] anArray) {
System.arraycopy(elementData, 0, anArray, 0, elementCount);
}
public synchronized void setSize(int newSize) {
}
public synchronized int capacity() {
return elementData.length;
}
//数组个数
public synchronized int size() {
return elementCount;
}
//获取元素
public Enumeration<E> elements() {
return new Enumeration<E>() {
int count = 0; public boolean hasMoreElements() {
return count < elementCount;
} public E nextElement() {
synchronized (Vector.this) {
if (count < elementCount) {
return elementData(count++);
}
}
throw new NoSuchElementException("Vector Enumeration");
}
};
}

3、LinkedList

是一个双链表,在add和remove时比ArrayList性能好,但get和set时就特别慢了。

恶补下:

双向链表,链表的一种。每个数据结点中都有两个指针,分别指向直接前驱和直接后继。因此,我们可以方便的访问他的前驱结点和后继结点。




下图是一个双向链表的图,element为元素,pre指向直接前驱(前一个 元素),next指向直接后继(后一个元素)。而LinkedList还不只是双向链表,他是双向循环链表。也就是第一个pre指针指向最后一个节点,最后一个节点的next指针指向第一个节点,形成一个环路,而不是下图中的Null。


下面来看下代码

public class LinkedList<E>
extends AbstractSequentialList<E>
implements List<E>, Deque<E>, Cloneable, java.io.Serializable{
//元素个数
transient int size = 0;
//相当于pre指针
transient Node<E> first;
//相当于next指针
transient Node<E> last; //Node内部类(双链表结构)
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;
}
}
......
}

Tips:

Deque,他是双端队列的接口,支持在两端插入和移除元素。

public interface Deque extends Queue {}

那么如何进行操作呢?这里以在中间位置添加元素为例。

代码实现如下


//在指定位置添加元素
public void add(int index, E element) {
//检查索引是否有效
checkPositionIndex(index); //索引值等于集合大小,直接添加在末尾
if (index == size)
linkLast(element);
else
linkBefore(element, node(index));
} //返回索引处的非空Node
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;
}
} //中间插入元素(核心方法!!!!)
void linkBefore(E e, Node<E> succ) { final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}

对于性能这块,在大数据量重复试验下得出的结论才比较有说服力,小编就随手贴上自己的测试结果,有兴趣看看就可以了。

测试代码差不多都是这样子的……

ArrayList<Integer> arrayList = new ArrayList<Integer>();
LinkedList<Integer> linkedList = new LinkedList<Integer>(); // add操作
int count = 100000; System.out.println("ArrayList--add()-------------------");
long startTime = System.nanoTime();
for (int i = 0; i < count; i++) {
arrayList.add(i);
}
long endTime = System.nanoTime();
System.out.println(endTime - startTime + "--------"); System.out.println("LinkedList--add()-------------------");
startTime = System.nanoTime();
for (int i = 0; i < count; i++) {
linkedList.add(i);
}
endTime = System.nanoTime();
System.out.println(endTime - startTime + "--------");

循环1W次

循环10W次

循环100W次

等一下……

等一下……

再等一下……

出去接杯水……

结果还是这个样子,直接给个图吧,LinkedList还没get出来(⊙﹏⊙)b

嗯……这篇差不多了,over

最新文章

  1. JCEF 如何修改右键菜单项(JCEF在右键菜单中添加开发者选项-show dev tools)
  2. unity3d CarWaypoints插件
  3. 智能车学习(十四)&mdash;&mdash;K60单片机GPIO学习
  4. ASP.NET反射(转载)
  5. BadgeView的使用介绍
  6. Class.forName(&quot;ClassName&quot;)与ClassName.class的区别
  7. OpenJudge计算概论-计算书费
  8. HTML5之 WebWorkers
  9. (转)为什么adrl r2,mem_cfg_val这里不用ldr r2,=mem_cfg_val
  10. 从string.size()和string.length()聊到长度的问题和一个关于数据结构定义的技巧
  11. c++virtual inline 是否冲突
  12. Android SimpleAdapter GridView (网格图片点击放大显示)
  13. 前端UI组件复用工具
  14. 从arduino到32单片机的转型
  15. Mysql--执行计划 Explain
  16. PLSQL Developer使用技巧
  17. Java接口和抽象类以及接口的意义,instanceof的利用
  18. 关于跨DB增量(增、改)同步两张表的数据小技巧
  19. css和HTML布局小技巧
  20. 4.json解析

热门文章

  1. vue和react总结
  2. css3 笔记
  3. c++后台开发 准备材料
  4. C++最接近整数的浮点运算
  5. leetcode笔记(三)207. Course Schedule
  6. Percona-Tookit工具包之pt-index-usage
  7. JSP/Servlet开发——第六章 JSP开发业务应用
  8. CentOS7.2中安装MongoDB
  9. FROM_UNIXTIME
  10. LINUX SSH 建立密钥对