目录
List
Set
在日常开发中,集合是我们经常用到的一种数据结构,当然,集合也并不是一种,也没有所谓的最好的集合,只有最适合的
大家用最多的是不是List和Set,不过你仔细想想,你在代码中用到的是不是都是什么ArrayList,HashSet,没有用过List和Set?
别犟,仔细看看你的代码,你会发现确实是,那当然啊,List和Set都是接口而已,接口是没法使用的,不信?看图,有没有发现都是接口~
所以,我们来探讨一下,上图中的集合们
(敲键盘!好好看看)
List
首先来讲 List,List接口不能用但是它的孩子可以用啊,常用的嘛,当然就是ArrayList和LinkedList,那这两个有什么区别呢?
1、ArrayList,看名字就能看出来,数组,没错,跟数组有关,ArrayList底层是数组哦~
2、LinkedList,这个,底层是双向链表哦~
对比一下这两个,有没有发现什么?没有?那你仔细想想数组和链表的特性,想想顺序存储结构和链式存储结构,想起来没?
所以啊,当你的功能中,很少进行插入和删除操作时,采用顺序存储结构,也就是数组,也就是ArrayList比较合适,而当你的功能对数据有较频繁的插入啊或者删除类的操作时,就适合用LinkedList了
这个时候你可能有一个疑问,我用ArrayList的时候没指定它的长度啊,不是底层是数组么,数组不是要指定长度么?很好,你发现了,说明你记得数组的使用,现在来解答,ArrayList是一个动态数组,当你使用无参构造方法去创建时,它会创建一个空数组
来,先创建一个ArrayList
public static void main(String[] args) {
List<Object> objects = new ArrayList<>();
}
然后我们来往下翻它!
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
是不是好奇这个 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 是啥?翻译一下子,是不是字面意思是 默认空容量数组 ,是不是想到了什么,但是又更好奇了,来,继续看
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
那你可能好奇,既然是空数组,那我怎么加进去的元素,我们来看看 add方法
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
是不是调用了一个 ensureCapacityInternal 方法,我们来看看这个方法
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
哦?又调用了另一个方法 calculateCapacity ,翻译过来是计算容量,那我们继续往下
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
你看,关键点来了,有一个判断,判断当前的数组是否等于空数组,你会发现有一个常量 DEFAULT_CAPACITY,我们去看看它是什么
private static final int DEFAULT_CAPACITY = 10;
天呐,你发现了什么,它是10!!!
也就是说,如果你通过无参构造方法创建一个ArrayList,经过这个容量计算,它会返回10!!! OK,我们继续调用链,接着看 ensureExplicitCapacity 确保内部容量方法
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
// overflow-conscious code
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
咦~ 它有一个判断,如果大于当前数组的长度,那就扩容!!! 我们来看看扩容方法
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}
当你看到了扩容方法最后一行,是不是明白了,扩容其实就是创建一个新数组,将老数组的数据放进去,也并没有加锁什么的,所以说,不管是出于效率还是出于安全什么,插入操作多的话,不推荐用底层为数组的ArrayList,既然到这儿了,我们继续往下翻翻看,你看,他就是一堆判断,一堆扩容,你会发现,除了第一次,后续的扩容量是当前容量的1.5倍,但是,你有发现了一个新参数, MAX_ARRAY_SIZE,看名字就知道,最大数组大小,我们看看它是多少
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
现在明白了吧?连底层都带你翻了可是
只要是List,那就有这么几个共性:元素有序、元素可重复、含带索引的方法
Set
1、HashSet,无序,底层是一个哈希表,JDK8之前是数组+单链表,JDK8之后是数组+单链表/数组+红黑树(为了提高查询效率)
2、LinkEdHashSet,有序,底层是哈希表加链表,JDK8之前是数组+单链表+单链表,JDK8之后是数组+单链表+单链表/数组+红黑树+单链表
顺嘴一提,哈希表就是数组+链表
无论是 HashSet 还是 LinkEdHashSet ,它们有一个共同点,那就是元素不可重复,所以 Set 这种结构可以用来做去重处理,当然,我说的是 Set 可以用来做去重,不是只有 Set 可以用来去重,至于为什么呢?就四个字,哈希冲突,相同元素的哈希值是相同的,这个时候就会发生哈希冲突。
PS >>> 滴滴面试这个去重必问,一般一面二面都会问到,至于三面四面不知道咯~ 提供几个思路哈:1-Set / 2-Map / 3-循环 /4-正则
这次的分享就到这里啦~ 说实话,翻源码确实不容易,本人能力也有限,只是把自己知道的分享给大家,希望有误的地方,不好的地方,大家可以指正,一起进步