从源码角度解析 Java 集合框架

上次我的一篇博客《史上最最最简单的MVP教程》发布以后,很多人看了都觉得不错,浅显易懂,我也很高兴,谢谢那些支持我的人,我会做得更好。

ArrayList
允许重复的元素,允许null值,非线程同步的(Vector是同步的),其内部实现还是数组

    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }

    public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

contains方法用来判断某个List中是否存在满足条件的object,我们看到它的返回值是indexOf(o)>=0;那我们来看看indexOf这个方法干嘛用的。首先判断这个Object是否为null,如果是null,那系统就会遍历列表中的数据,看看是否有没有哪个元素的值为null,如果有,就返回它的具体位置,如果没有,就返回-1。else代码块的思路也差不多,我就不啰嗦了。

    /**
     * Returns a shallow copy of this ArrayList instance.  (The
     * elements themselves are not copied.)
     *
     * @return a clone of this ArrayList instance
     */
    public Object clone() {
        try {
            @SuppressWarnings("unchecked")
                ArrayList
    
      v = (ArrayList
     
      ) super.clone(); v.elementData = Arrays.copyOf(elementData, size); v.modCount = 0; return v; } catch (CloneNotSupportedException e) { // this shouldn't happen, since we are Cloneable throw new InternalError(); } }
     
    

这个是克隆方法,从注释中可以看到,该克隆方法是“浅拷贝”,也就是说这种克隆方法只会克隆一个引用。举个栗子,我有一个list列表,该list列表有一个引用,这个引用就是我们的变量名,现在我克隆一下,会发生这样的事:我现在有两个变量名同时指向一个list列表,听懂了吧,所以只要其中一个变量名对列表进行了操作,那另外一个变量名访问列表的时候就是操作之后的数据。与之相反的还有一种“深拷倍”,深拷倍是什么,你们自己去研究吧,总要留点事情给你们自己做做,不能一动不动,要勤劳,知识要自己去收集

add方法不看源码,因为涉及到的方法很多,粘贴代码也挺麻烦的。直接给你们结论吧,结论:每次调用add方法的时候,系统会检测数据量是否超过ArrayList的最大承受范围,如果超过了,就先将ArrayList的容量扩大一倍,接着才会进行元素的添加;反之,如果数据量没有超过范围,那就正常添加数据,不会对ArrayList进行扩充。

remove方法

public E remove(int index) {
        rangeCheck(index);

        modCount++;
        E oldValue = elementData(index);

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

系统先检查要remove元素的下标是否越界,如果没有越界,就将该元素后面的所有元素往前移一位,最后将需要删除的那个元素赋值null,目的是通知垃圾回收机制去回收该对象。注意到System.arraycopy这个方法了吗,该方法就是用来实现“将某个元素后面的所有元素往前移动一位”

ArrayList中的Iterator,这相当于一个遍历器,能够依次访问到每个元素

ArrayList中的一些常见的方法都分析完了,其他的如果想自己分析,就查看JDK源码吧。与arraylist类似的还有LinkedList,内部是一个双向链表,学过数据结构的同学都知道,链表的插入元素和删除元素的时候比较方便,所以当数据量非常大的时候,用LinkedList更高效。

不知道大家注意到没有,modCount这个变量几乎在所有的方法里都碰到了,我只知道它是用来计算ArrayList的修改次数的,但是为什么要计算修改次数,我也不是很清楚,如果有人知道的话,麻烦告诉我,谢谢啊

HashSet
开发中HashSet用的比较多,它不允许重复的元素,允许null值,而且是无序的,它内部是基于HashMap实现的。它有两个关键的因数,一个是capacity,另一个是load factor。官方建议不要将capacity设置太大或者将load factor设置太小。如果capacity设置太大,bucket就放不满,占用过多无效的内存空间;如果load factor设置过小,系统就会频繁得给bucket扩容,影响程序的运行效率。

    /**
     * Constructs a new, empty set; the backing HashMap instance has
     * default initial capacity (16) and load factor (0.75).
     */
    public HashSet() {
        map = new HashMap<>();
    }

从源码中可以看出,默认的capacity是16,load factor是0.75。具体意思就是当里面的元素个数达到16*0.75=12时,系统就会为HashSet扩容,扩容到当前的两倍

    public int size() {
        return map.size();
    }
    public boolean contains(Object o) {
        return map.containsKey(o);
    }
    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }
    public boolean remove(Object o) {
        return map.remove(o)==PRESENT;
    }
    public void clear() {
        map.clear();
    }

这几个方法内部都是通过HashMap来实现的,其他方法不赘述,其实都差不多 由于HashSet是无序的,所以没有办法通过正常的循环结构来遍历其中的数据,JDK给我们提供了Iterator接口,使用方法和ArrayList中的Iterator方法一样 与HashSet类似的还有LinkedHashSet,看名字就能猜得出来,内部实现原理是链表;TreeMap,可以用来排序的Set,初始化时候需要传一个Comparator参数

HashMap
HashMap允许空值,而Hashtable不允许空值 这里我们重点看put和get方法

    public V put(K key, V value) {
        //如果HashMap内部的table是空的,就实例化一个table
        if (table == EMPTY_TABLE) {
            inflateTable(threshold);
        }
        //如果key为null,就调用putForNullKey()
        if (key == null)
            return putForNullKey(value);
        //如果key不为null,就找到key的hashcode所对应的地址,在该地址里面遍历所有的entry,看是否有相同的元素,相同则替换旧的value,不同就添加该元素
        int hash = hash(key);
        int i = indexFor(hash, table.length);
        for (Entry
    
      e = table[i]; e != null; e = e.next) { Object k; if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash, key, value, i); return null; }
    

get方法原理跟这个差不多,就不赘述了
HashMap中值得注意的一个知识点是,要判断两个元素是否相等,必须要同时满足hashcode和equals相同,只要有其中一个值不相同,就视为不同元素
与HashMap类似的一些集合类使用方法跟它差不多,所有就不多说了,可以自己查看JDK源码学习

有关HashCode的知识点,我觉得大家有必要去了解一下,这个东西说难不难,说简单也不简单,知道原理就行,不资道的人还是去找点资料看看吧。

学会看源码是一个程序员必备的技能!

完整的代码已经上传到Github上,地址:github:https://github.com/Elder-Wu/Notes

喜欢看我的博客的人可以点个赞,收藏下,我会持续更新技术文章的。
我的微信公众号:代码也是人,欢迎大家来踩点

《从源码角度解析 Java 集合框架》

技术交流群:471395156(赶紧入坑吧)

    原文作者:HashMap源码分析
    原文地址: https://juejin.im/entry/5810090dda2f60004f4795b4
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞