Java集合框架是工作中经常用到的,是很基础但是很重要的内容,本文将详述Java集合框架的内容。
Java集合框架图
- 下面是亲手绘制的一张Java集合框架图,列出了集合框架中常用的类。
- 集合其实就是一个用来盛放数据的容器,位于
java.util
包下,多线程的集合类位于java.util.concurrent
包下。 - Map接口并不属于Collection,Java集合类是由Collection和Map派生出来的。
集合和数组的区别
- 数组长度固定并且无法保存映射关系的数据。集合类可以用来保存数量不确定并且具有映射关系的数据。
- 数组可以保存基本类型的数据,也可以是引用类型的数据;而集合只能保存对象。
List集合
- List集合是一组有序可重复集合,元素都有索引,按照添加顺序排列,可以通过索引(LinkedList使用链表)快速查找,其中包含了三个常用的类,分别是ArrayList,Vector,LinkedList。下面我们逐一分析。
- ArrayList:
- 底层使用的是数组;
- 根据数组索引查找元素,所以查询快;增删因为要移动元素位置,所以增删慢;
- 元素按照插入的顺序排列,元素可以重复;
- 非线程安全
- 它的结构图如下所示:
- 注意点:如果我们在使用ArrayList的时候,提前知道会放入多少长度元素的时候,可以提前指定集合的容量,这样就会减少添加数据的时候扩容的次数,提高性能(当然数据量大时候效果更好)。比如下面这段程序,时间差异达7倍:
long date1 = System.currentTimeMillis(); ArrayList<Integer> list = new ArrayList<>(); for (int i = 0; i < 10000000; i++) { list.add(i); } System.out.println(System.currentTimeMillis() - date1); //不指定容量用时:2935 long date2 = System.currentTimeMillis(); ArrayList<Integer> list2 = new ArrayList<>(10000001); for (int i = 0; i < 10000000; i++) { list2.add(i); } System.out.println(System.currentTimeMillis() - date2); //指定容量用时:451
- Vector:
- 底层使用的是数组;
- 根据数组索引查找元素,所以查询快;增删因为要移动元素位置,所以增删慢;
- 元素按照插入的顺序排列,元素可以重复;
- 线程安全(对方法整体使用synchronized关键字加锁,效率不高)
- 它的结构图和ArrayList一样如下所示:
- LinkedList:
- 底层使用的双向链表;
- 使用链表查找元素,所以查询慢,增删只需要替换掉指针的指向即可,所以增删快;
- 元素按照插入的顺序排列,元素可以重复;
- 它的结构图如下所示:
Set集合
-
Set集合是一组无序不可重复集合(LinkedHashSet元素有序),其中包含了三个常用的类,分别是HashSet,TreeSet,LinkedHashSet。下面我们逐一分析。
-
HashSet:
- 底层是用HashMap数据结构,以存入的元素为Key,因为不关心value,所以value为同一个object,Key元素可以为null。
- 因为HashMap的Key不可重复,而我们只关心Key,所以HashSet元素不可重复,元素无序。
- 因为内部使用的是HashMap,并没有自身的数据结构,感兴趣可以查看HashMap数据结构; HashMap结构图在1.8中添加了红黑树,内部节点Node源码如下:
static class Node<K,V> implements Map.Entry<K,V> { final int hash; final K key; V value; Node<K,V> next; Node(int hash, K key, V value, Node<K,V> next) { this.hash = hash; this.key = key; this.value = value; this.next = next; } }
- 由上述源码可知,每一个节点中有一个指向下一个元素的指针next,所以HashMap的结构图如下所示(图形皆为原创,喜欢请点赞,多多支持):
-
TreeSet:
- 底层采用树结构来进行存储,内部使用的是TreeMap。
- 元素按照字符串顺序存储,元素不允许为null。
-
LinkedHashSet:
- 继承HashSet,内部采用的是LinkedHashMap数据结构。
- 和HashSet一样,内部存储元素不可重复。
- 和HashSet唯一不同的地方是元素有序。
- 因为内部使用的是LinkedHashMap,它内部的存储元素是LinkedHashMapEntry,源码如下:
static class LinkedHashMapEntry<K,V> extends HashMap.Node<K,V> { LinkedHashMapEntry<K,V> before, after; LinkedHashMapEntry(int hash, K key, V value, Node<K,V> next) { super(hash, key, value, next); } }
- 我们看到LinkedHashMapEntry继承了Node,并且在Node的基础上多了两个指针,before和afer,这两个指针就是用来记录插入的每一个元素的前后元素位置的,这样就保证了顺序。结构图如下:
Queue集合队列,不是本节的重点,会在后期单独讲解。
Map集合
-
Map集合中存储的是具有<Key,Value>映射关系的元素,它在工作中是使用最多的数据结构之一。它有几个常用的实现类,HashMap,LinkedHashMap,Hashtable,TreeMap,其实前面讲解Set的时候已经提到了,下面我们再逐个分析一下:
-
HashMap: HashSet中使用的这个数据结构
- 它通过Key的hashCode()做相关的位运算得到数组中的位置索引,如果hash散列算法以及数组长度合适的话,效率很高。
- 它可以存入键为null的键值对,JDK1.7是将其存在数组的第一个索引位置。
- 因为它不是线程安全的,并且在高并发的情况下HashMap容易出现死循环,所以多线程环境下可以使用Collections.synchronizedMap()或者HashTable,它们两个是线程安全的,但是因为HashTable是对方法整体加锁,而Collections.synchronizedMap()是对代码块加锁,所以效率不够高。
- 在高并发的环境下可以使用ConcurrentHashMap,它采用的是分段加锁机制(1.8中引入了二叉树),HashMap的结构图如下:
-
LinkedHashMap:
- 它是HashMap的子类,它和HashMap的区别是在HashMap的基础上,增加了一个双向链表来记录存储数据的先后顺序,能够保证遍历数据的时候,首先得到的是先插入的数据。
- LinkedHashMap的结构图如下:
-
HashTable:
- 它不允许存入键值对为null(Key和Value都不能为null,否则会报NullPointerException)。
- 它是线程安全的,但是因为是对方法整体加锁,所以并发性不太好。一般不使用这个类,在不要求线程安全的时候可以使用HashMap, 对线程安全有要求的时候可以使用ConcurrentHashMap。
- 数据结构图和HashMap一致。
-
- 它实现了SortedMap接口,查看put源码可以看到,key必须实现Comparable接口或者在构造TreeMap传入自定义的Comparator,否则会在运行时抛出java.lang.ClassCastException类型的异常。
- 它能够把保存的记录根据键排序,默认是按键值的升序排序,可以在构造方法中指定排序的比较器,当用Iterator遍历TreeMap时,得到的记录是排过序的。如果使用排序的映射,建议使用TreeMap。
总结
在工作中根据场景选用合适的集合数据结构,会大大增加程序的运行效率,这就要求得对集合框架非常熟悉才行。本篇中没有对并发和队列的部分做讲解,会在后期单独讲解。