Java 集合概览
Java 集合, 也叫作容器,主要是由两大接口派生而来:
一个是 Collection接口,主要用于存放单一元素;
另一个是 Map 接口,主要用于存放键值对。对于Collection 接口,下面又有三个主要的子接口:List、Set 和 Queue。没有stack接口,因为stack被jdk1.6引入的Deque取代。
Java 集合框架如下图所示:
List
包含以下三种类型:
ArrayList
Vector
LinkedList
ArrayList
ArrayList:Object[] 数组。
容量capacity: 表示数组能容纳的元素个数。这里注意: new一个默认arraylist,初始化的容量是0,只有当第一次插入元素之后,容量会变为DEFAUT_CAPACITY = 10;
扩容: 当插入新元素时,会先校验数组大小是否能容纳所有元素,如果不满足时需要扩容。扩容会创建一个新的数组,将现有数组数据拷贝到新数组;
扩容大小:每次数组容量的增长大约是其原容量的1.5倍。这种操作的代价是很高的,因此在实际使用时,我们应该尽量避免数组容量的扩张。
底层采用Object[]数组存储数据,能容纳任何类型的对象。线程不安全
ArrayList支持存储NULL数据
Vector
Vector:Object[] 数组。
底层使用Object[] 存储,线程安全。
LinkedList
LinkedList:双向链表(JDK1.6 之前为循环链表,JDK1.7 取消了循环)。
头尾指针 : 链表维护了两个指针,分别指向LinkedList的头和尾当链表元素为0时,这两支针指向null。因为可以快速找到头尾元素,所以LinkedList也可以用作栈、队列和双向队列;
节点:列表中所有元素包装成Node节点存入,因为是双向链表,所以Node节点分别有一个向前和向后的支针。头节点的向前指针和尾节点的向后指针都指向null。
性能: 下标相关的操作都是线性时间,而在首段或者末尾删除元素只需要常数时间。
插入和删除: 因为链表和数组的特性差异,LinkedList的插入和删除操作不会导致前后的元素挪动,而只需指针的变换。
线程不安全:
如果需要多个线程并发访问,可以先采用collections.synchronizedList()方法对其进行包装
ArrayDeque 支持存储NULL数据
HashSet、LinkedHashSet 和 TreeSet 的主要区别在于底层数据结构不同。HashSet 的底层数据结构是哈希表(基于 HashMap 实现)。LinkedHashSet 的底层数据结构是链表和哈希表,元素的插入和取出顺序满足 FIFO。TreeSet 底层数据结构是红黑树,元素是有序的,排序的方式有自然排序和定制排序。
Set
包含以下三种类型:
HashSet
LinkedHashSet
TreeSet
HashSet
HashSet 的底层数据结构是哈希表(基于 HashMap 实现)
所有元素的的Value值统一为PRESENT;只用Map的key作为Set的集合。
LinkedHashSet
底层通过LinkedHashMap来保存所有元素。
继承HashSet,操作上与父类相同,直接调用父类方法即可。
TreeSet
基于红黑树实现
使用二叉树原理,对元素进行排序。插入元素时需要调整树的结构,将元素放到指定位置。支持自然顺序访问,但是添加、删除、包含等操作要相对低效(log(n)时间)。
因为需要排序,所以必须具备比较大小的能力。基础包装类Integer,String等可以默认支持排序。如果自定义类,则需要实现Comparable接口,定义出两个自定义类的对象如何比较大小。
Queue
PriorityQueue
Object[] 数组来实现小顶堆
- . PriorityQueue利用了二叉堆的数据结构来实现的,底层使用可变长的数组来存储数据
- . PriorityQueue通过堆元素的上浮和下沉,实现了在O(logn)的时间复杂度内插入元素和删除堆顶元素。
- . PriorityQueue是非线程安全的,且不支持存储NULL和non-comparable的对象。
- . PriorityQueue默认是小顶堆,但可以接收一个Comparator 作为构造参数,从而来自定义元素优先级的先后。
DelayQueue
底层是一个基于 PriorityQueue 实现的一个无界队列,是线程安全的。
默认情况下, DelayQueue 会按照到期时间升序编排任务。只有当元素过期时(getDelay()方法返回值小于等于 0),才能从队列中取出。
为了保证线程安全还用到了可重入锁 ReentrantLock,确保单位时间内只有一个线程可以操作延迟队列。
最后,为了实现多线程之间等待和唤醒的交互效率,DelayQueue 还用到了 Condition,通过 Condition 的 await 和 signal 方法完成多线程之间的等待唤醒。
ArrayDeque:
可扩容动态双向数组。
. ArrayDeque是基于可变长的数组和双指针来实现,
. ArrayDeque 不支持存储NULL 数据
.ArrayDeque是在JDK1.6才被引入的
. ArrayDeque插入时可能存在扩容过程,不过均摊后的插入操作依然为
o(1)
从性能的角度上,选用ArrayDeque来实现队列要比LinkedList 更好。此外,ArrayDeque 也可以用于实现栈。