问: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() 等。

                   

阅读原文请关注微信公众号:码农每日一题

Java 集合 ArrayList 需要知道的几个问题的更多相关文章

  1. Java 集合 ArrayList和LinkedList的几种循环遍历方式及性能对比分析 [ 转载 ]

    Java 集合 ArrayList和LinkedList的几种循环遍历方式及性能对比分析 @author Trinea 原文链接:http://www.trinea.cn/android/arrayl ...

  2. Java基础系列 - JAVA集合ArrayList,Vector,HashMap,HashTable等使用

    package com.test4; import java.util.*; /** * JAVA集合ArrayList,Vector,HashMap,HashTable等使用 */ public c ...

  3. Java集合---ArrayList的实现原理

    目录: 一. ArrayList概述 二. ArrayList的实现 1) 私有属性 2) 构造方法 3) 元素存储 4) 元素读取 5) 元素删除                 6) 调整数组容量 ...

  4. Java集合 -- ArrayList集合及应用

    JAVA集合 对象数组 集合类之ArrayList 学生管理系统 斗地主案例 NO.one 对象数组 1.1 对象数组描述 A:基本类型的数组:存储的元素为基本类型 int[] arr={1,2,3, ...

  5. Java集合--ArrayList出现同步问题的原因

    1 fail-fast简介 fail-fast 机制是java集合(Collection)中的一种错误机制.当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件.例如:当某一个线 ...

  6. java集合-- arraylist小员工项目

    import java.io.*; import java.util.ArrayList; public class Emexe { public static void main(String[] ...

  7. Java集合ArrayList的应用

    /** * * @author Administrator * 功能:Java集合类ArrayList的使用 */ package com.test; import java.io.BufferedR ...

  8. java集合-ArrayList

    一.ArrayList 概述 ArrayList 是实现 List 接口的动态数组,所谓动态就是它的大小是可变的.实现了所有可选列表操作,并允许包括 null 在内的所有元素.除了实现 List 接口 ...

  9. Java集合ArrayList源码解读

    最近在回顾数据结构,想到JDK这样好的代码资源不利用有点可惜,这是第一篇,花了心思.篇幅有点长,希望想看的朋友认真看下去,提出宝贵的意见.  :) 内部原理 ArrayList 的3个字段 priva ...

  10. Java集合-ArrayList源码解析-JDK1.8

    ◆ ArrayList简介 ◆ ArrayList 是一个数组队列,相当于 动态数组.与Java中的数组相比,它的容量能动态增长.它继承于AbstractList,实现了List, RandomAcc ...

随机推荐

  1. java用递归输出目录结构

    package com.janson.day20180827; import java.io.File; public class TestTreeStructureDirectory { publi ...

  2. 窥探原理:实现一个简单的前端代码打包器 Roid

    roid roid 是一个极其简单的打包软件,使用 node.js 开发而成,看完本文,你可以实现一个非常简单的,但是又有实际用途的前端代码打包工具. 如果不想看教程,直接看代码的(全部注释):点击地 ...

  3. mac下安装好jdk和jmeter后设置环境变量

    1. 执行vim ~/.bash_profile,打开文件: 2. 按i,进入输入状态,并输入如下信息,其中为jdk安装路径: export JAVA_HOME=/Library/Java/JavaV ...

  4. 洛谷 4302 BZOJ 1090 SCOI2003 字符串折叠 UVA1630 Folding(输出方案版)

    [题解] 区间DP.  设f[i][j]表示i~j的最小代价.再枚举中间点k,很容易想到转移方程为f[i][j]=min(f[i][j],f[i][k]+f[k][j]),同时如果i~k可以通过重复获 ...

  5. Crackme3 破解教程

    Crackme3 破解教程 1.先用PEiD对 Crackme3进行 壳测试 点击File右边的按钮,选中Crackme3 结果如下图所示: 即 无壳. 试运行软件 点击 Register now! ...

  6. 【转】sizeof()用法总结

    传送门:https://blog.csdn.net/u011677209/article/details/52837065

  7. mysql 查询当天、本周,本月,上一个月的数据---https://www.cnblogs.com/benefitworld/p/5832897.html

    mysql 查询当天.本周,本月,上一个月的数据 今天 select * from 表名 where to_days(时间字段名) = to_days(now()); 昨天 SELECT * FROM ...

  8. [K3Cloud2.0]如何解决创建管理中心失败的问题

    标题有点大,实际上,Cloud2.0产品质量还是顶呱呱的,至于在安装使用过程中出现的这个管理中心无法创建的问题,实际上是与我的网络环境有关,下面讲一下我的网络环境: 开发团队目前所在网段:10.30. ...

  9. 源码分析-react3-创建dom

    React.createElement class Welcome extends React.Component { constructor(){ super() this.state={ test ...

  10. 从内核源代码配置文件预測泛泰新品(A920 ?)

    泛泰的IRON2 A910刚公布不久, 但近期在最新的泛泰890的内核源代码的config文件中又看到了这种编号,例如以下图: 图上ef63s 明显是A910S. 但这ef65s 又是什么呢, 看到这 ...