目录

List

Set


在日常开发中,集合是我们经常用到的一种数据结构,当然,集合也并不是一种,也没有所谓的最好的集合,只有最适合的

大家用最多的是不是List和Set,不过你仔细想想,你在代码中用到的是不是都是什么ArrayList,HashSet,没有用过List和Set?

别犟,仔细看看你的代码,你会发现确实是,那当然啊,List和Set都是接口而已,接口是没法使用的,不信?看图,有没有发现都是接口~ 

java 集合的底层实现 java集合底层原理_java

java 集合的底层实现 java集合底层原理_链表_02

java 集合的底层实现 java集合底层原理_java_03

所以,我们来探讨一下,上图中的集合们

(敲键盘!好好看看)

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-正则

这次的分享就到这里啦~ 说实话,翻源码确实不容易,本人能力也有限,只是把自己知道的分享给大家,希望有误的地方,不好的地方,大家可以指正,一起进步