JDK1.8常用容器类数据结构分析(List/Set/Map/Queue)

一、List

1、ArrayList

ArrayList内部维护了一个Object[] elementData数组,用来存放对象,同时维护了一个size,记录数组中有效对象的个数;

new ArrayList()的时候,实际就是初始化了一个空数组elementData={},当有第一个元素进入时,会初始化一个默认长度为10的数组,即elementData=new Object[10];后续添加元素如果超过了数组的长度,会按照 原长度+原长度/2的规则创建新的大数组,并把原来数组元素复制到新数组;

remove元素时则是利用了native修饰System.arraycopy()方法,将删除位置开始的后续所有元素都往前挪了一个位置

2、LinkedList

LinkedList是一个双向链表,主要维护了三个成员变量,一个size,记录链表节点数,一个first和一个last,都是Node<E>类型(LinkedList的一个静态内部类),Node对象维护了一个prev和一个next,分别指向前一个对象和后一个对象,数据结构图如下:

3、Vector

Vector跟ArrayList是一样的,内部也是数组结构,不过它的方法用了synchronized关键字,所以也说它是线程安全的ArrayList。

二、Map

1、HashMap

HashMap是数组+链表+红黑树的数据结构,所有存入HashMap的key,经过hash算法后对数组长度求余存入相应的数组index内,如果数组当前位置已经有其他Node,那么就会加入到Node的next下,形成一个单向链表,如果链表过长(大于8),会优先考虑扩展数组长度,如果数组长度也很长(大于64),就会将链表转化为一颗红黑树,数据结构图如下:

2、LinkedHashMap

LinkedHashMap继承自HashMap,整体存储方式跟HashMap一样,不过其节点对象继承自HashMap的Node,多了一个指向前一个节点的before和后一个节点的after,将所有存入的对象链接为一个双向链表,链表的顺序即为数据存入的先后顺序,相比于HashMap输出时候的无序,LinkedHashMap适用于按输入顺序输出的场景。

3、TreeMap

TreeMap是一颗红黑树,所有加入的节点,都会按照key的大小进行顺序存储,所以它是一个可以自动排序的容器,既然要排序,所以key必须是可以比较大小的,TreeMap中也提供了两种方式的比较:一种是需要写一个实现了Comparator接口的类,将这个Comparator传入TreeMap;另一种方式就是TreeMap<K,V>中的K对应的类必须实现了Comparable接口,否则运行时就会报java.lang.ClassCastException(当没有传入Comparator类时,会自动将key强转为Comparable)。

4、HashTable

HashTable内部结构同HashMap,方法上大多加了synchronized,所以它是线程安全版的HashMap,不过现在基本不用,因为有更好用的ConcurrentHashMap。

三、Set

1、HashSet

HashSet本质上就是一个HashMap,HashSet主要维护了两个对象,一个是HashMap对象,一个是类型为Object的一个常量PRESENT,对HashSet的操作都会转为对HashMap结构中key的操作,其value固定为PRESENT常量,也就是说:

HashSet.add(key)本质就是HashMap.put(key,PRESENT),由于HashMap中的key是不会重复的,所以HashSet的集合才不会有重复数据;

2、LinkedHashSet

LinkedHashSet继承自HashSet,也继承了HashSet的hashMap变量,这个hashMap=new LinkedHashMap(),所以本质上LinkedHashSet就是一个LinkedHashMap,这里有个有意思的地方是,将hashMap变量初始化为一个LinkedHashMap对象是HashSet的构造器中完成的,不过这个构造器是不公开的,所以我们直接通过HashSet时没办法初始化为LinkedHashMap,这个构造器可以被子类通过super()调用,所以等于是专门为LinkedHashSet定制的。

3、TreeSet

TreeSet本质上是一个TreeMap,TreeSet主要维护了两个对象,一个是NavigableMap对象(是一个接口,TreeMap实现了这个接口,TreeSet中NavigableMap对象的实例是一个TreeMap对象),一个是类型为Object的常量PRESENT,对TreeSet的操作都是对TreeMap的操作。

我们介绍了三个set,都是对应了前面说的三个map,所以只要学会了map也就学会了set。

四、Queue、Deque

Queue是一个单向的FIFO队列;

Deque是一个双向队列,允许从头部或尾部存入或取出数据;

队列分为阻塞队列和非阻塞队列,非阻塞队列的交互都是及时响应的,不管是否能存入数据或者取出数据,都会立马给出一个答复,要么正常返回,要么返回null或者抛出异常;而阻塞队列在队列满时存入数据会将线程阻塞,直到队列数据被消费腾出了新的空间,队列为空时,取出数据也会线程阻塞,直到队列有生产者存入数据,当然阻塞队列也不是所有的方法都会阻塞,比如用poll获取元素不会阻塞,而用take获取元素就会阻塞,具体根据实际应用场景使用。

1、非阻塞队列

1)、LinkedList

LinkedList是一个链表结构的双向队列,上面介绍List的时候应介绍过了,既可以当List用,也可以当双向队列使用。

2)、PriorityQueue

PriorityQueue是一个数组结构的单向队列,初始默认数组长度为11,每次放入或取出数据时,都会对数据进行排序,确保按数据的大小顺序依次出队,由于是对存入的数据做大小比较,所以该队列必须传入一个Comparator类,或者保证存入的对象实现了Comparable接口,否则会报ClassCastException;

PriorityQueue是一个优先级队列,出队顺序不依赖于入队顺序,而是依赖于数据本身的大小,其算法思想为堆排序,默认为小顶堆,可以通过传入Comparator实现大顶堆,其算法过程如下:

假设PriorityQueue中依此存入8 5 15 7 1

PriorityQueue用数组保存小顶堆,出队时,数组index=0的数据一定是最小值,同时,会将剩余的数据经过算法构建为新的小顶堆,保证每次出队时数组都是小顶堆,这样每次出队都是目前的最小值。

2、阻塞队列

1)、ArrayBlockingQueue

ArrayBlockingQueue是一个数组结构的单向有界队列,所以初始化时是必须指定容量的,另外其维护了一个takeIndex表示下一个出队的元素下标,维护了一个putIndex表示下一个进来的元素该存放的位置,这两个值在数组中的下标是递增的,如果到了末尾就重新定位到数组index=0的位置,这样数组内元素本身不需要因为元素的出队入队而频繁移动位置。

2)、LinkedBlockingQueue

LinkedBlockingQueue是一个双向链表结构的有界阻塞队列,其数据结构与上面说的LinkedList一样,不过因为是有界队列,所以容量是初始化时指定的,不可超出,而LinkedList可以无限自动扩展容量,直到达到资源上限;

3)、PriorityBlockingQueue

PriorityBlockingQueue是一个数组结构的无界阻塞队列,其数据结构和出队入队规则同PriorityQueue,只是使用了Reentrant和Condition实现了阻塞效果,PriorityBlockingQueue是无界队列,容量也是自动扩充的,所以它的put方法是不会阻塞的,只有take时才会由于队列中没有数据而阻塞;

4)、DelayQueue

DelayQueue是一个无界阻塞队列,内部使用的是PriorityQueue,所以它也是一个优先级队列,不过它的元素对象必须实现了Delayed接口,这个接口继承自Comparable接口,根据我们上面说的PriorityQueue机制,它是根据元素大小实现优先级的,而DelayQueue叫延时队列,它的优先级是按照时间来排序的,这个时间可以理解为剩余时间或倒计时时间,倒计时越小,优先级越高,Delayed接口提供了long getDelay(TimeUnit timeUnit)方法,队列中的每个元素调用这个方法返回剩余时间,元素类的compareTo方法比较大小时必须跟getDelay保持一致,否则剩余时间长的元素反而跑到了队列前面,就违反了这个队列的设计初衷。

五、Stack

Stack栈是一个后入先出的数据结构,它继承自Vector,所以它本身也是一个数组,存入数据时按照数组下标依此存入,而出栈数据直接从数组末尾读取,很简单的结构。

六、总结

1、上文中说的常用容器,其本质上都归类于三种数据结构:数组、链表和红黑树

2、主要类关系图(类关系过于复杂,只画出了部分接口和类的关系)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值