语言小知识-Java ArrayList类 深度解析

花了一天时间,翻译了一遍 java.util.ArrayList 类的源码(1700 多行,还是很有收获的),包括注释和代码解读,并提了一些问题,也写了下自己的理解 点我查看 ArrayList 源码翻译

  • 问题 1:ArrayList 的 size 和 capacity 怎么理解?

如果把 ArrayList 看作一个杯子的话,capacity 就是杯子的容积,也就是代表杯子能装多少东西,而 size 就是杯子装的东西的体积。杯子可能装满了,也可能没装满,所以 capacity >= size 。capacity 过大和过小都不好,过大会造成浪费,过小又存放不下多个元素的值,capacity == size,则 ArrayList 空间利用率最大,但是不利于添加新的元素。当 ArrayList 实例内的元素个数不再改变了,可以使用 trimToSize() 方法最小化 ArrayList 实例来节省空间,也即是使 capacity == size。

  • 问题 2:ArrayList 内部是怎么存放数据的?

ArrayList 可以看做是数组的封装,使用 elementData 数组来存储数据,使用 size 来代表 elementData 的非 null 元素个数。elementData 前没有访问修饰符,所以只有同类和同包下的类可以直接方法,外界想要知道 ArrayList 实例内元素的个数就要通过 size 属性。elementData 数组类型是 Object 类型,可以存放任意的引用类型,不能存放基本的数据类型。

  • 问题 3:ArrayList 类常量 EMPTY_ELEMENTDATA 和 DEFAULTCAPACITY_EMPTY_ELEMENTDATA 怎么理解?是不是多余?

这两个类常量都是空 Object 数组的引用,都代表 ArrayList 实例的空状态,也即是 elementData 数组中还没有元素。但是 EMPTY_ELEMENTDATA 是使用带初始化值的构造方法(有参构造函数,一个是指定初始容量,一个是指定初始集合)时使用的,DEFAULTCAPACITY_EMPTY_ELEMENTDATA 是使用默认的构造方法,也即是无参的构造方法时使用的。

  • 问题 4:ArrayList 是怎样实现扩充的?

扩容是发生在添加操作前的,要保证要添加元素在 elementData 数组中有位置,也即是 size 加上要添加的元素个数要小于 capacity(size + num <= capacity 就说明容量是充足的),所以在添加方法中,先调用 ensureCapacityInternal(int) 方法来确保 elementData 容量充足,然后再进行具体的添加操作。如果 ensureCapacityInternal 方法(ensureCapacityInternal 方法中有调用了其他方法)发现数组容量不够了,就会扩容。扩容实际的方法是 grow(int) 方法,使用位运算符来使数组的容量扩容 1.5 倍。但是需要注意的是,没有指定初始化值的 ArrayList 实例,第一次扩容并不是以 1.5 倍扩容的,而是使用的默认容量 10,所以网上很多直接说 ArrayList 扩容是 1.5 倍也有不当之处,这点从 JDK 源码中可以很明确的看出来。
如果在构造 ArrayList 实例时,指定初始化值(初始化容量或者集合),那么就会创建指定大小的 Object 数组,并把该数组对象的引用赋值给 elementData;如果不指定初始化值,在第一次添加元素值时会使用默认的容量大小 10 作为 elementData 数组的初始容量,使用 Arrays.conpyOf() 方法创建一个 Object[10] 数组。

  • 问题 5:Arrays.copyOf 方法和 System.arraycopy 方法的区别?

Arrays.copyOf(T[], int length) 方法是 Arrays 工具类中用来进行任意类型数组赋值(包括 null 值),并使数组具有指定长度的方法,ArrayList 中用这个方法来实现 elementData 数组的元素移动。但实际上 Arrays.copyOf 方法最终调用的是 System.arraycopy(U[], int srcPos, T[], desPos, int length) 方法,这个方法是一个本地方法,不能直接看源码。U 和 T 都是一种泛型,只是为了便于区分,U 表示的是原始数组(源数组)类型,T 表示的是存放拷贝值的数组(目标数组)类型,srcPos 是指原始数组中的起始位置(从原始数组的哪个位置开始拷贝),desPos 是指存放拷贝值的数组拷贝起始位置(从目标数组的哪个位置插入这些拷贝的值),length 表示要拷贝的元素数量(要从原始数组中拷贝多少个)。

  • 问题 6:ArrayList 的 add 操作优化?

核心就是避免 ArrayList 内部进行扩容。
1、对于普通少量的 add 操作,如果插入元素的个数已知,最好使用带初始化参数的构造方法,避免 ArrayList 内部再进行扩容,提高性能。
2、对于大量的 add 操作,最好先使用 ensureCapacity 方法来确保 elementData 数组中有充足的容量来存放我们后面 add 操作的元素,避免 ArrayList 实例内部进行扩容。上面提到的 ensureCapacityInternal 方法是一个私有方法,不能直接调用,而 ensureCapacity 方法是一个共有方法,专门提供给开发者使用的,提高大量 add 操作的性能。

测试代码如下:

    ArrayList<Integer> list1 = new ArrayList<>();
    int addCount = 1_000_000; // 这个值不要太小,否则效果不明显
    // 没有优化
    long begin1 = System.currentTimeMillis();
    for (int i = 0; i < addCount; i++) {
        list1.add(i);
    }
    long end1 = System.currentTimeMillis();
    long cost1 = end1 - begin1;

    ArrayList<Integer> list2 = new ArrayList<>();
    // 有优化
    list2.ensureCapacity(addCount);
    long begin2 = System.currentTimeMillis();
    for (int i = 0; i < addCount; i++) {
        list2.add(i);
    }
    long end2 = System.currentTimeMillis();
    long cost2 = end2 - begin2;

    System.err.println("大量 add 操作没有优化前的时间花费:" + cost1);
    System.err.println("大量 add 操作优化后的时间花费:" + cost2);
  • 问题 7:subList 方法生成的子列表会对父列表产生影响吗?

会,不管使修改子列表的值还是修改父列表的值都会对双方产生影响。阅读源码,就会发现,subList 方法后的子列表对元素的操作实际上调用的还是父列表中对应的方法。

  • 问题 8:ArrayList 中既有 Itr 迭代器类,又有 ListItr 迭代器类,该用哪个?

List 集合可以看作是数组的包装类型,遍历并不像数组那样方便,迭代器是为了迭代集合中的元素而存在的。Itr 迭代器类实现了 Iterator 接口,ListItr 迭代器类继承 Itr 迭代器类,并且实现了 ListIterator 接口,所以 ListItr 类的功能比 Itr 类更强大。Itr 类在迭代过程中不能修改 List 的结构(如 add 操作),否则会抛出并发修改异常 ConcurrentModificationException,并且在 next 方法之后才能 remove 元素,而 ListItr 类还支持在迭代过程中添加元素,对于 List 集合元素操作更加友好。所以对于 List 集合迭代,最好使用 ListItr 迭代器类。

查看 原文链接,第一时间获取更新。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342