JAVA中的集合主要又Collection和Map两个接口组成。其中Collection包括Set、List、Queue三个实现类,而Map接口包含TreeMap、HashMap、HashTable等实现类。下面是主要介绍。
1、Collection接口
1.1 Set接口
1. SetTreeSet:基于红⿊树实现,⽀持有序性操作,例如:根据⼀个范围查找元素的操作。但是查找效率不如 HashSet,HashSet 查找的时间复杂度为 O(1),TreeSet 则为 O(logN)。
2. HashSet:基于哈希表实现,⽀持快速查找,但不⽀持有序性操作。并且失去了元素的插⼊顺序信息,也就是 说使⽤ Iterator 遍历 HashSet 得到的结果是不确定的。
3. LinkedHashSet:具有 HashSet 的查找效率,且内部使⽤双向链表维护元素的插⼊顺序。
1.2 List接口
1. ArrayList:基于动态数组实现,⽀持随机访问。
2. Vector:和 ArrayList 类似,但它是线程安全的。
3. LinkedList:基于双向链表实现,只能顺序访问,但是可以快速地在链表中间插⼊和删除元素。不仅如此, LinkedList 还可以⽤作栈、队列和双向队列。
1.3 Queue接口
1. LinkedList:可以⽤它来实现双向队列。
2. PriorityQueue:基于堆结构实现,可以⽤它来实现优先队列。
2、Map接口
1. TreeMap:基于红⿊树实现。
2. HashMap:基于哈希表实现。
3. HashTable:和 HashMap 类似,但它是线程安全的,这意味着同⼀时刻多个线程可以同时写⼊ HashTable 并且不会导致数据不⼀致。它是遗留类,不应该去使⽤它。现在可以使⽤ ConcurrentHashMap 来⽀持线程安全,并且 ConcurrentHashMap 的效率会更⾼,因为 ConcurrentHashMap 引⼊了分段锁。
4. LinkedHashMap:使⽤双向链表来维护元素的顺序,顺序为插⼊顺序或者最近最少使⽤(LRU)顺序
3、关于JAVA集合的知识点
3.1、ArrayList的扩容机制
在具体实现中,ArrayList的扩容机制如下所示:
1、当向ArrayList中添加元素时,首先会检查ArrayList的当前大小(也就是它内部的数组大小)是否能够容纳新的元素。如果可以,那么新元素就直接被添加到ArrayList中。
2、如果ArrayList的当前大小不足以容纳新的元素,那么ArrayList就需要进行扩容操作。在扩容操作中,ArrayList会创建一个新的数组,新数组的大小是原数组大小的1.5倍(也就是原数组大小+原数组大小的一半)。这个1.5倍的值是在JDK的源码中定义的。
3、然后,ArrayList会使用System.arraycopy方法,将原有数组中的所有元素复制到新的数组中。
4、最后,新的数组会替代原有的数组,成为ArrayList的内部数组。
这个扩容机制的优点是,能够让ArrayList动态地适应数据量的变化,使得我们在使用ArrayList时,不需要关心容量问题。但是,扩容操作会消耗一定的性能,因为需要创建新的数组,并复制原有的元素。所以,如果你在创建ArrayList时,已经大致知道最终的数据量,那么可以在创建ArrayList时,指定一个接近最终数据量的初始容量,以减少扩容操作的次数,提高性能。对于ArrayList的无参构造,在添加第一个元素后,ArrayList的容量为10,然后正常扩容。
3.2、HashMap的扩容机制
一般情况下,当元素数量超过阈值时便会触发扩容。每次扩容的容量都是之前容量的2倍。
HashMap的容量是有上限的,必须小于1<<30,即1073741824。如果容量超出了这个数,则不再增长,且阈值会被设置为Integer.MAX_VALUE( 2^31-1,即永远不会超出阈值了)。
HashMap的容量变化通常存在以下几种情况:
1、空参数的构造函数:实例化的HashMap默认内部数组是null,即没有实例化。第一次调用put方法时,则会开始第一次初始化扩容,长度为16。
2、有参构造函数:用于指定容量。会根据指定的正整数找到不小于指定容量的2的幂数,将这个数设置赋值给阈值(threshold)。第一次调用put方法时,会将阈值赋值给容量,然后让
阈值=容量*负载因子。(因此并不是我们手动指定了容量就一定不会触发扩容,超过阈值后一样会扩容!!)
3、如果不是第一次扩容,则容量变为原来的2倍,阈值也变为原来的2倍。(容量和阈值都变为原来的2倍时,负载因子还是不变)
4、HashMap的底层是数组+链表+红黑树,当数组的某一索引位置上的元素以链表存在的数据个数大于8,且当前数组的长度大于64时,此时索引位置上的所有数据改为红黑树的方式存储。
此外还有几个细节需要注意:
首次put时,先会触发扩容(算是初始化),然后存入数据,然后判断是否需要扩容;
不是首次put,则不再初始化,直接存入数据,然后判断是否需要扩容;
3.3 HashSet的原理
HashSet 的实现是依赖于 HashMap 的,HashSet 的值都是存储在 HashMap 中的。在 HashSet 的构造法中会初 始化⼀个 HashMap 对象,HashSet 不允许值᯿复。因此,HashSet 的值是作为 HashMap 的 key 存储在 HashMap 中的,当存储的值已经存在时返回 false。HashSet的底层为数组+链表。
HashSet在于添加的元素不是按照顺序排列的,而是按照添加的元素的哈希值添加的,所以不支持有序操作。
对于HashSet添加的元素,必须重写hashCode方法和equals()方法,保证元素不能重复。
对于HashSet添加元素的过程主要有接下来几步。
(1)向HashSet中添加元素时,先计算元素的哈希值。
(2)判断哈希值对应的位置有没有元素,没有元素直接添加
(3)如果有元素,使用equals()方法判断是否相等,如果不相等就添加。