【Java】20211016_Java 集合 _ArrayList
【Java】20211016_Java集合_ArrayList
从简单数据结构谈起
数组
懂得都懂(doge)
ArrayList的底层结构
ArrayList的底层结构是数组(见代码块1-1),首先看ArrayList的初始化构造方法。
[^代码块1-1]
transient Object[] elementData;
ArrayList提供了三种构造方法(见代码块1-2)
[^代码块1-2]
private static final Object[] EMPTY_ELEMENTDATA = {}; private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; public ArrayList() { this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA; } public ArrayList(int initialCapacity) { if (initialCapacity > 0) { this.elementData = new Object[initialCapacity]; } else if (initialCapacity == 0) { this.elementData = EMPTY_ELEMENTDATA; } else { throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity); } } public ArrayList(Collection<? extends E> c) { Object[] a = c.toArray(); if ((size = a.length) != 0) { if (c.getClass() == ArrayList.class) { elementData = a; } else { elementData = Arrays.copyOf(a, size, Object[].class); } } else { // replace with empty array. elementData = EMPTY_ELEMENTDATA; } }
- ArrayList的无参构造,将其底层数组赋值为一个空数组。
- ArrayList的int型参数构造,传入参数initialCapacity作为底层数组的初始化长度,判断初始化长度小于0则抛出异常,等于0则将底层数组赋值为一个空数组,大于0则以初始化长度动态初始化一个数组。
- ArrayList的Collection型参数构造——略。
长度可变揭秘
无论使用哪种构造方式,一个确定的数组经过初始化后,其长度是不会发生改变的,所以我们需要看看ArrayList提供的add方法来探究ArrayList集合长度可变的秘密(见代码块1-3)
[^代码块1-3]
private int size; public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } private void ensureCapacityInternal(int minCapacity) { ensureExplicitCapacity(calculateCapacity(elementData, minCapacity)); } private static final int DEFAULT_CAPACITY = 10; private static int calculateCapacity(Object[] elementData, int minCapacity) { if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } return minCapacity; } protected transient int modCount = 0; private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } private void grow(int minCapacity) { // overflow-conscious code int oldCapacity = elementData.length; 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); }
以一个通过无参刚初始化的空集合为例:
- ensureCapacityInternal方法——再次确定集合容量。由于刚初始化,此时size为0,ensureCapacityInternal方法实际接收的参数minCapacity为1,在ensureCapacityInternal方法内部调用了calculateCapacity方法。
- calculateCapacity方法——计算集合容量。由于在使用无参构造初始化时
elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA,所以这个方法直接会返回minCapacity(1)和DEFAULT_CAPACITY(10)中的最大值即10。 - ensureExplicitCapacity方法——明确添加元素后的集合容量。在ensureExplicitCapacity方法中,传入的minCapacity是上一步的返回结果10,modCount++后modCount为1,接着在判断条件中,显然(10-0)>0为true,所以下一步要走grow方法。
- grow方法——扩容方法。
// 获取旧容量值
int oldCapacity = elementData.length;
// 计算新容量值,oldCapacity >> 1 <==> oldCapacity / 2,所以新容量为旧容量的1.5倍大
int newCapacity = oldCapacity + (oldCapacity >> 1);
紧接着是对新容量值做最终判断,如果newCapacity (这里为0)小于传入的minCapacity(这里是10),那么新容量值就取传入的minCapacity,否则就和MAX_ARRAY_SIZE比较,这里的MAX_ARRAY_SIZE非常大,一般不会为true(如果实际工作中你能让扩容走到这一步,联系我我给你发五毛钱安家费),接着就会通过Arrays工具类的copy方法将原来elementData数组中的数据拷贝到一个新的数组中并将新数组地址赋值给elementData完成集合扩容。 - 走完上述方法后,执行elementData[size++] = e;才正式将想要添加的元素e添加到集合中。
走完整个扩容方法,可以知道:
- 集合扩容的时机——如果一个集合原有长度是10,直到放置第11个元素才会扩容,即在添加超过集合容量的第一个元素时在add方法内部扩容。
- 扩容后的容量——原容量的1.5倍。
面试题
(其实14号美团面试没有问ArrayList扩容,我也没答,但还是写了这个md文档,主要是ArrayList的扩容机制和HashMap存在不一样的地方,所以要做个记录,以防下次被问到)
正题:List.contains(Object obe)方法是怎么实现的?
内心os:这还要想?你要确定一个集合里有没有这个元素,直接遍历然后去做比较呗?
想是这么想,答也是这么答的,ArrayList在实现上也确实是这么个思路。看看码(见代码块2-1):
[^代码块2-1]
public boolean contains(Object o) { return indexOf(o) >= 0; } public int indexOf(Object o) { if (o == null) { for (int i = 0; i < size; i++) if (elementData[i]==null) return i; } else { for (int i = 0; i < size; i++) if (o.equals(elementData[i])) return i; } return -1; }
直接看indexOf方法,indexOf方法分别谈论了o是否为null的两种情况,因为o为null在调用o.equals()时会报空指针异常。
- 如果o是null,就遍历集合通过==来判断集合中是否有null这个特殊元素。
- 如果o不是null,就遍历集合通过equals来判断集合中是否存在和o相同的元素。
null在Java中是特殊的,地址值固定的,所以可以通过==来判断,但是对于其他对象,应该调用可以被重写的对象o的equals方法来判断。(其实contains方法我还是回答对了的)
说到equals方法,上一篇写了下面的代码(见代码块2-2):
[^代码块2-2]
Integer a = 1000; Integer b = 1000; System.out.println(a == b);
上面的代码最终输出结果为false,实际上在对两个包装类进行比较时应该用equals方法,阿里巴巴代码规约里已经告诉我们了。
努力让自己不那么菜
欲速则不达,每天进步一点点。
(有没有成都的大佬内推我一波)
说实话,我看见java就头疼,库太多了 a.b.c.d 的点点点
面向对象的语言都不太喜欢
输出的 很好,持续加油