Java提高篇(一)----ArrayList

一、ArrayList简介

  ArrayList是实现了List接口的动态数组,元素允许为null,ArrayList是有序的。
  注意,ArrayList实现不是同步的。如果多个线程同时访问一个ArrayList实例,而其中至少一个线程从结构上修改了列表,那么它必须保持外部同步。所以为了保证同步,最好的办法是在创建时完成,以防止意外对列表进行不同步的访问:

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

二、ArrayList源码解析

  ArrayList实现List接口,底层使用数组实现,因此ArrayList的实现多是对数组进行操作。

2.1底层实现数组

transient Object[] elementData;

在讨论transient之前,有必要先搞清楚Java中序列化的含义:
  1. Java中对象的序列化指的是将对象转换成以字节序列的形式来表示,这些字节序列包含了对象的数据和信息, 一个序列化后的对象可以被写到数据库或文件中,也可用于网络传输, 一般当我们使用缓存cache(内存空间不够有可能会本地存储到硬盘)或远程调用rpc(网络传输)的时候,经常需要让我们的实体类实现Serializable接口,目的就是为了让其可序列化。
  2.当然,序列化后的最终目的是为了反序列化,恢复成原先的Java对象,要不然序列化后干嘛呢, 所以序列化后的字节序列都是可以恢复成Java对象的,这个过程就是反序列化。

关于transient关键字:

  Java中transient关键字的作用,简单地说,就是让某些被修饰的成员属性变量不被序列化,这一看好像很好理解,就是不被序列化,那么什么情况下,一个对象的某些字段不需要被序列化呢?如果有如下情况,可以考虑使用关键字transient修饰:
  1、类中的字段值可以根据其它字段推导出来,如一个长方形类有三个属性:长度、宽度、面积(示例而已,一般不会这样设计),那么在序列化的时候,面积这个属性就没必要被序列化了;
   2、其它,看具体业务需求吧,哪些字段不想被序列化;

2.2ArrayList的其他成员变量

// 序列值
private static final long serialVersionUID = 8683452581122892189L;

// 默认的初始容量,值为10
private static final int DEFAULT_CAPACITY = 10;

// 空的数组对象实例
private static final Object[] EMPTY_ELEMENTDATA = {};

// 默认容量的空数组对象实例
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

// Arraylist包含数据的条数
private int size;

  ArrayList提供了两个数组实例对象,EMPTY_ELENENTDATA 空数组实例和 DEFAULTCAPACITY_EMPTY_ELEMENTDATA带有初始容量的对象实例,初始容量为10.

2.3构造方法

  ArrayList提供了三种构造方法:
  1.public ArrayList() ;默认构造方法,该方法提供一个初始容量为10的空数组实例,jdk1.6默认初始容量为10,jdk1.7及以上默认初始容量 为0;
  2.public ArrayList(int initialCapacity);构造一个指定初始容量的空列表
  3.public ArrayList(Collection<? extends E> c);构造一个包含指定元素集的列表,这些元素按照该元素集迭代器返回的序列进行排列。

/**
 * 默认构造方法 提供一个初始容量为10的空列表(空数组) jdk1.6默认初始容量是10, jdk1.7默认初始容量是0
 * 
 * */
public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

/**
 * 构造一个指定初始容量的空列表(空数组) initialCapacity ----指定初始容量
 * 
 * */
public ArrayList(int initialCapacity) {
    // 当初始容量大于0,则构建一个指定容量的数组列表
    // 当初始容量为0时,则构建一个空的数组列表
    // 当初始容量小于0时,则抛出IllegalArgumentException异常
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "
                + initialCapacity);
    }
}

/**
 * 构造一个包含指定collection元素的列表, 这些元素按照该collection迭代器返回他们的序列 排列的
 * 
 * */
public ArrayList(Collection<? extends E> c) {
    // 将collection 转换成数组对象
    elementData = c.toArray();
    // 如果collection的大小不为0
    if ((size = elementData.length) != 0) {
        // collection对象可能不能转换成object对象,有时候可能转成String
        // 参考http://www.cnblogs.com/cmdra/p/5901793.html
        // 底层的toArray实现方式不同的原因
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        // 构建一个空列表
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

2.4ArrayList的扩容机制

  ArrayList默认情况下提供一个默认容量,内部扩容时,ArrayList会先判断当前列表是否是默认初始容量列表(elementData==DEFAULTCAPACITY_EMPTY_ELEMENTDATA),如果是则默认初始容量(DEFAULT_CAPACITY)与待扩容最小容量(minCapacity)比较,取二者之间较大的;然后进行二次确认,判断最小扩容容量(minCapacity)与当前列表长度(elementData.length)比较,如果待扩容容量较大,则进行内存分配;在分配容量时会先提供一个1.5倍的新容量,如果新容量比最小容量小,则最小容量为新容量;然后新容量再与默认最大容量(Integer.MAX_VALUE - 8)比较,如果该容量大于最大容量,则分配最大容量,否则就是该新容量;
  ArrayList的扩容机制是一个不断的进行容量比较和再确认的过程;详细过程见源码分析:

/*
 * 确认是否增加容量 minCapacity 最小容量
 * 
 * */
public void ensureCapacity(int minCapacity) {

    // 最小扩展容量,如果列表实例 不是默认容量的空列表,则最小扩展容量为0,否则最小扩展容量是10,
    int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
    // any size if not default element table
    ? 0
            // larger than default for default empty table. It's already
            // supposed to be at default size.
            : DEFAULT_CAPACITY;

    // 如果最小容量 大于扩展容量,则确认增加容量,扩展最小容量个空间,否则不进行扩展
    if (minCapacity > minExpand) {
        ensureExplicitCapacity(minCapacity);
    }
}

/**
 * 内部自动扩展
 * 
 * */
private void ensureCapacityInternal(int minCapacity) {
    // 如果列表是默认空列表,则最小容量是,最小容量和默认容量中最大值,
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    // 扩展minCapacity个空间,再次确认
    ensureExplicitCapacity(minCapacity);
}

/**
 * 再次确认是否扩容
 * 
 * */
private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // 首先判断是否下溢,如果不下溢,则进行扩容
    // 防止修改容量后导致,数组溢出
    // overflow-conscious code
    // 只能防止明显的内存溢出:(即 minCapacity -
    // elementData.length最大值达到Integer.MAX_VALUE)
    // 当minCapacity - elementData.length最大值小于等于Integer.MAX_VALUE
    if (minCapacity - elementData.length > 0)
        grow(minCapacity);
}

/**
 * 定义最大数组容量,整型最大值-8 通常由于一些虚拟机需要在数组的头部保留一些信息,如果分配最大值,则可能导致数组内存溢出(OutofMemory)
 * 请求的数组大小超过虚拟机的限制
 * */
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

/**
 * 扩容--容量大小为最小容量值
 * 
 * */
private void grow(int minCapacity) {
    // 内存溢出意识
    // overflow-conscious code
    // 得到原来的容量
    int oldCapacity = elementData.length;
    // 得到新的容量===1.5倍老的容量
    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);
}

/**
 * 最大容量
 * 
 * */
private static int hugeCapacity(int minCapacity) {
    // 如果最小扩容<0,则抛出OutOfMemoryError
    // 如果minCapacity超过Integer.MAX_VALUE,则minCapacity是负数
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    // 如果最小扩容大于数组最大容量,则返回整型数最大值,否则返回最大数组容量
    return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE
            : MAX_ARRAY_SIZE;
}

2.5新增

  ArrayList提供如下几个新增方法:
  1.public boolean add(E e);在列表尾追加一个新元素e;
  2.public boolean add(E e);在指定位置处添加一个新元素,该位置及之后的元素后移;
  3.public boolean addAll(Collection<? extends E> c);追加指定的元素集到列表尾;
  4.public boolean addAll(int index, Collection<? extends E> c);在指定的位置添加元素集,该位置及之后的元素后移指定元素集个数的位置;

/**
 * 在列表尾追加新的元素,
 * 
 * */
public boolean add(E e) {
    // 首先判断是否需要扩容
    ensureCapacityInternal(size + 1); // Increments modCount!!
    elementData[size++] = e;
    return true;
}

/**
 * 在指定位置添加元素,该位置及之后元素后移
 * */
public void add(int index, E element) {
    // 范围检查
    rangeCheckForAdd(index);

    // 内部容量检查
    ensureCapacityInternal(size + 1); // Increments modCount!!
    // 将该位置及其后元素后移一位
    System.arraycopy(elementData, index, elementData, index + 1, size
            - index);
    // 替换该位置元素
    elementData[index] = element;
    //列表的长度加一
    size++;
}

/**
  * 添加指定Collection元素到表尾
  * 
  * */
 public boolean addAll(Collection<? extends E> c) {
     //先将集合c转换成对象 数组
        Object[] a = c.toArray();
        //数组的长度
        int numNew = a.length;
        //检查是否需要扩容,容量为当前列表长度+集合列表长度
        ensureCapacityInternal(size + numNew);  // Increments modCount
        //复制数组到列表尾
        System.arraycopy(a, 0, elementData, size, numNew);
        //改变size大小
        size += numNew;
        //如果集合不为空,返回追加成功
        return numNew != 0;
    }
 
 /**
  * 添加元素集到指定位置,该位置及后面元素后移
  * 
  * 
  * 
  * */
 public boolean addAll(int index, Collection<? extends E> c) {
     //添加范围检查
        rangeCheckForAdd(index);

        //元素集转成对象数组
        Object[] a = c.toArray();
        //元素集个数
        int numNew = a.length;
        //内部扩容检查
        ensureCapacityInternal(size + numNew);  // Increments modCount
        //待移动元素个数
        int numMoved = size - index;
        
        //将index及之后的元素元素后移numMoved位
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,
                             numMoved);
        //将元素插入列表中
        System.arraycopy(a, 0, elementData, index, numNew);
        //列表长度改变
        size += numNew;
        return numNew != 0;
    }

 /**
  * 范围检查
  * 
  * */
  private void rangeCheck(int index) {
      //如果当前位置下标大于列表长度,抛出outOfBoundsMsg异常
        if (index >= size)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }
  
  /**
   * 添加元素到指定下标处时,范围检查
   * */
  private void rangeCheckForAdd(int index) {
      //下标不合法时,抛出outOfBoundsMsg异常
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

2.6删除

  ArrayList提供如下几个删除方法:
  1.public E remove(int index);移除指定位置处的元素,并返回该移除的元素值;
  2.public boolean remove(Object o);移除首次出现的元素;
  3.public boolean removeAll(Collection<?> c);移除列表中与元素集相同的所有元素;
  4.public boolean retainAll(Collection<?> c);移除列表中与元素集不同的所有元素;

/**
 * 移除指定位置处的元素,并返回该移除的元素值
 * 
 * */
public E remove(int index) {
    //范围检查
    rangeCheck(index);

    modCount++;
    //得到该位置处的元素
    E oldValue = elementData(index);
    
    //需要移动的元素数
    int numMoved = size - index - 1;
    //需要移动的数大于0,则将钙元素后面的元素前移
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    //将最后一位置为空,size并减一
    elementData[--size] = null; // clear to let GC do its work

    return oldValue;
}

/**
 * 移除第一次出现的元素Object
 * 
 * */
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;
        //从index+1处的元素前移
        if (numMoved > 0)
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        elementData[--size] = null; // clear to let GC do its work
    }

 /**
   * 移除列表中与元素集相同的所有元素
   * */
  public boolean removeAll(Collection<?> c) {
      //检查元素集是否为空
        Objects.requireNonNull(c);
        return batchRemove(c, false);
    }
  /**
   * 移除列表中与元素集不同的所有元素
   * 
   * */
  public boolean retainAll(Collection<?> c) {
      //检查元素集是否为空
        Objects.requireNonNull(c);
        //移除元素
        return batchRemove(c, true);
    }
  
  /**
   * ?
   * 移除元素
   * 
   * */
  private boolean batchRemove(Collection<?> c, boolean complement) {
        final Object[] elementData = this.elementData;
        int r = 0, w = 0;
        boolean modified = false;
        try {
            for (; r < size; r++)
                //判断是否包含该元素,complement为ture,则为相同元素,否则为不同元素
                if (c.contains(elementData[r]) == complement)
                    elementData[w++] = elementData[r];
        } finally {
            // Preserve behavioral compatibility with AbstractCollection,
            // even if c.contains() throws.
            if (r != size) {
                System.arraycopy(elementData, r,
                                 elementData, w,
                                 size - r);
                w += size - r;
            }
            if (w != size) {
                // clear to let GC do its work
                for (int i = w; i < size; i++)
                    elementData[i] = null;
                modCount += size - w;
                size = w;
                modified = true;
            }
        }
        return modified;
    }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 210,978评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 89,954评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,623评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,324评论 1 282
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,390评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,741评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,892评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,655评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,104评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,451评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,569评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,254评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,834评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,725评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,950评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,260评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,446评论 2 348

推荐阅读更多精彩内容