回顾HashMap:HashMap原理底层剖析
注意以下文章可能有描述和理解上的错误,如果出现错误请到评论区指出,我会第一时间修改问题。也希望文章能解决你的疑惑。
ArrayList
ArrayList学习
底层数据结构
其实ArrayList底层的数据就是维护了一个数组,在进行遍历的时候时间复杂度O(1),在进行增删时候时间复杂度为O(N),但是这里需要注意的是add(E e) 这个方法的时间复杂度就是O(1),因为他直接在尾部插入即可。
源码剖析
参数解释
//默认的容量
private static final int DEFAULT_CAPACITY = 10;
//用来存放数组的原始数组
transient Object[] elementData; // non-private to simplify nested class access
//集合中元素的个数
private int size;
//数组最大长度
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
构造函数
//传入指定容量的list
public ArrayList(int initialCapacity) {
//容量大于0初始化数组长度大小为initialCapacity
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {//容量等于0其实也是对其数组进行初始化
this.elementData = EMPTY_ELEMENTDATA;
} else {//表示的就是小于0肯定就是数据不合法啦
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
//无参构造函数,并对集合进行初始化
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
//传入指定的集合转换为ArrayList
public ArrayList(Collection<? extends E> c) {
//将这个集合转化为数组
elementData = c.toArray();
//表示传入集合为空
if ((size = elementData.length) != 0) {
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
//传入的集合为空,初始化elementData数组
this.elementData = EMPTY_ELEMENTDATA;
}
}
调整容量大小
//将集合大小变为size
public void trimToSize() {
//集合操作+1
modCount++;
//如果size小于数组长度,将数组的长度大小变为size
if (size < elementData.length) {
elementData = (size == 0)
? EMPTY_ELEMENTDATA
: Arrays.copyOf(elementData, size);
}
}
扩容
//扩容函数
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
//新的长度是1.5倍
int newCapacity = oldCapacity + (oldCapacity >> 1);
//进行增加后小于传入参数那么新的容量就是传的参数大小
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
//当扩容后新长度大于最大长度并且minCapacity 大于最大长度 返回 Integer.MAX_VALUE
//当扩容后新长度大于最大长度并且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);
}
容量个数
//返回数组中个数
public int size() {
return size;
}
判空函数
//判断集合是否为空
public boolean isEmpty() {
return size == 0;
}
判断包含函数
//查看是否有包含的元素
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
指定元素的下标
//返回指定元素的下标,如果不存在返回-1 如上contains方法就对其进行了调用
public int indexOf(Object o) {
//判断是否为null,找到为null的元素并返回下标
if (o == null) {
for (int i = 0; i < size; i++)
if (elementData[i]==null)
return i;
} else {
//返回非null元素的下标
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
//这个函数是从后先前找出指定元素,并返回下标,不存在返回-1
public int lastIndexOf(Object o) {
if (o == null) {
for (int i = size-1; i >= 0; i--)
if (elementData[i]==null)
return i;
} else {
for (int i = size-1; i >= 0; i--)
if (o.equals(elementData[i]))
return i;
}
return -1;
}
克隆一个集合
//克隆一个当前集合并返回
public Object clone() {
try {
//v实际克隆的是原有集合指向内存的地址值
ArrayList<?> v = (ArrayList<?>) super.clone();
//复制原本数组的数组,数组长度变为有效元素的长度
v.elementData = Arrays.copyOf(elementData, size);
//操作次数变为0
v.modCount = 0;
return v;
} catch (CloneNotSupportedException e) {
// this shouldn't happen, since we are Cloneable
throw new InternalError(e);
}
}
集合转数组
//将集合转换为数组
public Object[] toArray() {
return Arrays.copyOf(elementData, size);
}
获取指定索引值
//根据索引获取指定位置上的值
public E get(int index) {
//检查索引
rangeCheck(index);
//返回指定索引位置上的值
return elementData(index);
}
指定位置设置值
//在符合索引位置的地方设置值,并返回原本的值
public E set(int index, E element) {
//检查索引的合法性
rangeCheck(index);
//获取index位置的值并保存
E oldValue = elementData(index);
//将当前位置设置为新的值
elementData[index] = element;
//返回旧的值
return oldValue;
}
添加元素
//添加元素
public boolean add(E e) {
//操作数加一,当前函数里面会判断是否大于数组的长度,如果大于则进行扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
//在当前位置添加元素,指针后移
elementData[size++] = e;
return true;
}
//在指定位置添加元素
public void add(int index, E element) {
//检查索引的合法性,如果不合法抛出异常
rangeCheckForAdd(index);
//同上一个函数,操作加1,判断扩容
ensureCapacityInternal(size + 1); // Increments modCount!!
//这里实质就是将数组包括index位置之后的元素后移一位,这里不做具体讲述,不懂的可以Ctrl+左键查看源码
System.arraycopy(elementData, index, elementData, index + 1, size - index);
//在当前位置添加元素
elementData[index] = element;
//数组长度+1
size++;
}
删除元素
//删除指定位置元素,并返回删除的值
public E remove(int index) {
//检查下标
rangeCheck(index);
//集合操作数+1
modCount++;
//获取删除index位置元素的val
E oldValue = elementData(index);
//找出要删除元素的准确位置
int numMoved = size - index - 1;
if (numMoved > 0)
//删除元素
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//将之前最后一个位置置空方便垃圾回收
elementData[--size] = null; // clear to let GC do its work
//返回旧值
return oldValue;
}
//删除指定位置的值,并返回是否成功
public boolean remove(Object o) {
//对象为null
if (o == null) {
//遍历数组
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
//找到指定值的索引进行删除并且操作数+1
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
//元素非null删除元素
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
//他就是上面函数调用的方法
private void fastRemove(int index) {
//操作数+1
modCount++;
//计算出要删除元素的下标
int numMoved = size - index - 1;
if (numMoved > 0)
//删除指定位置元素
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
//方便垃圾回收,这里可以这样理解index 之后元素都想前移动一位
elementData[--size] = null; // clear to let GC do its work
}
清空集合
//清空集合元素
public void clear() {
//操作数+1
modCount++;
// 所有位值的元素全部置空
for (int i = 0; i < size; i++)
elementData[i] = null;
//有效元素的个数0
size = 0;
}
合并集合
//将一个集合所有元素添加到当前集合
public boolean addAll(Collection<? extends E> c) {
//将传入的集合转化为数组
Object[] a = c.toArray();
//获取传入集合数组的长度
int numNew = a.length;
//判断当前集合元素长度+新的元素个数 是否大于数组的长度,如果大于则进行扩容
ensureCapacityInternal(size + numNew); // Increments modCount
//将传入集合的所有的元素拼接在当前集合的后面
System.arraycopy(a, 0, elementData, size, numNew);
//有效个数+numNew
size += numNew;
return numNew != 0;
}
迭代器
/*
这里说明我没有全部的源码解释就解释一下迭代器最基本的工作过程
*/
//返回一个迭代器实列
public Iterator<E> iterator() {
return new Itr();
}
private class Itr implements Iterator<E> {
int cursor; // 要返回的下一个元素的索引
int lastRet = -1; //返回的最后一个元素的索引;-1如果没有
int expectedModCount = modCount;//这个元素主要是和modCount一起判断在遍历是对其集合进行修改会造成 ConcurrentModificationException
Itr() {}
//用来判断元素是否走到最后
public boolean hasNext() {
return cursor != size;
}
@SuppressWarnings("unchecked")
public E next() {
//检查modcount 与 expectedModCount是否一致 不一致抛出异常
checkForComodification();
int i = cursor;
//如果i>=size 表示已经遍历完集合
if (i >= size)
throw new NoSuchElementException();
//创建一个局部变量elementData指向原数组
Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length)
throw new ConcurrentModificationException();
//下标后移一个
cursor = i + 1;
//返回当前位置i的元素
return (E) elementData[lastRet = i];
}
//删除元素,这里需要注意:你使用集合对象remove 和 迭代器对象remove是不一样的,前者会造成ConcurrentModificationException
public void remove() {
//这里lastRet表示的是刚才next中i的值
if (lastRet < 0)
throw new IllegalStateException();
//在删除之前检测集合是否被改动改过
checkForComodification();
try {
//调用集合对象按索引remove
ArrayList.this.remove(lastRet);
//删除元素后对集合进行调整,所以lastRet是一个新的值需要遍历
cursor = lastRet;
//防止重复删除指定位置的值
lastRet = -1;
//因为这里调用ArrayList中remove如果不更新expectedModCount会造成ConcurrentModificationException
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException();
}
}
源码就讲到这里,都看到这里了 不来一个键三连,要是如上代码解释有误请在评论区提出来,我立即改正不想误人子弟。
面试题
-
ArrayList 是如何进行扩容的?
ArrayList 扩容是1.5倍
-
ArrayList 和 LinkedList 应用场景?为什么不同?(面试最爱问的ArrayList)
在看这个两个场景的时候首先要知道他们的数据结构,ArrayList – > 数组、LinkedList – > 双端链表(不是单向),这里既然知道了他们的数据结构就可以看出来他们特点:1.当一个业务需要进行频繁的增删的时候就使用LinkedList,因为LinkedList是链表插入删除时间复杂度O(1),2.当一个业务需要进行频繁的查找的时候使用ArrayList他的时间复杂度为O(1)。这里ArrayList 和 LinkedList 二者都实现统一接口List
-
如何快速实现ArrayList和数组的快速转换?
//由数组、多个数据快速转换为集合 List<String> strings = Arrays.asList("sd", "sdasd", "sdgf"); int[] arr = new int[]{1,2,3,4,6,60}; List<int[]> ints = Arrays.asList(arr); //由集合转换为数组 Object[] objects = new ArrayList<>().toArray();
-
如何使用一个线程安全的List?
//方式一
Vector vector = new Vector();
//方式二
List objects = Collections.synchronizedList(new ArrayList<>());
如上图可以看出Vector与List关系,因为List和Vector都实现了List接口。 -
Array和ArrayList的区别有哪些?
首先,Array长度是固定,但是在效率上要由于ArrayList,Array在改查的时候效率很高,但是在增删的时候很不方便,需要考虑手动扩大数组长度,Array可以存在int类型。ArrayList长度是动态变化, 效率上比Array差一点,不会有很大的差异,ArrayList底层数据结构是Array,ArrayList只能存储对象类型。不允许ArrayList arr = new ArrayList();
如果看完觉得得到帮助就留下你的一键三连,谢谢 注意如果有错误的地方评论区提出来我立即更正谢谢大佬的指正。