List(一):ArrayList源码浅析

ArrayList的结构属于线性表结构,基于数组实现,是一个动态的数组,容量能够自动的增长,支持序列化,但是不是线程安全的

ArrayList#add()

我们先从add()开始

ArrayList#add()

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

首先进入了ensureCapacityInternal(),然后将add的参数放到elementData数组的size++位置上,最后返回true。sizearrayList的集合数据数量,前面我们有说ArrayList是一个数组,具体表现为elementData,所以add()的后两句代码很清晰,将ArrayList的长度即elementData的长度size自增1,然后将要add的参数放入到elementData自增后的size位置上。
回过头来看ensureCapacityInternal(),入参为当前数组长度加一。

ArrayList#ensureCapacityInternal()

// 默认空数组实现  
 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
 // 默认数组容量 
 private static final int DEFAULT_CAPACITY = 10;
 
 private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

里面有一个判断,如果elementData数组是一个默认的空数组,也就是刚初始化完毕或者clear之后的数组,一句话,此时的数组即集合是空的,没有任何数据在里面,此时会在默认数组长度以及首次添加数据时的size+1之间取最大值,所以minCapacity的值为10,最后进入到ensureExplicitCapacity()中。

ArrayList#ensureExplicitCapacity():

// 代表集合被修改的次数,主要是用于在检测在迭代集合的过程中,集合是否发生了变化,下面在说迭代的时候会说到
protected transient int modCount = 0;

 private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }

modCount自增1,然后判断当前数组是否还有多余的位置,即添加元素后的要求的数组长度是否大于数组的本身的长度,如果否的话,不进行任何操作,可以直接添加元素到数组之后只能怪,如果是的话则进入grow()进行扩容操作

ArrayList#grow():

private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

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);
    }

首先获取到数组的长度,此时的数组是长度为0默认空数组,即oldCapacity = 0,0右移一位还是0,所以newCapacity = 0,默认的数组长度即minCapacity = 10,所以第一个判断条件成立,此时的newCapacity = 10;第二个判断条件,MAX_ARRAY_SIZE是Integer.MAX_VALUE - 8,newCapacity - MAX_ARRAY_SIZE > 0即数组容量大于这个值,在Android中基本不存在这么大的数组,所以这里暂不考虑,最后一行拷贝旧数组并把数组长度传进去,生成 一个新的数组并赋值给elementData
重新捋一下grow():

首先拿到当前数组的长度,并由此得到当前数组的长度的1.5倍数值,并赋予newCapacity,如果被要求的数组长度(size + 1)大于当前数组长度的1.5倍(1.5 * size),则将minCapacity(size + 1)作为被扩展的数组的目标长度,然后进行拷贝扩容;如果被要求的数组长度(size + 1)小于当前数组的1.5倍值(size * 1.5)则将当前数组长度的1.5倍值作为目标数组长度进行拷贝扩容操作

最后回到add()中,将要加入的add放入到新创建的elementData[]

ArrayList#get(int Index)

get()没有什么可以说的内容,普通的数组取值

 public E get(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        return (E) elementData[index];
    }
ArrayList#set(int index,E element)

set也没有什么可说的,注意的就是入参的index要小于数组中的元素数量size,因为set()主要的作用的元素替换,替换掉指定位置上的元素,并将传入的元素,赋予此位置,然后返回此位置上的原始元素。

 public E set(int index, E element) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        E oldValue = (E) elementData[index];
        elementData[index] = element;
        return oldValue;
    }
ArrayList# remove(int index)

根据指定的下标删除该位置上的元素

  public E remove(int index) {
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        modCount++;
        E oldValue = (E) elementData[index];

        int numMoved = size - index - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work

        return oldValue;
    }

首先获取到该位置上的元素,然后以此下标(index + 1)为节点,截取并重新拷贝生成一个新的数组(保证数组的连续性),元素数量减一(size--),此时数组的最后两个元素是相同的,只要将最后一个元素的值设置为空即可,最后返回原始元素

ArrayList#remove(Object o)

删除指定元素

  public boolean remove(Object o) {
       if (o == null) {
           for (int index = 0; index < size; index++)
               if (elementData[index] == null) {
                   fastRemove(index);
                   return true;
               }
       } else {
           for (int index = 0; index < size; index++)
               if (o.equals(elementData[index])) {
                   fastRemove(index);
                   return true;
               }
       }
       return false;
   }

   private void fastRemove(int index) {
       modCount++;
       int numMoved = size - index - 1;
       if (numMoved > 0)
           System.arraycopy(elementData, index+1, elementData, index,
                            numMoved);
       elementData[--size] = null; // clear to let GC do its work
   }

这里暂且不讨论为null的情况,首先遍历数组找到与指定元素相同的元素,并将下标传递给fastRemove(),此方法跟remove()基本一致,同样以该下标为节点,拷贝复制一份新的数组,并删除最后一个重复元素即可。
ps:

我们可以看到,删除指定元素的时候,只是遍历了一次,如果有就删除找到的第一个指定元素,然后返回,如果arryList中有多个重复元素的话,只能删除数组靠前的元素,其他元素则不变

关于其他的一些操作性的方法这里就不多说了,其实主要就是对数组的增删改查,通过上面几个方法的说明,可以看出,ArrayList本质就是一个数组,不过在数组长度不满足需求时,会自动的“扩容”,每次扩容是原容量的1.5倍(容量为1时例外,此时的容量为1 + 1 ),删除指定位置元素或指定元素以,都会重新的拷贝数组进而生成新的数组,所以在增删操作上相比于查询修改花费的时间要长一些

下面来说ArrayList的迭代器

Iterator

我们平获取迭代器的方法是ArrayList.iterator()
ArrayList#iterator()

public Iterator<E> iterator() {
        return new Itr(); //直接返回了一个Itr对象,Itr的实现就在ArrayList中
    }

Itr继承于Itorator,并重写了它的四个方法:

hasNext 判断当前迭代器还有下一个元素
next 获取到当前迭代器的下一个元素
remove 删除当前迭代器中所持有的元素
forEachRemaining 暂且不知

Itr中还有四个成员变量:

limit 当前ArrayList中的元素数量
cursor 遍历到的当前元素的下个元素的下标,主要用来与limit对比,如果比limit小,则说明当前的元素后面还有其他元素,否则的话说明当前元素为最后一个元素
lastRet 最后一次获取next值时的下标,即当前便利到的当前元素的下标
expectedModCount 前面我们再说ArrayList#add()时有一个遗留问题,即modCount的作用,主要是用来判断在迭代过程中,是否对集合进行了其他的操作,如果一致,则说明迭代过程中没有对集合进行其他的操作,否则会抛出异常

我们先来回忆下,平常迭代器的用法:

        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add("chuan");
        arrayList.add("chuan");
        arrayList.add("chuan");
        Iterator<String> iterator = arrayList.iterator();
        while (iterator.hasNext()){
            String str = iterator.next();
            Log.i("chuan", str);
        }

ok,我们来看hasNext()以及next()

ArrayList#Itr#hasNext()

// 通过cursor与limit做对比,来判断是否有余下的元素
  public boolean hasNext() {
        return cursor < limit;
    }

ArrayList#Itr#next()

        public E next() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            int i = cursor;
            if (i >= limit)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

首先对modeCount进行判断,是否在在获取到迭代器之后有其他的对集合的操作,如果有则抛出ConcurrentModificationException;然后对cursor进行判断,如果cursor >= limit则抛出NoSuchElementException,之后再获取到当前ArrayList的elementData数组,然后在原始cursor的值的基础上+1,获取到新的cursor的值,并将原始的cursor的值赋予lastRet,最后根据lastRet获取到elementData中的元素

仅仅从这两个方法来看,更像是一个普通的for循环,cursor为index值

继续往下看:
ArrayList#Itr#remove()

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
                limit--;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

前面的两个判断不再多说,这里会调用当前ArrayList的remove方法,入参为lastRet,同时会同步Itr的成员变量

cursor被赋值lastRet,因为是删除操作,所以cursor会减一,也就是lastRet
lastRet = -1,上面有一个判断,lastRet<0状态下,不能进行remove操作,也就是说每一次next只能进行一次Remove操作,即删除当前迭代到的元素
expectedModeCount重新赋值modeCount,前面有说,操作集合modeCount会发生变化,在迭代期间发生变化会抛出异常,所以这里要同步下expectedModeCount
limit代表数组的元素数量,删除之后需要减一

最后来看下forEachRemaining()
ArrayList#Itr#forEachRemaining()

         public void forEachRemaining(Consumer<? super E> consumer) {
            Objects.requireNonNull(consumer);
            final int size = ArrayList.this.size;
            int i = cursor;
            if (i >= size) {
                return;
            }
            final Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length) {
                throw new ConcurrentModificationException();
            }
            while (i != size && modCount == expectedModCount) {
                consumer.accept((E) elementData[i++]);
            }
            // update once at end of iteration to reduce heap write traffic
            cursor = i;
            lastRet = i - 1;

            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }

这个方法其实跟for循环也是一样的,主要是中间的while循环,如果i != size && modeCount == expectedModCount就会通过Consumer.accept()将遍历到的数组元素回调给调用端,这个方法是可以仅仅用于遍历集合,如果需要对集合进行删除操作,那这个方法是不适用的,同时这个方法需要在Android N版本上使用,

    ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add("chuan");
        arrayList.add("chuan");
        arrayList.add("chuan");
        Iterator<String> iterator = arrayList.iterator();
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
            iterator.forEachRemaining(new Consumer<String>() {
                @Override
                public void accept(String s) {
                    Log.i("wz", "accept: " + s);
                }
            });
        }
        
    输出为:26766-26766/com.chuan.jun I/wz: accept: chuan--1
    accept: chuan--2
    accept: chuan--3    
ListItr

listItr继承了ItrListIterator,ListIterator有插入和修改删除方法,同时还具有向前遍历的方法,所以ListIterator就具备了增删改查的功能,比Itr的功能更加齐全

        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add("chuan--1");
        arrayList.add("chuan--2");
        arrayList.add("chuan--3");
        ListIterator<String> listIterator = arrayList.listIterator();
        while (listIterator.hasNext()){
            String str = listIterator.next();
            if(str.equals("chuan--2")){
                listIterator.set("chuan--3");
            }

        }
        Log.i("wz", "onResume: " + arrayList.toString());
        
        输出为:27152-27152/com.chuan.jun I/wz: onResume: [chuan--1, chuan--3, chuan--3]

下面来看下ListItr
ArrayList中初始化ListItr的方法有两个地方

   public ListIterator<E> listIterator(int index) {
        if (index < 0 || index > size)
            throw new IndexOutOfBoundsException("Index: "+index);
        return new ListItr(index);
    }
    public ListIterator<E> listIterator() {
        return new ListItr(0);
    }

一个需要传入一个下标值,一个则不需要,但是会默认传入0

ArrayList#ListItr#ListItr()

        ListItr(int index) {
            super();
            cursor = index;
        }

可以看到下标值赋予了cursor,因为ListItr继承于Itr所以,cursor即为Itr中的cursor,代表迭代到当前元素的下一个元素的下标值.
我们来看ListItr中重写的方法,主要是重写的ListIterator中的方法

hasPrevious 判断当前迭代到的元素前面是否还有其他的元素
nexIndex 获取到当前元素的下一个元素的下标值
prevoousIndex 获取到当前元素的前一个元素的下标值
previous 获取到当前迭代到的元素的上一个元素的值,与Itr中的next想对应
set 修改当前遍历的元素
add 在当前迭代到的元素位置插入一个新的元素

    // 判断是否还有上一个元素,如果当前元素为0,说明当前元素是首元素,即往前无元素
        public boolean hasPrevious() {
            return cursor != 0;
        }
    // 获取下/上一个或者当前元素的cursor值  如果调用了next()则cursor代表下一个元素的下标,如果调用了previous则代表上一个元素的下标,如果在next()/previous()之前调用 则代表当前元素的下标
        public int nextIndex() {
            return cursor;
        }
        // 获取当前元素下标的上一个元素下标
        public int previousIndex() {
            return cursor - 1;
        }

上面的三个方法比较简单,这里不再多说
ArrayList#ListItr#previous()

        public E previous() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
            int i = cursor - 1;
            if (i < 0)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i;
            return (E) elementData[lastRet = i];
        }

基本跟next()一样的道理,获取当前下标的上一个元素

ArrayList#ListItr#set()

        public void set(E e) {
            if (lastRet < 0)
                throw new IllegalStateException();
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();

            try {
                ArrayList.this.set(lastRet, e);
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        public void add(E e) {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();

            try {
                int i = cursor;
                ArrayList.this.add(i, e);
                cursor = i + 1;
                lastRet = -1;
                expectedModCount = modCount;
                limit++;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

set() add()也是一样的道理
总的来说ListItr要比Itr的功能要强大一些

SubList

subList也是ArrayList的一个内部类,主要是为了做数组的截取使用,他持有外部ArrayList的一个引用,因此他拥有add() remove() get()等一些增删改查的功能,还拥有迭代器,不过操作的最终结果会影响到外部ArrayList的变动

        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add("chuan--0");
        arrayList.add("chuan--1");
        arrayList.add("chuan--2");
        arrayList.add("chuan--3");
        arrayList.add("chuan--4");
        List<String> subList = arrayList.subList(1,3);
        Log.i("wz", "onResume: " + subList.toString());
        subList.remove(0);
        Log.i("wz", "onResume: " + arrayList.toString());
        
        输出为:
        28391-28391/com.chuan.jun I/wz: onResume: [chuan--1, chuan--2]
        onResume: [chuan--0, chuan--2, chuan--3, chuan--4]

可以看到得到的subList()返回的是一个List,然后对subList进行删除,原先的ArrayList也发生了变化

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

推荐阅读更多精彩内容