Java集合之ArrayList源码分析
https://www.cnblogs.com/xujian2014/p/4625346.html
面试必备:ArrayList源码解析(JDK8)
https://blog.csdn.net/zxt0601/article/details/77281231
小结:
ArrayList它是基于数组实现的List类。
相较于数组,它可以动态增长和缩减的索引序列。原理:该类封装了一个动态再分配的Object[]数组,每一个类对象都有一个capacity属性(表示它们所封装的Object[]数组的长度),当向ArrayList中添加元素时,该属性值会自动增加。
ArrayList的用法和Vector向类似,ArrayList和Vector的区别是:ArrayList是线程不安全的,当多条线程访问同一个ArrayList集合时,程序需要手动保证该集合的同步性,而Vector则是线程安全的。但是Vector是一个较老的集合,具有很多缺点(Vector 简介和优缺点 https://blog.csdn.net/mercy_ps/article/details/81098577),不建议使用。
ArrayList源码(这么看源码的方式的话,相当于在看API -_-||):常量的写法、三元运算符、三个构造方法、
//将当前容量值设为当前实际元素大小
public void trimToSize()
//将集合的capacit增加minCapacity
public void ensureCapacity(int minCapacity)
// 变大/扩容
private void grow(int minCapacity)
{
int oldCapacity = elementData.length;
//注意此处扩充capacity的方式是将其向右一位再加上原来的数,实际上是扩充了1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
//返回ArrayList的大小
public int size()
//判断ArrayList是否为空
public boolean isEmpty() {
//判断ArrayList中是否包含Object(o)
public boolean contains(Object o) {
//正向查找,返回ArrayList中元素Object(o)的索引位置
public int indexOf(Object o)
//逆向查找,返回返回ArrayList中元素Object(o)的索引位置
public int lastIndexOf(Object o) {
//返回此 ArrayList实例的浅拷贝。
public Object clone()
//返回一个包含ArrayList中所有元素的数组
public Object[] toArray() {
//返回至指定索引的值
public E get(int index)
//将指定索引上的值替换为新值,并返回旧值
public E set(int index, E element)
//将指定的元素添加到此列表的尾部
public boolean add(E e)
// 将element添加到ArrayList的指定位置
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1);
//从指定源数组中复制一个数组,复制从指定的位置开始,到目标数组的指定位置结束。
//arraycopy(被复制的数组, 从第几个元素开始复制, 要复制到的数组, 从第几个元素开始粘贴, 一共需要复制的元素个数)
//即在数组elementData从index位置开始,复制到index+1位置,共复制size-index个元素
System.arraycopy(elementData, index, elementData, index + 1,size - index);
elementData[index] = element;
size++;
}
//删除ArrayList指定位置的元素
public E remove(int index)
//移除ArrayList中首次出现的指定元素(如果存在)。
public boolean remove(Object o) {
//快速删除指定位置的元素
private void fastRemove(int index)
//清空ArrayList,将全部的元素设为null
public void clear()
//按照c的迭代器所返回的元素顺序,将c中的所有元素添加到此列表的尾部
public boolean addAll(Collection<? extends E> c) {
//从指定位置index开始,将指定c中的所有元素插入到此列表中
public boolean addAll(int index, Collection<? extends E> c) {
rangeCheckForAdd(index);
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
int numMoved = size - index;
if (numMoved > 0)
//先将ArrayList中从index开始的numMoved个元素移动到起始位置为index+numNew的后面去
System.arraycopy(elementData, index, elementData, index + numNew, numMoved);
//再将c中的numNew个元素复制到起始位置为index的存储空间中去
System.arraycopy(a, 0, elementData, index, numNew);
size += numNew;
return numNew != 0;
}
//删除fromIndex到toIndex之间的全部元素
protected void removeRange(int fromIndex, int toIndex)
{
modCount++;
//numMoved为删除索引后面的元素个数
int numMoved = size - toIndex;
//将删除索引后面的元素复制到以fromIndex为起始位置的存储空间中去
System.arraycopy(elementData, toIndex, elementData, fromIndex,numMoved);
int newSize = size - (toIndex-fromIndex);
//将ArrayList后面(toIndex-fromIndex)个元素置为null
for (int i = newSize; i < size; i++)
{
elementData[i] = null;
}
size = newSize;
}
//检查索引是否越界
private void rangeCheck(int index)
{
//删除ArrayList中包含在c中的元素
public boolean removeAll(Collection<?> c)
{
//删除ArrayList中除包含在c中的元素,和removeAll相反
public boolean retainAll(Collection<?> c)
//将ArrayList的“容量,所有的元素值”都写入到输出流中
private void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException
{
int expectedModCount = modCount;
s.defaultWriteObject();
//写入数组大小
s.writeInt(size);
//写入所有数组的元素
for (int i=0; i<size; i++) {
s.writeObject(elementData[i]);
}
if (modCount != expectedModCount) {
throw new ConcurrentModificationException();
}
}
//先将ArrayList的“大小”读出,然后将“所有的元素值”读出
private void readObject(java.io.ObjectInputStream s)
HashMap源码解析:
https://blog.csdn.net/zxt0601/article/details/77413921#comments
https://blog.csdn.net/zxt0601/article/category/6697194
小结:
HashMap 是一个关联数组、哈希表,它是线程不安全的,允许key为null,value为null。遍历时无序。
其底层数据结构是数组称之为哈希桶,每个桶里面放的是链表,链表中的每个节点,就是哈希表中的每个元素。
在JDK8中,当链表长度达到8,会转化成红黑树,以提升它的查询、插入效率,它实现了Map<K,V>, Cloneable, Serializable接口。
因其底层哈希桶的数据结构是数组,所以也会涉及到扩容的问题。当HashMap的容量达到threshold域值时,就会触发扩容。扩容前和后,哈希桶的长度都是2的次方。 也因此,在根据key的hash值寻找对应的哈希桶时,可以用位运算替代取余操作,更加高效。
而key的hash值,并不仅仅只是key对象的hashCode()方法的返回值,还会经过扰动函数的扰动,以使hash值更加均衡。
因为hashCode()是int类型,取值范围是40多亿,只要哈希函数映射的比较均匀松散,碰撞几率是很小的。 但就算原本的hashCode()取得很好,每个key的hashCode()不同,但是由于HashMap的哈希桶的长度远比hash取值范围小,默认是16,所以当对hash值以桶的长度取余,以找到存放该key的桶的下标时,由于取余是通过与操作完成的,会忽略hash值的高位。因此只有hashCode()的低位参加运算,发生不同的hash值,但是得到的index相同的情况的几率会大大增加,这种情况称之为hash碰撞。 即,碰撞率会增大。
扰动函数就是为了解决hash碰撞的。它会综合hash值高位和低位的特征,并存放在低位,因此在与运算时,相当于高低位一起参与了运算,以减少hash碰撞的概率。(在JDK8之前,扰动函数会扰动四次,JDK8简化了这个操作)
扩容操作时,会new一个新的Node数组作为哈希桶,然后将原哈希表中的所有数据(Node节点)移动到新的哈希桶中,相当于对原哈希表中所有的数据重新做了一个put操作。所以性能消耗很大,可想而知,在哈希表的容量越大时,性能消耗越明显。
扩容时,如果发生过哈希碰撞,节点数小于8个。则要根据链表上每个节点的哈希值,依次放入新哈希桶对应下标位置。
因为扩容是容量翻倍,所以原链表上的每个节点,现在可能存放在原来的下标,即low位, 或者扩容后的下标,即high位。 high位= low位+原哈希桶容量
如果追加节点后,链表数量》=8,则转化为红黑树
由迭代器的实现可以看出,遍历HashMap时,顺序是按照哈希桶从低到高,链表从前往后,依次遍历的。属于无序集合。
HashMap的源码中,充斥个各种位运算代替常规运算的地方,以提升效率:
-
与运算替代模运算。用 hash & (table.length-1) 替代 hash % (table.length)
-
用if ((e.hash & oldCap) == 0)判断扩容后,节点e处于低区还是高区。
-
运算尽量都用位运算代替,更高效。
-
对于扩容导致需要新建数组存放更多元素时,除了要将老数组中的元素迁移过来,也记得将老数组中的引用置null,以便GC
-
取下标 是用 哈希值 与运算 (桶的长度-1) i = (n - 1) & hash。 由于桶的长度是2的n次方,这么做其实是等于 一个模运算。但是效率更高
-
扩容时,如果发生过哈希碰撞,节点数小于8个。则要根据链表上每个节点的哈希值,依次放入新哈希桶对应下标位置。
-
因为扩容是容量翻倍,所以原链表上的每个节点,现在可能存放在原来的下标,即low位, 或者扩容后的下标,即high位。 high位= low位+原哈希桶容量
-
利用哈希值 与运算 旧的容量 ,if ((e.hash & oldCap) == 0),可以得到哈希值去模后,是大于等于oldCap还是小于oldCap,等于0代表小于oldCap,应该存放在低位,否则存放在高位。这里又是一个利用位运算 代替常规运算的高效点
-
如果追加节点后,链表数量》=8,则转化为红黑树
-
插入节点操作时,有一些空实现的函数,用作LinkedHashMap重写使用。