Day114 Java容器底层原理

ArrayList的扩容机制

概述:ArrayList在创建时默认初始容量为10,ArrayList在添加第一个元素时,会创建一个长度为10的数组,之后随着元素的增加,以1.5倍原数组的长度创建一个新数组。

源码分析:
主要参数

// 数组默认初始容量
private static final int DEFAULT_CAPACITY = 10;
// 定义一个空的数组实例以供其他需要用到空数组的地方调(比如制定初始容量为0)  
private static final Object[] EMPTY_ELEMENTDATA = {};
//定义一个空数组,跟前面的区别就是这个空数组是用来判断ArrayList第一添加数据的时候要扩容多少。默认的构造器情况下返回这个空数组 
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
//数据存的地方它的容量就是这个数组的长度,同时只要是使用默认构造器(DEFAULTCAPACITY_EMPTY_ELEMENTDATA )第一次添加数据的时候容量扩容为DEFAULT_CAPACITY = 10 
transient Object[] elementData;
//当前数组的长度
private int size;

构造方法
在这里插入图片描述

add方法
(add——>collection;put——>map)

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

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }

扩容机制

  • 添加元素之前,先执行 ensureCapacityInternal() ·确认内部容量
  • 计算容量 calculateCapacity ,如果这个数组等于空,返回最大的容量,否则,还是原来的容量
  • 确认扩展容量 ensureExplicitCapacity
  • 进入扩展方法 grow :调用 Arrays.copyOf 复制方法,在原来元素上增加容量,这就是可变集合的实现原理。用新长度复制原数组。

private void grow(int minCapacity) {
    // overflow-conscious code
获取原来数组容量的长度
    int oldCapacity = elementData.length;
新增加的容量长度为原来容量的1.5int 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);
}
  • Java容器的快速报错机制ConcurrentModificationException:Java容器有一种保护机制,能够防止多个进程同时修改同一个容器的内容。如果你在迭代遍历某个容器的过程中,另一个进程介入其中,并且插入,删除或修改此容器的某个对象,就会立刻抛出ConcurrentModificationException。

4、ArrayList和LinkedList的区别,如果一直在list的尾部添加元素,用哪个效率高?

    ArrayList:他的内部使用数组的数据结构。它的默认容量为10,当容量不够的时候,就会自动扩大容量,1.5倍;  在遍历时通过索引序号访问比较快,而使用迭代器的效果最慢。

    LindedList:内部使用了双向链表的方式。

    linkdeList的效率会高一些,因为他只需要改变一些尾部的指针就可以了。而arrayList必须判断是否会越界,如果会,就需要扩大容量。这个过程就会影响到效率。

HashSet实现原理

概述:HashSet实际上为(key,null)类型的HashMap,而我们知道,HashSet的key是不能重复的,所以HashSet的值自然也是没有重复的.因为HashMap的key可以为null,所以HashSet的值可以为null.。

以下为源码分析:

public class HashSet<E>  
    extends AbstractSet<E>  
    implements Set<E>, Cloneable, java.io.Serializable  
{   
    // 底层使用HashMap来保存HashSet中所有元素。  
    private transient HashMap<E,Object> map;  
 
    // 定义一个虚拟的Object对象作为HashMap的value
    private static final Object PRESENT = new Object();  
 
    // 默认的无参构造器,构造一个空的HashSet。 
    public HashSet() {  
    map = new HashMap<E,Object>();  
    }  
 
    /** 
     * 以指定的initialCapacity和loadFactor构造一个新的空链接哈希集合。 
     * 此构造函数为包访问权限,不对外公开,实际只是是对LinkedHashSet的支持。 
     */  
    HashSet(int initialCapacity, float loadFactor, boolean dummy) {  
    map = new LinkedHashMap<E,Object>(initialCapacity, loadFactor);  
    }  
 
    /** 
     * 返回对此set中元素进行迭代的迭代器。返回元素的顺序并不是特定的。 
     *  
     * 底层实际调用底层HashMap的keySet来返回所有的key。 
     * 可见HashSet中的元素,只是存放在了底层HashMap的key上, 
     * value使用一个static final的Object对象标识。 
     * @return 对此set中元素进行迭代的Iterator。 
     */  
    public Iterator<E> iterator() {  
    return map.keySet().iterator();  
    }  
 
 
    /** 
     * 如果此set中尚未包含指定元素,则添加指定元素。 
     * 更确切地讲,如果此 set 没有包含满足(e==null ? e2==null : e.equals(e2)) 
     * 的元素e2,则向此set 添加指定的元素e。 
     * 如果此set已包含该元素,则该调用不更改set并返回false。 
     * 
     * 底层实际将将该元素作为key放入HashMap。 
     * 由于HashMap的put()方法添加key-value对时,当新放入HashMap的Entry中key 
     * 与集合中原有Entry的key相同(hashCode()返回值相等,通过equals比较也返回true), 
     * 新添加的Entry的value会将覆盖原来Entry的value,但key不会有任何改变, 
     * 因此如果向HashSet中添加一个已经存在的元素时,新添加的集合元素将不会被放入HashMap中, 
     * 原来的元素也不会有任何改变,这也就满足了Set中元素不重复的特性。 
     * @param e 将添加到此set中的元素。 
     * @return 如果此set尚未包含指定元素,则返回true。 
     */  
    public boolean add(E e) {  
    return map.put(e, PRESENT)==null;  
    }  
 
    /** 
     * 如果指定元素存在于此set中,则将其移除。 
     * 更确切地讲,如果此set包含一个满足(o==null ? e==null : o.equals(e))的元素e, 
     * 则将其移除。如果此set已包含该元素,则返回true 
     * (或者:如果此set因调用而发生更改,则返回true)。(一旦调用返回,则此set不再包含该元素)。 
     * 
     * 底层实际调用HashMap的remove方法删除指定Entry。 
     * @param o 如果存在于此set中则需要将其移除的对象。 
     * @return 如果set包含指定元素,则返回true。 
     */  
    public boolean remove(Object o) {  
    return map.remove(o)==PRESENT;  
    }  
 
}  

sort 方法的底层实现原理

Collections.sort()

  • 从Collections.sort(),一路点进去,会进到Arrays里
    • 如果LegacyMergeSort.userRequested为true的话就会使用归并排序
    • 否则会进入TimSort,Timsort是结合了合并排序(merge sort)和插入排序(insertion sort)而得出的排序算法,它在现实中有很好的效率。
    public static <T> void sort(T[] a, Comparator<? super T> c) {
        if (c == null) {
            sort(a);
        } else {
            if (LegacyMergeSort.userRequested)
                legacyMergeSort(a, c);
            else
                TimSort.sort(a, 0, a.length, c, null, 0, 0);
        }
    }

Arrays.sort()

  • 会调用DualPivotQuicksort方法(双轴快速排序)
    快排数据越无序越快(加入随机化后基本不会退化),平均常数最小,不需要额外空间,不稳定排序。
    归排速度稳定,常数比快排略大,需要额外空间,稳定排序。
    所以大于或等于47或少于286会进入快排,而在大于或等于286后,会有个小动作:“// Check if the array is nearly sorted”。
    这里第一个作用是先梳理一下数据方便后续的归并排序,第二个作用就是即便大于286,但在降序组太多的时候(被判断为没有结构的数据,The array is not highly structured,use Quicksort instead of merge sort.),要转回快速排序。
    在这里插入图片描述

快速失败机制

Java集合的快速失败机制 “fail-fast”?
是java集合的一种错误检测机制,当多个线程对集合进行结构上的改变的操作时,有可能会产生 fail-fast 机制。

例如:假设存在两个线程(线程1、线程2),线程1通过Iterator在遍历集合A中的元素,在某个时候线程2修改了集合A的结构(是结构上面的修改,而不是简单的修改集合元素的内容),那么这个时候程序就会抛出 ConcurrentModificationException 异常,从而产生fail-fast机制。

原因:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。

解决办法:

在遍历过程中,所有涉及到改变modCount值得地方全部加上synchronized。

使用CopyOnWriteArrayList来替换ArrayList

https://www.jianshu.com/p/d7ba7d919b80
https://blog.youkuaiyun.com/u010890358/article/details/80515284
https://blog.youkuaiyun.com/yz972641975/article/details/78662617
https://blog.youkuaiyun.com/ThinkWon/article/details/104588551

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值