Java集合底层原理

目录

  • ArrayList集合源码
    • 创建ArrayList集合
    • 扩容机制
  • LinkedList集合源码
    • 添加数据
  • 迭代器源码
  • HashSet底层原理
  • HashMap源码
    • 创建HashMap对象
    • 添加元素
  • TreeMap源码
    • 基本属性与构造器
    • 添加元素

以下源码来自JDK11

ArrayList集合源码

创建ArrayList集合

/*
无参构造,返回一个空数组
参数:elementData - ArrayList集合维护的数组
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/*
* 有参构造
* 构造一个具有指定初始容量的空数组。
* 参数:initialCapacity - 列表的初始容量
* 抛出:IllegalArgumentException – 如果指定的初始容量为负
* */
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+                initialCapacity);
    }
}

扩容机制

	//第一次添加元素
	public boolean add(E e) {
        modCount++;//记录集合被修改的次数,防止多个线程同时修改
        /*
        第一个参数:添加的元素
        第二个参数:数组名
        第三个参数:集合长度/现在元素的插入位置索引
        */
        add(e, elementData, size);
        return true;
    }

	
    private void add(E e, Object[] elementData, int s) {
        //如果集合长度与数组长度相同,则需要扩容
        if (s == elementData.length)
            elementData = grow();
        elementData[s] = e;//赋值
        size = s + 1;//集合长度加一,亦表示下个元素插入位置
    }


    private Object[] grow() {
        return grow(size + 1);//传入参数表示添加完当前元素后的最小容量
    }


    private Object[] grow(int minCapacity) {
        /*
        copyOf方法会创建一个长度为newCapacity(minCapacity)的新数组,并将elementData数组中的元素全部复制到新数组当中去
        */
        return elementData = Arrays.copyOf(elementData,
                                           newCapacity(minCapacity));
    }


    private int newCapacity(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;//记录老容量
        //新容量等于老容量加上老容量的一半,即老容量的1.5倍
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //作新容量是否小于等于最小容量判断
        if (newCapacity - minCapacity <= 0) {
            //如果数组是初始的空数组(即第一次添加元素),返回默认容量大小10和最小容量值两者中的最大值
            if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
                return Math.max(DEFAULT_CAPACITY, minCapacity);
            /*如果最小容量值小于0,抛出内存溢出异常,最小容量超过整数最大值Integer.MAX_VALUE时就会内存溢出*/
            if (minCapacity < 0) // overflow
                throw new OutOfMemoryError();
            //返回最小容量值
            return minCapacity;
        }
        /*新容量值大于最小容量值时,判断新容量值是否小于等于数组容量最大值,满足则返回新容量值,否则使用hugeCapacity方法确保不会扩容得太大*/
        return (newCapacity - MAX_ARRAY_SIZE <= 0)
            ? newCapacity
            : hugeCapacity(minCapacity);
    }


    private static int hugeCapacity(int minCapacity) {
        /*如果最小容量值小于0,抛出内存溢出异常,最小容量超过整数最大值Integer.MAX_VALUE时就会内存溢出*/
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        //判断最小容量值是否超过了数组最大容量值,超过则返回整数最大值,否则返回数组最大值
        return (minCapacity > MAX_ARRAY_SIZE)
            ? Integer.MAX_VALUE
            : MAX_ARRAY_SIZE;
    }

LinkedList集合源码

添加数据

    public boolean add(E e) {
        linkLast(e);
        return true;
    }

    void linkLast(E e) {
        //记录尾指针所指向的元素
        final Node<E> l = last;
        //创建新节点,将l作为新节点的前一个节点,指向后一个节点的指针赋值为null
        final Node<E> newNode = new Node<>(l, e, null);
        //将新节点赋值给尾指针
        last = newNode;
        //如果尾指针初始为null,说明当前插入的节点是第一个节点,让first指向它
        if (l == null)
            first = newNode;
        else
            //当前插入的不是第一个节点,将新节点赋值给插入前的尾结点的next指针
            l.next = newNode;
        size++;
        modCount++;
    }

//Node类是LinkedList的静态内部类
    private static class Node<E> {
        //元素值
        E item;
        //指向后一个节点的指针
        Node<E> next;
		//指向前一个节点的指针
        Node<E> prev;

        Node(Node<E> prev, E element, Node<E> next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }

迭代器源码

    ArrayList<String> strings = new ArrayList<>();
    strings.add("aaa");
    strings.add("bbb");
    strings.add("ccc");
	//获取迭代器对象
    Iterator<String> iterator = strings.iterator();

	//调用Itr的无参构造,返回一个迭代器对象
    public Iterator<E> iterator() {
        return new Itr();
    }

	//ArrayList的内部类
    private class Itr implements Iterator<E> {
        int cursor;       // 光标,迭代器的指针,初始值为0
        int lastRet = -1; // 上一个被遍历元素的索引
        int expectedModCount = modCount;//创建迭代器时,集合被添加/删除的总次数

        // 无参构造
        Itr() {}

        //光标不等于集合元素总数时,光标所指位置有值
        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            //检查此时集合有没有被修改(添加/删除)
            checkForComodification();
            int i = cursor;
            //当前光标的值超过集合总元素个数,抛出不存在此元素异常
            if (i >= size)
                throw new NoSuchElementException();
            //获取集合存储数据的数组
            Object[] elementData = ArrayList.this.elementData;
            //当前光标的值超过数组长度,抛出并发修改异常
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            //移动光标位置,指向下一个元素
            cursor = i + 1;
            //返回光标移动前位置的元素,并将其索引赋值给lastRet
            return (E) elementData[lastRet = i];
        }

        final void checkForComodification() {
            //当前集合的被操作次数与创建迭代器时不一致,抛出并发修改异常
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

HashSet底层原理

创建HashSet集合时,创建一个默认长度16,默认加载因子为0.75的数组,数组名为table

添加元素时,根据元素的哈希值跟数组长度计算出应存入位置的索引值

若该位置为null,直接将元素插入进去,若不为null,比较当前元素与该位置元素是否相等,相等则不插入

JDK8以前,不相等的情况 使用头插法,将新元素插入到数组中,旧元素挂在新元素下面,形成链表

JDK8以后,不相等时使用尾插法,即直接将新元素挂在旧元素下面,形成链表

且在JDK8以后,当链表长度超过8并且数组长度大于等于64时,将链表转换为红黑树

若集合中存储的时自定义对象,必须要重写hashCode()和equals()方法

当数组中的元素个数达到数组长度的75%(加载因子)时,数组将扩容为原来的2倍

HashMap源码

创建HashMap对象

//HashMap的三个重要成员变量
//数组默认初始长度
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
//数组最大长度
static final int MAXIMUM_CAPACITY = 1 << 30;
//数组默认加载因子
static final float DEFAULT_LOAD_FACTOR = 0.75f;


//无参构造
public HashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR; // all other fields defaulted
}

添加元素

考虑三种情况

  1. 数组位置为null
  2. 数组位置不为null,键不重复,挂在数组下面形成链表或红黑树
  3. 数组位置不为null,键重复,覆盖原来的值
//put方法传入当前添加元素的key和value
/*
调用putval()方法
第一个参数:调用hash()方法,hash方法使用hashCode()方法得到了key对应的hash值,并进行了一定的处理
简单理解:第一个参数就是根据key获取的hash值
第二个参数:key
第三个参数:value
第四个参数:key相同时是否覆盖,true表示不覆盖,false表示覆盖
第五个参数:不相关,不做解释
*/
public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

static final int hash(Object key) {
    int h;
    //h = key.hashCode()计算key的hash值
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}


//核心方法
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    //定义一个局部变量,用于记录hash表中table的地址值
    Node<K,V>[] tab;
    //第三方变量,用于记录集合中元素的地址值
    Node<K,V> p;
    //n用于记录数组长度,i用于记录索引
    int n, i;
    //第一次添加元素时,table为null
    if ((tab = table) == null || (n = tab.length) == 0)
        //resize方法创建一个默认长度为16,加载因子为0.75的数组赋值给tab,并将数组长度赋值给n
        n = (tab = resize()).length;
    /*使用当前插入元素的key和数组长度减一做与运算得到应该插入的位置索引,并获取该位置的元素赋值给p,
    判断p是否为空,即判断要插入的位置是否为空*/
    if ((p = tab[i = (n - 1) & hash]) == null)
        //要插入的位置为空,创建一个新节点并插入
        tab[i] = newNode(hash, key, value, null);
    else {
        //要插入的位置不为空
        Node<K,V> e;
        K k;
        /*判断该位置元素的hash值与新元素的hash值是否相等,若相等再次判断该位置的元素与新元素的key是否相等,若也相等,将该位置元素交由e保存,留待进行值覆盖操作*/
        if (p.hash == hash &&
            ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;
        /*判断获取到的数组元素是否是红黑树中的节点,若是,则调用putTreeVal方法将新元素按照红黑树规则添加到树当中去*/
        else if (p instanceof TreeNode)
            e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {
            //获取到的数组元素是链表节点,使用binCount记录数组中的元素个数
            for (int binCount = 0; ; ++binCount) {
                /*获取p所指向节点的下一个节点,赋值给e(第一次判断时,p代表的是从数组中获取到的元素,即判断该元素下面是否还有节点),判断是否为空*/
                if ((e = p.next) == null) {
                    //为空,则创建一个新的节点挂在下面
                    p.next = newNode(hash, key, value, null);
                    //判断链表长度是否超过8,超过则调用treeifyBin方法
                    //treeifyBin方法底层还会继续判断数组长度是否大于等于64
                    //如果两个条件同时满足,就会将数组转为红黑树
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash);
                    //跳出循环
                    break;
                }
                //不为空,判断e与要插入的节点hash值是否相同,key是否相同
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    //相同,跳出循环,留待进行值覆盖操作
                    break;
                //不相同,将e赋值给p,继续往下遍历
                p = e;
            }
        }
        //判断e是否为空,若为空,表示本次插入没有元素被覆盖,若不为空说明需要做覆盖操作
        if (e != null) { 
            //记录e的存储的值
            V oldValue = e.value;
            //判断是否做覆盖操作
            if (!onlyIfAbsent || oldValue == null)
                //将新元素的值赋值给e
                e.value = value;
            //不相关,不做解释
            afterNodeAccess(e);
            //返回被覆盖的值
            return oldValue;
        }
    }
    ++modCount;
    //判断数组中的元素是否达到了扩容阈值,未达到阈值,不做操作
    if (++size > threshold)
        //达到扩容阈值(16*0.75=12),resize方法会将数组扩容为原来的两倍,并将旧数组中的元素全部复制到新数组中去
        resize();
    //不相关,不做解释
    afterNodeInsertion(evict);
    //返回null表示没有元素被覆盖
    return null;
}

TreeMap源码

基本属性与构造器

	//比较器
	private final Comparator<? super K> comparator;
	//根节点
    private transient Entry<K,V> root;
	//节点个数
    private transient int size = 0;
	//空参构造
    public TreeMap() {
        comparator = null;
    }
	//有参构造,传入构造器赋值
    public TreeMap(Comparator<? super K> comparator) {
        this.comparator = comparator;
    }

	//使用静态常量表示节点颜色,增加代码可读性
    private static final boolean RED   = false;
    private static final boolean BLACK = true;

	//静态内部类,Entry,集合中的元素都以Entry的类型存储
    static final class Entry<K,V> implements Map.Entry<K,V> {
        //键
        K key;
        //值
        V value;
        //左节点
        Entry<K,V> left;
        //右节点
        Entry<K,V> right;
        //父节点
        Entry<K,V> parent;
        //节点颜色
        boolean color = BLACK;
        ...
    }

添加元素

public V put(K key, V value) {
	//记录根节点
    Entry<K,V> t = root;
    //判断根节点是否为空
    if (t == null) {
        compare(key, key); 
		//为空,将插入的节点作为根节点
        root = new Entry<>(key, value, null);
        size = 1;
        modCount++;
        return null;
    }
    //用于记录两个元素键比较的差值
    int cmp;
    //用于记录新插入节点的父节点
    Entry<K,V> parent;
	//获取集合的比较器
    Comparator<? super K> cpr = comparator;
    if (cpr != null) {
        //集合指定了比较器
        do {
            //第一次循环,将根节点作为父节点
            parent = t;
            //比较新插入元素的key和根元素的key,将差值赋值给cmp
            cmp = cpr.compare(key, t.key);
            if (cmp < 0)
            //cmp小于0,说明新元素要放在t节点的左边,故将t节点的左节点赋值给t
                t = t.left;
            else if (cmp > 0)
                //cmp大于0,说明新元素要放在t节点的右边,故将t节点的右节点赋值给t
                t = t.right;
            else
                //cmp等于0,说明新元素与t节点键相同,做值覆盖操作
                return t.setValue(value);
        } while (t != null);
    }
    else {
        //集合未被指定比较器,使用默认比较规则
        //如果传入的key为null,抛出空指针异常
        if (key == null)
            throw new NullPointerException();
        //压制未检查警告
        @SuppressWarnings("unchecked")
        //将新元素的key强转为Comparable类型,要求key必须实现了Comparable接口,否则报错
        Comparable<? super K> k = (Comparable<? super K>) key;
        do {
            //下列代码与上面相同,不做解释
            parent = t;
            cmp = k.compareTo(t.key);
            if (cmp < 0)
                t = t.left;
            else if (cmp > 0)
                t = t.right;
            else
                return t.setValue(value);
        } while (t != null);
    }
    //遍历完红黑树,没有覆盖元素,做插入操作,创建新节点
    Entry<K,V> e = new Entry<>(key, value, parent);
    if (cmp < 0)
        parent.left = e;
    else
        parent.right = e;
    //从新插入节点开始调整红黑树
    fixAfterInsertion(e);
    size++;
    modCount++;
    return null;
}


private void fixAfterInsertion(Entry<K,V> x) {
    //将当前节点颜色设为红色,红黑树节点默认为红色
    x.color = RED;
	//按照红黑树规则调整节点
    //判断当前节点是否为根节点,父节点是否为红色,若父节点为黑色则不需要调整
    while (x != null && x != root && x.parent.color == RED) {
        //判断当前节点的父节点是否为爷爷节点的左节点
        if (parentOf(x) == leftOf(parentOf(parentOf(x)))) {
            //当前节点的父节点是爷爷节点的左节点
            //获取当前节点的叔叔节点,即当前节点爷爷节点的右节点
            Entry<K,V> y = rightOf(parentOf(parentOf(x)));
            //判断叔叔节点的是否为红色
            if (colorOf(y) == RED) {
                //叔叔节点为红色
                //将父节点设为黑色
                setColor(parentOf(x), BLACK);
                //将叔叔节点设为黑色
                setColor(y, BLACK);
                //将爷爷节点设为红色
                setColor(parentOf(parentOf(x)), RED);
                //将爷爷节点赋值给当前节点
                x = parentOf(parentOf(x));
            } else {
                //叔叔节点为黑色
                //判断当前节点是否为父节点的右节点
                if (x == rightOf(parentOf(x))) {
                    //当前节点是父节点的右节点
                    //将父节点作为当前节点
                    x = parentOf(x);
                    //以父节点为当前节点进行左旋
                    rotateLeft(x);
                }
                //将父节点设为黑色
                setColor(parentOf(x), BLACK);
                //将爷爷节点设为红色
                setColor(parentOf(parentOf(x)), RED);
                //以爷爷节点为当前节点右旋
                rotateRight(parentOf(parentOf(x)));
            }
        } else {
            //当前节点的父节点是爷爷节点的右节点,以下代码与上面相同,不再解释
            Entry<K,V> y = leftOf(parentOf(parentOf(x)));
            if (colorOf(y) == RED) {
                setColor(parentOf(x), BLACK);
                setColor(y, BLACK);
                setColor(parentOf(parentOf(x)), RED);
                x = parentOf(parentOf(x));
            } else {
                if (x == leftOf(parentOf(x))) {
                    x = parentOf(x);
                    rotateRight(x);
                }
                setColor(parentOf(x), BLACK);
                setColor(parentOf(parentOf(x)), RED);
                rotateLeft(parentOf(parentOf(x)));
            }
        }
    }
    //将根节点颜色设为黑色
    root.color = BLACK;
}

你可能感兴趣的:(Java源码,java)