Java 集合 ArrayList 需要知道的几个问题
问:Arraylist 的动态扩容机制是如何自动增加的?简单说说你理解的流程?
答:当在 ArrayList 中增加一个对象时 Java 会去检查 Arraylist 以确保已存在的数组中有足够的容量来存储这个新对象(默认为 10,最大容量为 int 上限,减 8 是为了容错),如果没有足够容量就新建一个长度更长的数组(原来的1.5倍),旧的数组就会使用 Arrays.copyOf 方法被复制到新的数组中去,现有的数组引用指向了新的数组。下面代码展示为 Java 1.8 中通过 ArrayList.add 方法添加元素时,内部会自动扩容,扩容流程如下:
public boolean add(E e) { //确保容量够用,内部会尝试扩容,如果需要 ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } //在未指定容量的情况下,容量为DEFAULT_CAPACITY = 10 //并且在第一次使用时创建容器数组,在存储过一次数据后,数组的真实容量至少DEFAULT_CAPACITY private void ensureCapacityInternal(int minCapacity) { //判断当前的元素容器是否是初始的空数组 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { //如果是默认的空数组,则 minCapacity 至少为DEFAULT_CAPACITY minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity); } ensureExplicitCapacity(minCapacity); } //通过该方法进行真实准确扩容尝试的操作 private void ensureExplicitCapacity(int minCapacity) { modCount++;//记录List的结构修改的次数 //需要扩容 if (minCapacity - elementData.length > 0) grow(minCapacity); } private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; //扩容操作 private void grow(int minCapacity) { //原来的容量 int oldCapacity = elementData.length; //新的容量 = 原来的容量 + (原来的容量的一半) int newCapacity = oldCapacity + (oldCapacity >> 1); //如果计算的新的容量比指定的扩容容量小,那么就使用指定的容量 if (newCapacity - minCapacity < 0) newCapacity = minCapacity; //如果新的容量大于MAX_ARRAY_SIZE(Integer.MAX_VALUE - 8) //那么就使用hugeCapacity进行容量分配 if (newCapacity - MAX_ARRAY_SIZE > 0) newCapacity = hugeCapacity(minCapacity); // minCapacity is usually close to size, so this is a win: //创建长度为newCapacity的数组,并复制原来的元素到新的容器,完成ArrayList的内部扩容 elementData = Arrays.copyOf(elementData, newCapacity); } private static int hugeCapacity(int minCapacity) { if (minCapacity < 0) // overflow throw new OutOfMemoryError(); return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE; }
问:请写出下面代码片段的运行结果及原因?
ArrayList<Integer> list = new ArrayList<Integer>(); list.add(1); list.add(2); list.add(3); Integer[] array1 = new Integer[3]; list.toArray(array1); Integer[] array2 = list.toArray(new Integer[0]); System.out.println(Arrays.equals(array1, array2)); // 1 结果是什么?为什么? Integer[] array = { 1, 2, 3 }; List<Integer> list = Arrays.asList(array); list.add(4); // 2 结果是什么?为什么? Integer[] array = { 1, 2, 3 }; List<Integer> list = new ArrayList<Integer>(Arrays.asList(array)); list.add(4); // 3 结果是什么?为什么?
1 输出为 true,因为 ArrayList 有两个方法可以返回数组 Object[] toArray() 和 <T> T[] toArray(T[] a),第一个方法返回的数组是通过 Arrays.copyOf 实现的,第二个方法如果参数数组长度足以容纳所有元素就使用参数数组,否则新建一个数组返回(这里也是使用copyOf来实现的),所以结果为 true。
public <T> T[] toArray(T[] a) { if (a.length < size) // Make a new array of a's runtime type, but my contents: return (T[]) Arrays.copyOf(elementData, size, a.getClass()); System.arraycopy(elementData, 0, a, 0, size); if (a.length > size) a[size] = null; return a; }
2 会抛出 UnsupportedOperationException 异常,因为 Arrays 的 asList 方法返回的是一个 Arrays 内部类的 ArrayList 对象,这个对象没有实现 add、remove 等方法,只实现了 set 等方法,所以通过 Arrays.asList 转换的列表不具备结构可变性。
public static <T> List<T> asList(T... a) { return new ArrayList<>(a); } private static class ArrayList<E> extends AbstractList<E> implements RandomAccess, java.io.Serializable { private static final long serialVersionUID = -2764017481108945198L; private final E[] a; ArrayList(E[] array) { a = Objects.requireNonNull(array); } @Override public int size() { return a.length; } @Override public Object[] toArray() { return a.clone(); } @Override @SuppressWarnings("unchecked") public <T> T[] toArray(T[] a) { int size = size(); if (a.length < size) return Arrays.copyOf(this.a, size, (Class<? extends T[]>) a.getClass()); System.arraycopy(this.a, 0, a, 0, size); if (a.length > size) a[size] = null; return a; } @Override public E get(int index) { return a[index]; } @Override public E set(int index, E element) { E oldValue = a[index]; a[index] = element; return oldValue; } @Override public int indexOf(Object o) { E[] a = this.a; if (o == null) { for (int i = 0; i < a.length; i++) if (a[i] == null) return i; } else { for (int i = 0; i < a.length; i++) if (o.equals(a[i])) return i; } return -1; } @Override public boolean contains(Object o) { return indexOf(o) != -1; } @Override public Spliterator<E> spliterator() { return Spliterators.spliterator(a, Spliterator.ORDERED); } @Override public void forEach(Consumer<? super E> action) { Objects.requireNonNull(action); for (E e : a) { action.accept(e); } } @Override public void replaceAll(UnaryOperator<E> operator) { Objects.requireNonNull(operator); E[] a = this.a; for (int i = 0; i < a.length; i++) { a[i] = operator.apply(a[i]); } } @Override public void sort(Comparator<? super E> c) { Arrays.sort(a, c); } }
3 当然可以正常运行咯,不可变结构的 Arrays 的 ArrayList 通过构造放入了真正的万能 ArrayList,自然就可以操作咯。
问:为什么 ArrayList 的增加或删除操作相对来说效率比较低?能简单解释下为什么吗?
答:ArrayList 在小于扩容容量的情况下其实增加操作效率是非常高的,在涉及扩容的情况下添加操作效率确实低,删除操作需要移位拷贝,效率是低点。因为 ArrayList 中增加(扩容)或者是删除元素要调用 System.arrayCopy 这种效率很低的方法进行处理,所以如果遇到了数据量略大且需要频繁插入或删除的操作效率就比较低了,具体可查看 ArrayList 的 add 和 remove 方法实现,但是 ArrayList 频繁访问元素的效率是非常高的,因此遇到类似场景我们应该尽可能使用 LinkedList 进行替代效率会高一些。
问:简单说说 Array 和 ArrayList 的区别?
答:这题相当小儿科,Array 可以包含基本类型和对象类型,ArrayList 只能包含对象类型;Array 的大小是固定的,ArrayList 的大小是动态变化的;ArrayList 提供了更多的方法和特性,譬如 addAll()、removeAll()、iterator() 等。
阅读原文请关注微信公众号:码农每日一题