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.5倍
int 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