关闭

[Jdk源码阅读]ArrayList实现

标签: jdkarraylist阅读源码
474人阅读 评论(0) 收藏 举报
分类:

本文来自mrcode markdown博客:http://www.mrcode.cn/zhuqiang/article/details/20.html

关键属性:

属性 说明
transient Object[] elementData; 底层使用该Object数组进行存储元素,并且该属性的访问权限是:同类同包,且不序列化
modCount 从java.util.AbstractList 继承的字段;记录的应该是被修改的次数,比如add一次就等于修改了一次

入门

用以下简单的示例进行分析

        ArrayList<String> list = new ArrayList<String>();

        list.add("1");
        list.add("2");
        list.add("3");
        list.add("4");
        list.add("1");
        list.add("6");
        list.add("7");
        list.add("8");
        list.add("9");
        list.add("10");
        list.add("11");

        list.remove("1");
        list.remove(null);
        list.remove(1);
        list.contains("5");
        list.ensureCapacity(5);

1.1. add 添加元素方法分析

    // 步骤1:空参
    public ArrayList() {
        // 全局默认的空元素数组
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    // 步骤2:添加元素
    public boolean add(E e) {
        //负责扩容elementData仓库,当前列表长度+1.为当前数据所需要的最小容量
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //把元素添加到列表(对外我们看到的arraylist列表)末尾,并且让列表的大小加1
        elementData[size++] = e; //由于支持的最大容量是Integer.MAX_VALUE,这里如果超过了最大容量是不是就会抛出下标越界异常呢?
        return true;
    }

    //该方法负责扩容elementData仓库;该类add重载的方法都调用了该方法,在下图可以看到
    private void ensureCapacityInternal(int minCapacity) {
        // 当仓库为默认空数组的时候,计算最小容量
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            //默认容量=10;
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

    // 记录修改的次数;判断是否需要进行扩容; 在jdk文档中也说了:在添加大量元素之前可以使用ensureCapacity方法来增加数组仓库的容量,减少copy的次数,提高性能
    private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        // 这个最小容量相当于预判端 已有的容量仓库是否能装得下增加的数据
        if (minCapacity - elementData.length > 0)
            grow(minCapacity);
    }
    // 负责扩容
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;
        // 每次扩容是源存储仓库的一半,/2
        int newCapacity = oldCapacity + (oldCapacity >> 1);
        // 特殊判断:当this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA的时候,上面的newCapacity算出来会=0,所以这里需要初始化一个最小容量。
        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:
        //把旧的存储仓库进行copy到一个新的数组中,并返回
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
    // 这里针对最大容量超过了 默认的容量的处理返回容量:MAX_ARRAY_SIZE在源码中是Integer.MAX_VALUE-8,也就是说arraylist的最大容量是Integer.MAX_VALUE,
    private static int hugeCapacity(int minCapacity) {
        if (minCapacity < 0) // overflow
            throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ?
            Integer.MAX_VALUE :
            MAX_ARRAY_SIZE;
    }

所有添加元素的方法都调用了该方法进行判断扩容

1.2.remove 移除元素方法分析

    // 步骤 3:移除元素
    public boolean remove(Object o) {
        //针对null元素的移除,和非null元素的代码差不多
        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)
            //从数组仓库中 index+1 到 列表size-1 之前的所有元素都重新复制到 数组仓库中:从index索引开始覆盖,最后就会存在多出来一个元素。这里也就是把删除掉元素后面的所有元素都往前进行了挪动,并且是以覆盖的方式进行挪动。
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //把多出来的元素置为null,并且把列表大小-1;
        elementData[--size] = null; // clear to let GC do its work
    }

1.3. contains 是否包含指定的元素方法分析

如果认真看了上面两个方法的分析,这里的就很简单了。能看出来,判断什么的几乎上都是一样的;

    public boolean contains(Object o) {
        return indexOf(o) >= 0;
    }
    // 返回此列表中首次出现的指定元素的索引
    public int indexOf(Object o) {
        if (o == null) {
            for (int i = 0; i < size; i++)
                if (elementData[i]==null)
                    return i;
        } else {
            for (int i = 0; i < size; i++)
                if (o.equals(elementData[i]))
                    return i;
        }
        return -1;
    }

1.4 Iterator 遍历

    private class Itr implements Iterator<E> {
        //当前游标
        int cursor;       // index of next element to return; 记录下一个元素的索引
        int lastRet = -1; // index of last element returned; -1 if no such 记录最后一次的索引(也就是上一次元素的索引)
        int expectedModCount = modCount; //获取itr对象最后修改的次数(相当于版本吧,用来判断在itr期间该list是否被更改,用来进行快速失败,因为在迭代获取的时候,如果list被更改,那么获取到的数据不准不说,还会导致程序出错)

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification(); //检查list是否被更改,只是判断了修改版本,快速失败
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }
        //itr 的删除
        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification(); //同样再次检查是否修改

            try {
                ArrayList.this.remove(lastRet); //调用原来的删除方法删除元素,在元素删除的时候数组都会被copy一次,那么元素中的原来的索引就会发生变化
                cursor = lastRet;// 重置游标到当前的元素,因为当前的被删除掉了,那么下一次获取的时候就是下一个元素的索引
                lastRet = -1; //该参数是在获取的时候 根据cursor游标进行赋值的,所以这里重置为-1; 是因为如果都删除完了还调用next方法,就会抛出异常。
                expectedModCount = modCount; //修正最新的修改次数。
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

        final void checkForComodification() {
            if (modCount != expectedModCount)
                throw new ConcurrentModificationException();
        }
    }

1.4.1. 总结

  1. 为什么在itr中删除元素比在fori中删除list中的元素 要方便很多,在fori中删除list元素,你要考虑到当前的索引是否超过了大小,而在itr中不用考虑呢?
    因为itr中记录了当前的游标,删除操作还是委托的原来的删除方法,那么也会导致list的数组大小和元素索引发生变化,正是因为有了这个游标,才解决了上面的问题。
  2. 在其他方面,防止多线程对外修改导致itr内部数据计算出错,使用了检测修改次数的方式进行快速失败

总结

第一次认真的阅读Jdk的源码,网上说该工具类是相对简单的实现,先从这个类看起。现在看来果然不错。
从上面三个方法,基本上就把该类给整明白了。

  1. 线程是不安全的
  2. 底层使用一个Object数组进行存储元素
  3. 可自动扩容
  4. 允许null元素
  5. 由于扩容策略,在大量插入元素的时候可以使用ensureCapacity方法来增加数组容量
0
0
查看评论
发表评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场

JDK部分源码阅读与理解

本文为博主原创,允许转载,但请声明原文地址:http://www.coselding.cn/article/2016/05/31/JDK部分源码阅读与理解/    不喜欢重复造轮子,不喜...
  • u014394255
  • u014394255
  • 2016-12-03 23:41
  • 3449

有关阅读JDK源码的看法

源码阅读,我觉得最核心有三点:技术基础+强烈的求知欲+耐心。  说到技术基础,我打个比方吧,如果你从来没有学过Java,或是任何一门编程语言如C++,一开始去啃《Core Java》,你是很难从...
  • mijinghjb
  • mijinghjb
  • 2014-04-25 16:49
  • 3140

【JDk源码解析之一】ArrayList源码解析

1.ArrayList的继承关系如图所示: 2.宏观上说,ArrayList是基于动态数组实现的,数组具有按索引查找的特性,所以访问很快,适合经常查询的数据。 3.具体源码解析。    为什么说A...
  • weililansehudiefei
  • weililansehudiefei
  • 2017-04-15 02:12
  • 380

ArrayList源码分析(基于JDK8)

ArrayList 源代码解读,以及ArrayList特点总结
  • fighterandknight
  • fighterandknight
  • 2017-03-12 00:23
  • 1773

JDK源码阅读——Collection

JDK源码阅读——Collection
  • xmkid
  • xmkid
  • 2016-03-19 22:47
  • 874

jdk源码阅读之ArrayList

ArrayList是我们java编码过程中最常用的集合类型。它在java collection framework中的位置如下: ArrayList是List的可变大小实现,它实现了List的所...
  • jiangmingzhi23
  • jiangmingzhi23
  • 2017-12-17 18:09
  • 25

jdk源码阅读一:ArrayList

ArrayList介绍 ArrayList是我们使用的最多的集合类了。特点主要如下: 基于数组实现底层存储,容量可以自动扩展。 实现List全部接口。 允许控对象 提供操作数组容量大小的方法。 粗略...
  • taotoxht
  • taotoxht
  • 2017-01-14 22:59
  • 282

JDK7源码阅读-String

三个顶层接口 Serializable : 类通过实现 java.io.Serializable 接口以启用其序列化功能。未实现此接口的类将无法使其任何状态序列化或反序列化。可序列化类的所有子类型本身...
  • mr_zhuqiang
  • mr_zhuqiang
  • 2017-03-10 16:36
  • 594

JDK源码阅读——ArrayList

jdk源码阅读-ArrayList
  • kingszelda
  • kingszelda
  • 2017-02-06 21:08
  • 173

jdk源码阅读——ArrayList

jdk源码阅读——ArrayList
  • xhwwc110
  • xhwwc110
  • 2017-03-10 18:47
  • 223
    个人资料
    • 访问:155658次
    • 积分:2330
    • 等级:
    • 排名:第18569名
    • 原创:86篇
    • 转载:27篇
    • 译文:0篇
    • 评论:60条
    关于博客
    • 本博客记录一些学习笔记和工作开发中遇到的一些问题
    • QQ:99299684
    相关博客和项目
    1. mrcode 博客
    2016.02-2016.10为了学习技术而写的一个多人使用的博客
    2. 微信SDK自用封装
    工作中封装了一部分用到的微信api,搭成了一个架子,扩展方便。
    3. GitBook
    最近发现Gitbook能更好的记录笔记,以后就转移到这里了
    4. GitBook 自开发插件
    在使用GitBook过程中,方便使用,模仿开发了一些插件。
    最新评论