JAVA学习笔记——集合

比较常用的集合一般有4种接口,每种接口可以由多种数据结构去实现。这样我们就可以在实现相同接口(功能)的情况下,根据具体场景选择不同的数据结构。

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接口是一个有顺序的集合,元素可以重复,可以通过索引去访问元素。

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个问题:

  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。

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/
posted @ 2018-01-22 13:58  水煮海鲜  阅读(140)  评论(0编辑  收藏  举报