Java ArrayList HashMap 底层分析及扩容方法

ArrayList源码分析:

常量:

private static final int DEFAULT_CAPACITY = 10;//默认容量,即数组长度为10
private static final Object[] EMPTY_ELEMENTDATA = {};
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//初始化时,默认使用这个空数组
transient Object[] elementData; // non-private to simplify nested class access 数组
private int size;

构造函数:

// 默认的无参构造函数 一般我们是这样创建的
public ArrayList() { 
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; //默认空数组常量
}
// 有参构造函数 参数为容量大小
public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];//大于0,直接创建一个initialCapacity大小的数组对象
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;//空数组是我们上面那个常量
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+initialCapacity);//小于0直接报错
    }
}
// 有参构造函数,参数是一个Collection
 public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();//将c转化为数组
        if ((size = elementData.length) != 0) {
            // c.toArray might (incorrectly) not return Object[] (see 6260652) 不返回Object
            if (elementData.getClass() != Object[].class)
                elementData = Arrays.copyOf(elementData, size, Object[].class);
        } else {
            // replace with empty array.
            this.elementData = EMPTY_ELEMENTDATA; 
        }
    }

add()方法

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // size默认是0 
    elementData[size++] = e;
    return true;
}


ensureCapacityInternal()

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));//调用了这个方法
}
//可以发现,只有在数组为刚创建的时候这个函数才返回DEFAULT_CAPACITY 10 否则都是minCapacity 那么为什么要抽象出一个方法呢?
private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
    }

ensureExplicitCapacity(int minCapacity)

private void ensureExplicitCapacity(int minCapacity) {
    modCount++;
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)//如果现有的容量大于数组的长度
        grow(minCapacity);//扩容函数
}

重头戏 grow(int minCapacity)

private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;//旧的长度即数组的长度
    int newCapacity = oldCapacity + (oldCapacity >> 1);//新的长度1.5倍 为什么用位不用除?算的比较快
    if (newCapacity - minCapacity < 0)//这里为什么不直接比大小呢
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)//新的长度大于最大长度
        newCapacity = hugeCapacity(minCapacity); //看下面的代码  就是比最大大的话就是  Integer.MAX_VALUE 否则就是  MAX_ARRAY_SIZE = Integer.MAX_VALUE-8
    // minCapacity is usually close to size, so this is a win: 这英语是什么意思啊?
    elementData = Arrays.copyOf(elementData, newCapacity);
}

private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ? 
        Integer.MAX_VALUE :
    MAX_ARRAY_SIZE;
}

ensureCapacity 我们提前知道打算插入比较多的数据的时候,先调用一下这个方法,一次性扩容,省得一直扩容

public void ensureCapacity(int minCapacity) {
    int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
        // any size if not default element table
        ? 0
        // larger than default for default empty table. It's already
        // supposed to be at default size.
        : DEFAULT_CAPACITY;

    if (minCapacity > minExpand) {
        ensureExplicitCapacity(minCapacity);//事实上也是调用ensureExplicitCapacity
    }
}

我们可以发现,在ArrayList中大量使用 Arrays.copyOf()和System.arraycopy

例如,add()插到具体位置的方法

public void add(int index, E element) {
    rangeCheckForAdd(index);

    ensureCapacityInternal(size + 1);  // Increments modCount!!

    // elementData原数组 index 起始位置 elementData 目标数组 index + 1 开始存放的下标 size - index 长度 
    
    System.arraycopy(elementData, index, elementData, index + 1,size - index); 
    elementData[index] = element;
    size++;
}

事实上两个方法底层都是System.arraycopy ,而这个方法的底层是Native方法

public static <T,U> T[] copyOf(U[] original, int newLength, Class<? extends T[]> newType) {
    @SuppressWarnings("unchecked")
    T[] copy = ((Object)newType == (Object)Object[].class)
        ? (T[]) new Object[newLength]
        : (T[]) Array.newInstance(newType.getComponentType(), newLength);
    System.arraycopy(original, 0, copy, 0,
                     Math.min(original.length, newLength));//可以发现底层也是调用的 System.arraycopy
    return copy;
}
   // 从源数组src取元素,范围为下标srcPos到srcPos+length-1,取出共length个元素,存放到目标数组中,存放位置为下标destPos到destPos+length-1 
public static native void arraycopy(Object src,  int  srcPos,  Object dest, int destPos,
                                        int length);

Map 的底层原理

 //初始化hashmap的时候先创建一个长度为0的Node类型的数组 
//在执行put()操作的时候会初始化扩容 长度16 
//put的时候计算2次Key的hashcode 以保证散列
//找到数组的位置,对键值对进行操作
//扩容
// 如果数组的某个地方有位置的时候,判断一下数组的实际长度是否达到扩容的要求,若是就扩容

常量:

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;//扩容阈值
static final int TREEIFY_THRESHOLD = 8;//链表转为红黑树的长度
static final int UNTREEIFY_THRESHOLD = 6;//当桶中bin的数量小于该阈值,就将树转化为链表。
static final int MIN_TREEIFY_CAPACITY = 64;//当hash数组中实际容量大于64才会转红黑树,否则就是重新扩容

transient Node<K,V>[] table;//底层数组
transient Set<Map.Entry<K,V>> entrySet;
transient int size;//实际容量的大小
transient int modCount;//修改的次数
int threshold;// 扩容阈值 *0.75
final float loadFactor;//加载因子 0.75

节点:

//1.8中,Entry变成了 Node<K,V>的数据结构
static class Node<K,V> implements Map.Entry<K,V> {
    final int hash;
    final K key;
    V value;
    Node<K,V> next;//这也是实现LinkedHashMap的关键

    Node(int hash, K key, V value, Node<K,V> next) {
        this.hash = hash;
        this.key = key;
        this.value = value;
        this.next = next;
    }

构造函数:

//参数 初始大小 initialCapacity   加载因子即扩容因子loadFactor 可见我们可以决定它怎么扩容
public HashMap(int initialCapacity, float loadFactor) {
    if (initialCapacity < 0)
        throw new IllegalArgumentException("Illegal initial capacity: " +
                                           initialCapacity);
    if (initialCapacity > MAXIMUM_CAPACITY)
        initialCapacity = MAXIMUM_CAPACITY;
    if (loadFactor <= 0 || Float.isNaN(loadFactor))
        throw new IllegalArgumentException("Illegal load factor: " +
                                           loadFactor);
    this.loadFactor = loadFactor;
    this.threshold = tableSizeFor(initialCapacity);
}

public HashMap(int initialCapacity) {
    this(initialCapacity, DEFAULT_LOAD_FACTOR);//默认是0.75
}

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


//或者直接传入一个Map
public HashMap(Map<? extends K, ? extends V> m) {
    this.loadFactor = DEFAULT_LOAD_FACTOR;
    putMapEntries(m, false);
}
//放入Map的过程
final void putMapEntries(Map<? extends K, ? extends V> m, boolean evict) {
    int s = m.size();
    if (s > 0) {
        if (table == null) { // 如果还没初始化,直接传进来的map的长度/0.75,就是新map的长度
            float ft = ((float)s / loadFactor) + 1.0F;
            int t = ((ft < (float)MAXIMUM_CAPACITY) ?
                     (int)ft : MAXIMUM_CAPACITY);
            if (t > threshold)
                threshold = tableSizeFor(t);
        }
        else if (s > threshold)//有长度但是容量小于阈值的话,resize()扩容
            resize();
        for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) {
            K key = e.getKey();
            V value = e.getValue();
            putVal(hash(key), key, value, false, evict);
        }
    }
}

元素操作:

//获取map元素的方法  底层用的getNode(hash(key), key)
public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}
//Map怎么计算Hash?
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);// Key的hashCode() 和 其右移16位 的异或
}
public native int hashCode();// native 即 JNI,Java Native Interface 使用native关键字说明这个方法是原生函数,也就是这个方法是用C/C++语言实现的,并且被编译成了DLL,由java去调用
//可以看到首先通过hash找到桶即Node 再根据equals 遍历链表或者红黑树获得值
final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    if ((tab = table) != null && (n = tab.length) > 0 &&
        (first = tab[(n - 1) & hash]) != null) {//确保不是空的map
        if (first.hash == hash && // always check first node
            ((k = first.key) == key || (key != null && key.equals(k))))
            return first;
        if ((e = first.next) != null) {
            if (first instanceof TreeNode)//如果是一个树
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);//从红黑树拿
            do {
                if (e.hash == hash &&
                    ((k = e.key) == key || (key != null && key.equals(k))))
                    return e;
            } while ((e = e.next) != null);
        }
    }
    return null;
}

//出入数据
public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}

 /**
     *
     * @param hash  由key计算出来的 hash值
     * @param key   要存储的key
     * @param value  要存储的value
     * @param onlyIfAbsent  如果当前位置已存在一个值,是否替换,false是替换,true是不替换 默认是替换
     * @param evict  表是否在创建模式,如果为false,则表是在创建模式。
     */
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i;
    if ((tab = table) == null || (n = tab.length) == 0)//第一次put的时候
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null)//n 和 hash &的话相当于取n的模,如果没有就新建node
        tab[i] = newNode(hash, key, value, null);
    else {
        Node<K,V> e; K k;
        if (p.hash == hash &&
            ((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);//尾插法
                    if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                        treeifyBin(tab, hash); // 转换树为链表 TREEIFY_THRESHOLD 6 
                    break;
                }
                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;
}

扩容:

final Node<K,V>[] resize() {
    Node<K,V>[] oldTab = table;
    int oldCap = (oldTab == null) ? 0 : oldTab.length;//第一次直接为0
    int oldThr = threshold;//阈值
    int newCap, newThr = 0;
    if (oldCap > 0) {
        if (oldCap >= MAXIMUM_CAPACITY) {
            threshold = Integer.MAX_VALUE;//最大是Integer最大值
            return oldTab;
        }
        else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                 oldCap >= DEFAULT_INITIAL_CAPACITY)
            newThr = oldThr << 1; //threshold 的 2倍
    }
    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);//16*0.75
    }
    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;
    //这里重新分配一下,node的位置
    if (oldTab != null) {
        for (int j = 0; j < oldCap; ++j) {//遍历数组
            Node<K,V> e;
            if ((e = oldTab[j]) != null) {//如果只有一个数直接拿出来放到新的数组中 index为e.hash & (newCap - 1)重新计算的
                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 { //处理链表的情况,分为两个链表  为什么要两个链表,因为hash是重新计算的 有可能不在oldCap范围内 loTail就是放不在这个范围内的 
                    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) {//判断重新hash后是否在旧的数组index范围内,0代表超出去了 17 & 16 大家想一下 是不是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;//放 新的链表 这也是为什么要扩容两倍的原因  j+oldCap
                    }
                }
            }
        }
    }
    return newTab;
}

参考:https://blog.csdn.net/balsamspear/article/details/85069207
https://blog.csdn.net/java_eehehe/article/details/104676190

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值