集合源码分析

目录

一.集合的基本数据结构

1.数组

2.链表

3.树

二叉树

红黑树:

 二.集合

1.框图

接口

工具类

比较器

集合的特点

List接口

一.ArrayList

 1.ArrayList中的几个初始状态

2.初始操作

3.add方法

4.get方法

5.set方法

6.remove方法

7.FaiFast机制

二.LinkedList

 1.Node类

2.push方法

3.add方法

4.get方法

5.set方法

三.Vector

Set接口

一.HashSet

二.TreeSet

Map接口

一.TreeMap

二.HashMap


一.集合的基本数据结构

1.数组

特点:

  1. 内存地址连续,使用之前必须要指定数组长度
  2. 可以通过下标访问的方式访问成员,查询效率高
  3. 增删操作会给系统带来性能消耗[保证数据下标越界的问题,需要动态扩容]

2.链表

特点:

  1. 灵活的空间要求,存储空间不要求连续
  2. 不支持下标的访问,支持顺序遍历检索
  3. 针对增删效率会更高些,只和操作节点的前后节点,无需移动元素存储位置

3.树

二叉树

二叉树优点:

  1. 某节点的左子树节点值仅包含小于该节点值
  2. 某节点的右子树节点值仅包含大于该节点值
  3. 左右子树每个也必须是二叉查找树
  4. 顺序排列

二叉树相当于综合了数组和链表的优点,但是他仍然存在问题,就是当我们插入的数据是一个递增或者是递减的情况,二叉树就会退化成相当于链表。

红黑树:

红黑树,Red-Black Tree [RBT]是一个自平衡【不是绝对】的二叉查找树,树上的节点满足如下的规则:

  1. 每个节点要么是红色,要么是黑色。
  2. 根节点必须是黑色
  3. 每个叶子节点【NIL】是黑色
  4. 每个红色节点的两个子节点必须是黑色
  5. 任意节点到每个叶子节点的路径包含相同数量的黑节点

红黑树能自平衡,它靠的是什么?三种操作∶左旋、右旋和变色

  • 左旋:以某个结点作为支点(旋转结点),其右子结点变为旋转结点的父结点,右子结点的左子结点变为旋转结点的右子结点,左子结点保持不变。
  • 右旋:以某个结点作为支点(旋转结点),其左子结点变为旋转结点的父结点,左子结点的右子结点变为旋转结点的左子结点,右子结点保持不变。
  • 变色:结点的颜色由红变黑或由黑变红。

红黑树插入场景:

 二.集合

1.框图

接口

  • Collection接口
  • Map接口
  • Iterator迭代

工具类

  • Collections :可以把map list 变成线程安全的  也可以把map转换成set等等
  • Arrays :可以数组转list  sort排序 copy数组

比较器

  • Comparable
  • Comparator

集合的特点

  • Set能够存储唯一的列的数据(唯一,不可重复)
  • List能够存储可以重复的数据(可重复)
  • Map值的顺序取决于键的顺序
  • Map键和值都是可以存储null元素的

List接口

一.ArrayList

本质上就是个动态数组

特点:查询快(可以根据下标直接取),增删慢(没次增删都涉及到数组复制来移位),线程不安全。

 1.ArrayList中的几个初始状态


    /**
     * 默认初始容量
     */
    private static final int DEFAULT_CAPACITY = 10;

    /**
     * 用于空实例的共享空数组实例
     */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    /**
     *用于默认大小的空实例的共享空数组实例。 我们将其与 EMPTY_ELEMENTDATA 区分开来,    
     *以了解添加第一个元素时要膨胀多少。
     */
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    /**
     * ArrayList 的元素存储在其中的数组缓冲区。 ArrayList 的容量就是这个数组缓冲区的长度            
     *添加第一个元素时,任何带有 elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA 的空 
     *ArrayList 都将扩展为 DEFAULT_CAPACITY。
     */
    transient Object[] elementData; // non-private to simplify nested class access

    /**
     * ArrayList 的大小(它包含的元素数)。
     */
    private int size;

2.初始操作

无参构造

//按照初始容量10构造一个空列表  直接赋给elementDatat一个空数组
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

有参构造 

/**
 *构造一个具有特定初始容量的列表
 *参数:initialCapacity
 *抛出:如果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);
        }
    }

3.add方法

    public boolean add(E e) {
        //确定容量 动态扩容
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //添加元素
        elementData[size++] = e;
        return true;
    }

    private void ensureCapacityInternal(int minCapacity) {
        //如果采用无参构造创建ArrayList 则第一次add返回10
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

    private void ensureExplicitCapacity(int minCapacity) {
        //自增的
        modCount++;

        //如果要插入元素的位置大于当前的最大容量就要进行扩容
        if (minCapacity - elementData.length > 0)
            //grow是扩容的方法
            grow(minCapacity);
    }


    private void grow(int minCapacity) {
        // oldCapacity 原来的容量
        int oldCapacity = elementData.length;
        //old + old >> 1 相当于 old * 1.5 但是前者的效率会更高
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        //如果扩容后的容量还是不够 那就把当前要插入的容量赋给new 
        if (newCapacity - minCapacity < 0)
            newCapacity = minCapacity;
        //MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
        //要分配的数组的最大大小。 一些 VM 在数组中保留一些头字。 尝试分配更大的数组                 
        //可能会导致OutOfMemoryError:请求的数组大小超出 VM 限制
        if (newCapacity - MAX_ARRAY_SIZE > 0)
            newCapacity = hugeCapacity(minCapacity);
        // minCapacity is usually close to size, so this is a win:
        //确定完新容量大小后  会把elementData以新容量复制然后再赋给elementData
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

4.get方法

    public E get(int index) {
        //检查下标是否越界
        rangeCheck(index);

        return elementData(index);
    }

    private void rangeCheck(int index) {
        //如果越界 抛出异常
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

5.set方法

    public E set(int index, E element) {
        //检查下标是否越界
        rangeCheck(index);
        //获取要被替换的元素值
        E oldValue = elementData(index);
        elementData[index] = element;
        //返回被替换元素的值
        return oldValue;
    }

6.remove方法

     public E remove(int index) {
        //检查下标是否越界
        rangeCheck(index);

        modCount++;
        //获取要被删除的元素 {1,2,3,4,5,6,7,8,9} index = 3
        E oldValue = elementData(index);
        //9 - 3 - 1 = 5 要移动的元素个数
        int numMoved = size - index - 1;
        if (numMoved > 0)
            //原数组 开始下标   目标数组 开始下标  长度
            //{5,6,7,8,9}    {1,2,3,5,6,7,8,9,null}
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
        //返回删除的元素
        return oldValue;
    }

7.FaiFast机制

        快速失败的机制,Java集合类为了应对并发访问在集合迭代过程中,内部结构发生变化的一种防护措施,这种错误检查的机制为这种可能发生错误通过抛出java.util.ConcurrentModificationException         

ArrayList的add和remove方法都会使modCount++  当modCount与expectedModCount不相等的时候就会触发这个FF机制

二.LinkedList

LinkedList是通过双向链表区实现的,所以他拥有双向链表的特性,既然是双向链表,那么他的顺序访问效率会很高,而随机访问效率会比较低,它包含一个非常重要的私有内部类:Node。LinkedList的不能像ArrayList的哪像可以取到特定的元素,LinkedList只能通过顺序的遍历,当然它也有优化的地方,就是比如我们要查找元素的下标为index,他首先会判断index 与 size>>1的大小,如果index较小,说明这个元素在链表的前半段,那么我们就从头部开始遍历;如果index比较大就说明这个元素在链表的后半段,那么我们就从尾部开始遍历。

特点:查询慢(因为是链表,只能顺序遍历,不能根据下标直接取值),增删快,线程不安全

遍历LinkedList最好使用迭代器的方式,如果用for循环遍历的话,需要使用get方法,而LinkedList的买次get方法都要先判断这个下标在前半段还是后半段,然后再选择从头遍历,还是从尾遍历,所以时间复杂度去向O(n^2)

 1.Node类

    private static class Node<E> {
        //item代表存储的数据元素
        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;
        }
    }

2.push方法

    //在列表的开头插入指定元素   
    public void push(E e) {
        addFirst(e);
    }

    public void addFirst(E e) {
        linkFirst(e);
    }

    private void linkFirst(E e) {
        //把头节点赋给f
        final Node<E> f = first;
        //以要插入的元素创建一个新的节点
        final Node<E> newNode = new Node<>(null, e, f);
        //把这个新节点作为头节点
        first = newNode;
        if (f == null)
        //说明列表在插入元素前为空 所以插入的这个节点既是头节点又是尾节点
            last = newNode;
        else
            //把原来链表头的prev指向新的头节点
            f.prev = newNode;
        size++;
        modCount++;
    }

3.add方法

    //添加到列表的尾部
    public boolean add(E e) {
        linkLast(e);
        return true;
    }
    //与linkFirst逻辑一样
    void linkLast(E e) {
        final Node<E> l = last;
        final Node<E> newNode = new Node<>(l, e, null);
        last = newNode;
        if (l == null)
            first = newNode;
        else
            l.next = newNode;
        size++;
        modCount++;
    }

4.get方法

    public E get(int index) {
        //检查元素的索引 就是判断index是否小于size
        checkElementIndex(index);
        return node(index).item;
    }

    Node<E> node(int index) {
        // assert isElementIndex(index);
        //先把index与size的一般比较
        //如果比一般小 就从头部开始遍历
        if (index < (size >> 1)) {
            Node<E> x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
        //否则就会从尾部开始遍历
            Node<E> x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }

5.set方法

    public E set(int index, E element) {
        //检查元素的索引是否合法
        checkElementIndex(index);
        //找出要修改位置的节点
        Node<E> x = node(index);
        //获取该节点的元素
        E oldVal = x.item;
        //把要修改的值赋给item
        x.item = element;
        //返回被修改的元素
        return oldVal;
    }

三.Vector

Vector和ArrayList很类似,都是以动态数组的方式来存储数据的。

vector是线程安全的,因为他在每个操作方法上都加有synchronized关键字,所以性能较差,慢慢的就被放弃了。

那我们应该如何保证ArrayyList的线程安全呢?

    //方式一
    List list = Collections.synchronizedList(new ArrayList());
    //方式二 加了ReentrantLock锁
    List<String> list1 = new CopyOnWriteArrayList();

可以使用Collections工具类下的synchronizedList方法把ArrayList转化为线程安全的list.这种方法可以提高代码的灵活度。

Set接口

一.HashSet

HashSet实现Set接口,HashSet的本质是一个"没有重复元素"的集合,他是通过HashMap实现的.由哈希表支持,它不保证set的迭代顺序,特别是它不保证该顺序永久不变,允许使用null。 

    public HashSet() {
        map = new HashMap<>();
    }

add方法

    public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

本质上就是创建一个默认大小的HashMap,将数据存放再HashMap中,key就是我们要添的元素,value是自定以的一个Object对象,是个虚拟值没有实际的意义。

二.TreeSet

         基于TreeMap的 NavigableSet实现。使用元素的自然顺序对元素进行排序,或者根据创建set时提供的Comparator进行排序,具体取决于使用的构造方法。

    public TreeSet() {
        this(new TreeMap<E,Object>());
    }

本质是将数据保存在TreeMap中,key是我们添加的内容,value是定义的一个Object对象。

Map接口

一.TreeMap

自定义实现内部比较器Comparable接口并重写compareTo方法,才可以使用TreeMap实现有序存储

1.Entry<k, v>类

 //树中的节点   
 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;

        /**
         * Make a new cell with given key, value, and parent, and with
         * {@code null} child links, and BLACK color.
         */
        Entry(K key, V value, Entry<K,V> parent) {
            this.key = key;
            this.value = value;
            this.parent = parent;
        }
    }

2.put方法

    public V put(K key, V value) {
        //获取根节点 第一次put时root应该==null
        Entry<K,V> t = root;
        //初始操作
        if (t == null) {
            //检查key是否为空
            compare(key, key); // type (and possibly null) check
            //以k,v创建节点 赋给根节点
            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent; //父节点
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator; //比较器 如果使用其键的自然顺序,则为null
        if (cpr != null) {
            do {
                //第一次先把根部赋给parent  也就是从根部开始遍历
                parent = t; 
                //把插入元素的key和t的key比较
                cmp = cpr.compare(key, t.key);
                //说明得向左遍历
                if (cmp < 0)
                    t = t.left;
                //说明得向有遍历
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        else {
            if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
                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);
        }
        //以k,v创建entry对象
        Entry<K,V> e = new Entry<>(key, value, parent);
        //如果比父节点小 就放在left
        if (cmp < 0)
            parent.left = e;
        else
        //如果比父节点大 就放在right
            parent.right = e;

        fixAfterInsertion(e); //实现红黑树的自平衡
        size++;
        modCount++;
        return null;

3. fixAfterInsertion方法

    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);
                    //将祖父节点设置为插入节点 (继续while循环)
                    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;
    }

左旋

    private void rotateLeft(Entry<K,V> p) {
    if (p != null) {
        Entry<K,V> r = p.right;
        p.right = r.left;
        if (r.left != null)
            r.left.parent = p;
        r.parent = p.parent;
        if (p.parent == null)
            root = r;
        else if (p.parent.left == p)
            p.parent.left = r;
        else
            p.parent.right = r;
        r.left = p;
        p.parent = r;
    }
}

右旋

    private void rotateRight(Entry<K,V> p) {
        if (p != null) {
            Entry<K,V> l = p.left;
            p.left = l.right;
            if (l.right != null) l.right.parent = p;
            l.parent = p.parent;
            if (p.parent == null)
                root = l;
            else if (p.parent.right == p)
                p.parent.right = l;
            else p.parent.left = l;
            l.right = p;
            p.parent = l;
        }
    }

二.HashMap

HashMap底层结构
Jdk1.7及以前是采用数组+链表
Jdk1.8之后采用数组+链表+红黑树方式进行元素的存储。存储在hashMap集合中的元素都将是一个Map. Entry的内部接口的实现。

1.HashMap中的几个初始状态

    //HashMap中数组的默认容量 16
    static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16
    //HashMap中数组的最大容量
    static final int MAXIMUM_CAPACITY = 1 << 30;
    //默认的负载因子 0.75
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    //链表树化的最小节点 8
    static final int TREEIFY_THRESHOLD = 8;
    //红黑树转换成链表的节点数 6
    static final int UNTREEIFY_THRESHOLD = 6;
    //链表树化的最小容量 64
    static final int MIN_TREEIFY_CAPACITY = 64;
    //HashMap中的数组结构
    transient Node<K,V>[] table;
    //HashMap中的元素个数
    transient int size;
    //扩容的临界值(容量*负载因子)
    int threshold;
    //操作HashMap的次数
    transient int modCount;
    //哈希表的负载因子(我创建时候指定的)
    final float loadFactor;

2.put方法原理分析

    public V put(K key, V value) {
        return putVal(hash(key), key, value, false, true);
    }

hash(key):获取key对应的hash值

    static final int hash(Object key) {
        int h;
        //key.hashCode()会得到32位二进制的数
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

为什么要右移16位 再进行异或?

A:100001000111000100o001111000000
B:0111011100111000101000010100000

如果直接取hashCode的话会使他们的高位参与不到计算,因为后面我们算他在数组中的下标时会与数组的容量-1进行与运算,那么A和B对15 11111 的与运算会得到相同的值,所以会造成散列不均匀。

    final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        //初始table为null
        if ((tab = table) == null || (n = tab.length) == 0)
            //resize()初始化数组容量和扩容 第一次以容量16进行初始化
            n = (tab = resize()).length;//n获取容量长度 16
        //n保证是2的次幂 所以n-1高位是0地位是1 
        //再与上我们的hash一定能得到个小于我们容量的值 这个值作为它在数组中的下标
        if ((p = tab[i = (n - 1) & hash]) == null)
            //如果这个下标==null 说明它里面没值 直接放进去
            tab[i] = newNode(hash, key, value, null);
        else {
            //说明发生了hash碰撞
            Node<K,V> e; K k;
            if (p.hash == hash &&
                //如果他们的hash值相等
                //他们的key也相等
                ((k = p.key) == key || (key != null && key.equals(k))))
                //新插入的把原来的干掉
                e = p;
            else if (p instanceof TreeNode)
                //如果是红黑树
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                //如果是链表结构
                for (int binCount = 0; ; ++binCount) {
                    //遍历到链表的尾部
                    if ((e = p.next) == null) {
                        //将它插入链表的尾部
                        p.next = newNode(hash, key, value, null);
                        //判断是否满足树化的节点数  是否>=8
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            //链表转红黑树方法
                            treeifyBin(tab, hash);
                        break;
                    }
                    //判断是否右 hash值 和 key值相同节点
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        break;
                    //继续往下遍历
                    p = e;
                }
            }
            if (e != null) { // existing mapping for key
                V oldValue = e.value;
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }

resize()动态扩容

    final Node<K,V>[] resize() {
        //获取原来的数组
        Node<K,V>[] oldTab = table;
        //获取原来数组容量大小
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        //扩容的临界值
        int oldThr = threshold;
        //定义新数组容量 
        int newCap, newThr = 0;
        if (oldCap > 0) {
            //如果原来的容量 已经达到了最大容量
            if (oldCap >= MAXIMUM_CAPACITY) {
                //把扩容的临界值设置为最大值 大约20亿
                threshold = Integer.MAX_VALUE;
                //返回原来的数组  无法扩容了
                return oldTab;
            }
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                //如果扩容*2后依旧小于最大容量 而且原数组容量大于默认容量
                //扩容的临界值也*2
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            //原来的临界值设为新的容量
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        //设置新的扩容临界值
        threshold = newThr;
        //以新容量创建数组
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        //如果原来数组中就一个值 直接赋值
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        //如果是红黑树
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        //如果是链表
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        do {
                            next = e.next;
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }

HashMap面试回答

三.ConcurrentHashMap

线程安全 采用分段锁 ,读不加锁,写加锁

四.LinkedHashMap

可以扩展LRU

五.IdentityHashMap

无序,线程不安全 去重采用System.identityHashCode

六.WeakHashMap

弱引用:当key被回收时, value若没有强引用,则会被回收
且会加入Referenceueue队列,通过expungeStaleEntries清除

适合做本地缓存 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

w7486

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值