Java中的 ArrayList

本篇文章是【Java集合系列】文章的第一篇,本系列将会逐个分析 Java 中的常用集合的特性及实现,然后对比不同场景下应该选择哪种集合使用。

List 系列

List

先看下 ArrayList 实现的接口 List 的相关概念。

  • List 可以称为有序集合或者序列,通过整数索引访问元素
  • 允许插入相同元素
  • 一般来说也允许插入 null 值

List 接口中还提供了一个特殊的迭代器:ListIterator

ListIterator

ListIterator 专门为了 List 打造,在 Iterator 基础上还提供了插入和替换元素以及双向访问的功能。
我们来看下使用:

List<String> list= new ArrayList<>();
list.add("1");
list.add("2");
list.add("7");
list.add("4");
ListIterator<String> iterator = list.listIterator();
while (iterator.hasNext()){
    String next = iterator.next();
    if(next.equals("7")){
        iterator.set("3");
        iterator.previous();
    }else{
        System.out.println(next);
    }
}

ArrayList

Java 原生提供了数组数据结构,但由于本身设计存在诸多问题,例如无法扩容、类型不安全等,不够灵活,所以大部分时候可以使用 ArrayList 来替代,效率上没有太大的差异。

ArrayList 是 List 接口可调整大小的数组实现。

size,isEmpty,get,set,iterator以及 listIterator方法调用的执行时间都是固定时间

add操作时间是摊销固定时间(amortized constant time),也就是添加 n 个元素需要 O(n) 的时间。其他操作都是线性时间

ArrayList 中有个 capacity 参数用于描述 ArrayList 中数组的长度,add操作会先进行扩容,引起capacity的增长,这是个耗时操作。如果将会发生多次add操作,可以在此之前先调用ensureCapacity方法扩容,以此减小扩容的次数,提升性能。

需要注意的是 ArrayList 是非线程安全的,多线程环境下,如果希望修改其结构,必须进行同步,也可以使用 Collections 中的包装方法获取同步对象:

List list = Collections.synchronizedList(new ArrayList(...));

iterator 以及 listIterator 也被设计为 fail-fast 的,多线程环境下使用 iterator 将会直接抛出异常。

ArrayList 长度默认为 0,可以通过构造器设置初始长度。

扩容机制是每次添加元素时会检测是否需要扩容,每次增加的长度为当前长度的一半,可以通过ensureCapacity方法使其扩容到指定长度。

源码

我们先从构造函数开始看

//ArrayList 中存储数据的数组
transient Object[] 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() {
    //DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

可以看到elementData默认长度是 0,如果设置了默认长度就会初始化成改长度的数组。

再来看看add方法:

public boolean add(E e) {
    ensureCapacityInternal(size + 1);
    elementData[size++] = e;
    return true;
}

注意这里的size字段表示当前 ArrayList 的实际长度,不代表elementData的长度。
add方法会先调用ensureCapacityInternal方法,传入的参数size+1并不代表本次的扩容长度,而是最小扩容长度。
然后该方法会先判断是否需要进行扩容,判断依据就是当前数组是否还有空余空间添加新元素。

//minCapacity = size + 1
if (minCapacity - elementData.length > 0)
        grow(minCapacity);

那么再看看扩容是如何实现的,可以先猜想下,既然内部使用数组来存储数据,但数组是不可变长度的,那么最可行的方案应该就是重新创建一个更大的数组,然后将数据拷贝进去,我们可以看看是不是这么做的。

private void grow(int minCapacity) {
    int oldCapacity = elementData.length;
    //右移一位等于整除2
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        //hugeCapacity 返回的最大值为 Integer.MAX_VALUE
        newCapacity = hugeCapacity(minCapacity);
    elementData = Arrays.copyOf(elementData, newCapacity);
}

可以看到每次扩容的长度是当前长度 + 当前长度的一半,也就是扩容 50%
MAX_ARRAY_SIZEInteger.MAX_VALUE - 8,一般来说数组长度不会超过这个值,但如果非要继续进行扩容将会扩张到 Integer.MAX_VALUE

那么既然可以扩张到Integer.MAX_VALUE为什么还要一个MAX_ARRAY_SIZE呢,代码里面的解释是一些 JVM 会在数组中添加一些保留字段,如果将数组长度扩张到Integer.MAX_VALUE可能会导致OutOfMemoryError

欢迎关注我的公众号,还有更多干货。
微信扫描二维码或者搜索:zhangke_blog


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