JAVA学习笔记——集合

比较经常使用的集合通常有4种接口,每种接口能够由多种数据结构去实现。这样咱们就能够在实现相同接口(功能)的状况下,根据具体场景选择不一样的数据结构。java

Interface Hash Table Resizable Array Balanced Tree Linked List Hash Table + Linked List
Set HashSet TreeSet LinkedHashSet
List ArrayList LinkedList
Deque ArrayDeque LinkedList
Map HashMap TreeMap LinkedHashMap

1. List

ArrayList、LinkedList、Vector都实现了接口List。List接口是一个有顺序的集合,元素能够重复,能够经过索引去访问元素。api

1.1 ArrayList

ArrayList类维护了一个数组,访问操做效率高,删除和插入操做效率低。数组

transient Object[] elementData; // non-private to simplify nested class access

当增长元素且ArrayList的容量不够时,须要对数组进行扩容,扩容使用Arrays.copyOf()方法。
当删除元素时,会使用System.arraycopy()方法。
以上两个copy方法都是蛮费时间的。缓存

1.2 LinkedList

LinkedList类维护了一个双端链表,first和last分别指向链表的头尾Node。安全

1.3 Vector

Vector类与ArrayList类很是相像,主要的区别是:Vector是线程安全的,ArratList不是线程安全的。数据结构

protected Object[] elementData;

1.3.1 Stack

Stack类继承了Vector类,由于Vector类维护了一个数组,在数组基础上实现LIFO的功能仍是很方便的。但这也会带来2个问题:oracle

  1. Stack类是基于数组实现的,push和pop操做的开销会比较大。
  2. Stack可使用Vector设置任意位置元素的方法setElementAt(E obj, int index),那么Stack就不是纯粹的栈,栈只能操做栈顶的元素。

JDK文档建议,若是要使用栈这种数据结构,应该优先使用Deque<Integer> stack = new ArrayDeque<Integer>();优化

2. Deque

Deque是Double End Queue的意思,该双端队列便可以用做FIFO的队列,也能够用做LIFO的栈。Stack类也是栈,但它有些缺点,应该优先使用Deque做为栈。线程

2.1 LinkedList

LinkedList类维护了一个双端链表,first和last分别指向链表的头尾Node。code

2.2 ArrayDeque

ArrayDeque是在JDK1.6中才出现的。以数组的形式实现双端队列。
JDK文档建议:

  1. 当要使用栈时,ArrayDeque比Stack要高效;
  2. 当要使用队列时,ArrayDeque要比LinkedList要高效。

第1点的缘由在上面说过解释过,第2点的缘由有如下两点:

  1. LinkedList每次push一次须要new一个Node,每次pop一次会产生一个Node垃圾等待回收。ArrayDeque因为是数组,push不会new,pop不会产生垃圾,虽然在容量不够时,仍是会作扩大一倍容量的操做。
  2. LinkedList在内存中是分散的,因此缓存命中率会比较低。

3. Map

3.1 HashMap

HashMap(散列表)是一个增删、查找都高效的数据结构。查找散列表通常分为2个步骤:

  1. 计算键的hash值;
  2. 找到相应的位置并解决冲突,放入该键值对。
    解决冲突有2种方法:拉链法和开放地址法(线性探测、二次探测、随机探测)

java中的HashMap采用的是拉链法,这是数组和链表的结合形式。

HashMap中的put()方法:

  1. 计算哈希值。经过hashcode()计算出该对象key的hash值
  2. 哈希值取模。对hash值进行取模操做hash%length,length是数组的长度。该长度应该是2的幂方,这样咱们就能够对取模操做进行优化hash&(length-1)
  3. 插入链表。获得模值以后,就将其做为索引找到相应的链表表头,该条链表中存储hash值同样的键值对,键值对存储在静态内部类Entry中。Entry保存键、值以及指向下一个键值对的next。

HashMap中的resize()方法:
当entry的数量大于阈值时,开始resize()。其中阈值是容量乘以加载因子。
resize()以后,table长度变为2倍,从新取模,从新链表插入。

负载因子:当hashmap中的有效容量除以总容量大于负载因子时,hashmap的容量就会翻倍。java的hashmap默认容量是16,负载因子时0.75。
容量:

Java8对HashMap的改进:当链表的长度大于必定值时,链表就转换为红黑树,实现了快速的增删改查,时间复杂度从顺序查找的O(n)优化到O(logn)。

HashTable与HashMap很是类似,他们有如下三点区别:

  1. HashTable是synchronized的,是线程安全的,多个线程能够共享;HashMap相反
  2. HashTable是synchronized的,在单线程下速度会比较慢;HashMap的速度比较快
  3. HashTable不能接受null的键和值;HashMap则能够

【Reference】

  1. JDK API文档 http://docs.oracle.com/javase/8/docs/api/
相关文章
相关标签/搜索