ArrayLIst

Base:JDK1.8

一、ArrayList:

首先ArrayList是最多见的一种数据结构,数组。咱们知道在Java中,咱们若是想要使用数组,能够某种数组(int为例子)html

int arr=new int[10];java

这种形式进行去进行声明,不过这有必定的缺憾,就是若是我想存储的数据超过10个,就没法存储了,也就是说这个长度是不能改变的。c++

而ArrayList就很好的解决了这样一个问题,可以动态扩容,可以存储任何对象(基本类型会自动变成包装了类)。算法

二、继承的类and实现的接口:

public class ArrayList<E> extends AbstractList<E>
        implements List<E>, RandomAccess, Cloneable, java.io.Serializable

 

这个是源代码,首先继承了AbstractList类,这个类是一个抽象的类,里面定义的方法有:数组

2.一、AbstractList:

因为LinkList也实现了这个类,因此里面定义的方法都是一些共有的方法。(下篇就是LinkList)。安全

2.二、List接口:

接口中主要定义了不少经常使用的方法。数据结构

ps:我看到 AbstractList中有的方法跟List中的方法是同名方法,做为重载,为何不放在一个里面呢?多线程

-----------------------------------------------app

若是有谁能帮忙回答一下,感激涕零dom

------------------------------------------------

2.三、RandomAccess

/* * Copyright (c) 2000, 2006, Oracle and/or its affiliates. All rights reserved. * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms. * */

package java.util;

/** * Marker interface used by <tt>List</tt> implementations to indicate that * they support fast (generally constant time) random access. The primary * purpose of this interface is to allow generic algorithms to alter their * behavior to provide good performance when applied to either random or * sequential access lists. * * <p>The best algorithms for manipulating random access lists (such as * <tt>ArrayList</tt>) can produce quadratic behavior when applied to * sequential access lists (such as <tt>LinkedList</tt>). Generic list * algorithms are encouraged to check whether the given list is an * <tt>instanceof</tt> this interface before applying an algorithm that would * provide poor performance if it were applied to a sequential access list, * and to alter their behavior if necessary to guarantee acceptable * performance. * * <p>It is recognized that the distinction between random and sequential * access is often fuzzy. For example, some <tt>List</tt> implementations * provide asymptotically linear access times if they get huge, but constant * access times in practice. Such a <tt>List</tt> implementation * should generally implement this interface. As a rule of thumb, a * <tt>List</tt> implementation should implement this interface if, * for typical instances of the class, this loop: * <pre> * for (int i=0, n=list.size(); i &lt; n; i++) * list.get(i); * </pre> * runs faster than this loop: * <pre> * for (Iterator i=list.iterator(); i.hasNext(); ) * i.next(); * </pre> * * <p>This interface is a member of the * <a href="{@docRoot}/../technotes/guides/collections/index.html"> * Java Collections Framework</a>. * * @since 1.4 */
public interface RandomAccess {
}

 

由于里面没有任何方法,因此我也不知道具体这个类是干什么用的,只能使用度娘去查:

jdk中有个RandomAccess接口,这是一个标记接口(Marker),它没有任何方法,这个接口被List的实现类(子类)使用。若是List子类实现了RandomAccess接口,那就表示它可以快速随机访问存储的元素。RandomAccess接口的意义在于:在对列表进行随机或顺序访问的时候,访问算法可以选择性能最佳方式。

通常的列表访问算法在访问列表元素以前,都被建议先使用instanceof关键字检查一下列表是不是一个RandomAccess子类,而后再决定采用随机仍是顺序方式访问列表中的元素,这样能够保证访问算法拥有最佳的性能。

http://blog.csdn.net/zht666/article/details/51597361  博客连接。

------------------------------------------------------------------------------------------------------

若是想要知道干什么,推荐去翻译一下英语,写的仍是挺明白的。

-------------------------------------------------------------------------------------------------------

2.四、接口: Cloneable, java.io.Serializable

实现了这两个接口,也就说明ArrayList是支持  克隆和序列化的。

这两个接口貌似也是 标记性接口。没有具体的方法实现。

三、ArrayList中得数据常量、成员变量、数据结构、经常使用方法

3.一、常量

初始化的容量。

//Default initial capacity. 
 private static final int DEFAULT_CAPACITY = 10;

共享空数组?不知道做用

//Shared empty array instance used for empty instances.
 private static final Object[] EMPTY_ELEMENTDATA = {};

做用未知

   /**
     * Shared empty array instance used for default sized empty instances. We
     * distinguish this from EMPTY_ELEMENTDATA to know how much to inflate when
     * first element is added.
     */
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

数组最大长度。具体为何是这个数。看英语应该这个大小是vm 的极限?

/**
     * The maximum size of array to allocate.
     * Some VMs reserve some header words in an array.
     * Attempts to allocate larger arrays may result in
     * OutOfMemoryError: Requested array size exceeds VM limit
     */
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

3.二、成员变量

/**
     * The size of the ArrayList (the number of elements it contains).
     *
     * @serial
     */
    private int size;

表示数组大小,无需解释。

3.三、数据结构

/**
     * The array buffer into which the elements of the ArrayList are stored.
     * The capacity of the ArrayList is the length of this array buffer. Any
     * empty ArrayList with elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA
     * will be expanded to DEFAULT_CAPACITY when the first element is added.
     */
    transient Object[] elementData; // non-private to simplify nested class access

真正用来存储数据的数组。

咱们发现是:

transient的 即不可被序列化。(既然实现了Serializable接口,为何又不让数据进行可序列化呢?这里就涉及到了人家设计的巧妙之处。涉及到后面的两个方法:

writeObject(java.io.ObjectOutputStream s) 和 readObject(java.io.ObjectInputStream s))

底层是Object数组,所以它能存储各类数据。(最好使用泛型,这样更加安全高效)

因为是数组,一定的数据结构是一个顺序的结构。

这个结构是一个比较典型的:在逻辑内存在(脑海中)是连续的一片内存,在物理内存(实际电脑内存)中也是连续的一片内存。

这样势必会对内存有必定的要求,若是个人数组特别特别特别大,在内存中也得寻找能连续的起来的内存来存储它,这样就要求仍是比较大的,这个也算是一个弊端吧。

3.二、经常使用方法重要方法

初始化方法,这个最经常使用了吧,想用就得先初始化:

3.2.一、构造方法:

3.2.1.一、无参构造

这是让数组指向 默认的空的数组。

(这里其实我很不明白,elementData 引用了DEFAUL...这个是一个常量,为何初始化要这么指定呢?但愿有人能给解释一下

/**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }

3.2.1.二、带有数组长度的构造方法

这个构造方法能够指定底层数组初始化时候的长度。

/**
     * Constructs an empty list with the specified initial capacity.
     *
     * @param  initialCapacity  the initial capacity of the list
     * @throws IllegalArgumentException if the specified initial capacity
     *         is negative
     */
    public ArrayList(int initialCapacity) {
        if (initialCapacity > 0) {
            //若是指定长度>0,就使用这个长度
            this.elementData = new Object[initialCapacity];
        } else if (initialCapacity == 0) {
            //等于0 就使用共享的默认数组
            this.elementData = EMPTY_ELEMENTDATA;
        } else {
           // 不然抛出初始化异常
            throw new IllegalArgumentException("Illegal Capacity: "+
                                               initialCapacity);
        }
    }

3.2.1.三、将某Collection中数据放入ArrayList的构造方法

 /**
     * Constructs a list containing the elements of the specified
     * collection, in the order they are returned by the collection's
     * iterator.
     *
     * @param c the collection whose elements are to be placed into this list
     * @throws NullPointerException if the specified collection is null
     */
    public ArrayList(Collection<? extends E> c) {
        elementData = c.toArray();
        if ((size = elementData.length) != 0) {
            // 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;
        }
    }

这个构造方法我用的不是不少,具体的解释就看英文吧。。。

3.2.二、add

添加元素的方法,一样也是有重载。

3.2.2.一、直接放入数组尾部的add

/**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        //确保再放入一个后,数组仍是未满状态,这个方法里面会调用扩容方法。
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //注意size++ 与 ++size,这里面直接先放入尾部,而后size+1
        elementData[size++] = e;
        return true;
    }

3.2.2.二、放入指定位置的add

/**
     * Inserts the specified element at the specified position in this
     * list. Shifts the element currently at that position (if any) and
     * any subsequent elements to the right (adds one to their indices).
     *
     * @param index index at which the specified element is to be inserted
     * @param element element to be inserted
     * @throws IndexOutOfBoundsException {@inheritDoc}
     */
    public void add(int index, E element) {
        //越界检测
        rangeCheckForAdd(index);
        //确保数组放入一个后,仍是未满状态
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        //这个使用的是System中的arraycopy方法,这个方法是native的,即本地的c/c++写好的, 所以效率仍是能够的
        //方法参数arraycopy(Object src(源数组),  int  srcPos(源数组开始的位置),
        //                              Object dest(目标数组), int destPos(目标数组开始的位置),
        //                              int length(复制的数组长度))
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        //目标位置上的数据被新传来的数据覆盖
        elementData[index] = element;
        //size+1
        size++;
    }

3.2.3 ensureCapacityInternal

private void ensureCapacityInternal(int minCapacity) {
        //判断数组是否是刚刚初始化,第一次添加
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        //若是是第一次初始化,取10 和 minCapacity 中大数 做为初始化长度
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        //确认数组长度
        ensureExplicitCapacity(minCapacity);
    }

其实我仍是有点不太明白,为何初始化的时候要去指定数组引用一个final数组,然后来又初始化?为何不直接初始化呢?

3.2.4 ensureExplicitCapacity

private void ensureExplicitCapacity(int minCapacity) {
        modCount++;//每一次的操做都要modCount++,这个是基于fail-fast机制的。

        // overflow-conscious code
        //判断是否容量是否装满
        if (minCapacity - elementData.length > 0)
             //若是满,就进行扩容
             grow(minCapacity);
    }

这个函数的做用就是要去判断是否须要去进行扩容。

3.2.5 grow

private void grow(int minCapacity) {
        // overflow-conscious code
        //由于数组存满,才会进行扩容,所以老的数组长度,就是等于数组.length
        int oldCapacity = elementData.length;
        //这个 =  oldCapacity+oldCapacity/2  所以这个是1.5倍(新的是老的1.5倍,而不是增长的长度是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:
        //上面的if 判断是为了保证新的长度是大于旧的长度,毕竟int也是有范围的。
        //接下来是数组的总体复制。
        elementData = Arrays.copyOf(elementData, newCapacity);
    }

小结

以上是对 初始化,添加,扩容的一系列方法的源码展现。

基本这个就是一个 完整的 操做流程。

首先初始化(默认长度为10,也能够指定长度)--->底层实现是一个Object数组,所以能存各类对象,由于Object是全部类的超类--->根据传入的参数个数,进行add添加(分2种状况,一个是直接加到数组末尾,一个加到指定位置,根据传入的参数个数,可是这个时候并无添加到数组中)--->参数1个,先判断是否须要进行扩容,若是须要就1.5倍扩容(这个1.5倍仍是有好处的,好处baidu)--->参数2个的话,就先判断index是否越界,若是越界就抛出异常,不然就判断是否须要扩容,最后使用System.Arraycopy方法进行数组从指定位置开始,总体后移--->最后将数据放入应该的位置(末尾 or 指定位置)。

3.2.6 get

public E get(int index) {
        //下标检测
        rangeCheck(index);

        return elementData(index);
    }

get方法比较简单,先检查index 是否越界,而后 返回对应位置的数据。

@SuppressWarnings("unchecked")
    E elementData(int index) {
        return (E) elementData[index];
    }

这两个方法,合起来就是 get方法。 第二段代码的@SuppressWarnings("unchecked") ,该批注的做用是给编译器一条指令,告诉它对被批注的代码元素内部的某些警告保持静默。

3.2.7 remove

remove是有两个方法,一个直接是根据下标值,另外一个是根据对象的

3.2.7.1 remove(int)

public E remove(int index) {
        //越界检测
        rangeCheck(index);
       //基于fail-fast 机制
        modCount++;
        //先得到
        E oldValue = elementData(index);
        //求出须要移动元素的个数
        int numMoved = size - index - 1;
        if (numMoved > 0)
         //若是index不是最后一个元素,则须要将后面的全部元素进行前移,
         //这个方法在 add(int,E)中也使用过就是使用的本地方法进行数组拷贝
            System.arraycopy(elementData, index+1, elementData, index,
                             numMoved);
        //将数组最后一个制空,让gc回收
        elementData[--size] = null; // clear to let GC do its work
        //返回remove的值
        return oldValue;
    }

remove的方法代码不是不少,逻辑也很明了,中间也是使用了一个arraycoyp方法。

3.2.7.2 remove(Object)

public boolean remove(Object o) {
        if (o == null) {
            //首先判断是否是为null
            for (int index = 0; index < size; index++)
                if (elementData[index] == null) {
                    fastRemove(index);
                    return true;
                }
        } else {
            //若是不是null,就一次去使用equals去判断每一,是否同样
            for (int index = 0; index < size; index++)
                if (o.equals(elementData[index])) {
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }

注意:这个方法也很简单,可是须要主意的一点是,当你使用add(1) 的时候,jdk会自动识别,将你的int 类型自动装箱转换为Integer,add(1) ==add(new Interger(1))

可是当使用remove()方法的时候,就不会帮你自动装箱,转换为Interger类型了。

演示:测试代码

public static void main(String[] args) {
		ArrayList<Integer> arr=new ArrayList<Integer>();
		for(int i=9;i>-1;i--){
			arr.add(i);
		}
		for (Integer integer : arr) {
			System.out.print(integer.intValue()+" ");
		}
		System.out.println();
		arr.remove(2);
		for (Integer integer : arr) {
			System.out.print(integer.intValue()+" ");
		}
		
	}

结果:

这个很明显的是删除了index为2的 值。

再次测试:

public static void main(String[] args) {
		ArrayList<Integer> arr=new ArrayList<Integer>();
		for(int i=9;i>-1;i--){
			arr.add(i);
		}
		for (Integer integer : arr) {
			System.out.print(integer.intValue()+" ");
		}
		System.out.println();
                //此处进行了修改
		arr.remove(new Integer(2));
		for (Integer integer : arr) {
			System.out.print(integer.intValue()+" ");
		}
		
	}

结果:

这个结果很明显的说明了删除的是 值是2的数据。

 

所以,当储存的是整形的时候,须要去主意,是remove的index 仍是remove的 O

 

3.2.8 set

public E set(int index, E element) {
        rangeCheck(index);
         
        E oldValue = elementData(index); 
        //直接放入指定位置,覆盖原来数据就行。
        elementData[index] = element;
        return oldValue;
    }

set 方法就是覆盖原来位置上的数据。很是简单。

3.3 其余经常使用方法或者简单方法

这里面就不贴方法了,由于比较简单,简单的列举一下其余方法

public boolean isEmpty() 判断数组是否是空
public int size() 返回数组的长度
 public boolean contains(Object o) 返回是否包含指定对象
 public int indexOf(Object o) contains调用的具体的业务逻辑去判断是否包含的方法
public int lastIndexOf(Object o) 从后往前判断是指定对象的下标
public Object[] toArray() 返回一个数组
private void rangeCheckForAdd(int index) 边界检测,越界直接抛出异常
public Iterator<E> iterator() 返回一个迭代器(这有一个内部类),迭代器模式
   
   

3.4 补充方法

前面说到这个类是实现了一个 java.io.Serializable 接口,可是 数组Object 却又被 transient 修饰,为何要这么作呢?

假设有这么一个场景:假设如今数据量大,ArrayList的 length=20W,而后 size=11W。

因为数组会被自动的填充,Object 都会是null 若是这样 直接去 序列化,就会很浪费,毕竟后面的null不须要去序列化。所以他就去重写了方法:

3.4.1 writeObject

private void writeObject(java.io.ObjectOutputStream s)
        throws java.io.IOException{
        // Write out element count, and any hidden stuff
        //记录序列化前的 fail-fast 
        int expectedModCount = modCount;
        s.defaultWriteObject();

        // Write out size as capacity for behavioural compatibility with clone()
        //记录数组的大小
        s.writeInt(size);
        //具体的序列化的方法,只序列化有数据的部分,其余部分略掉
        // Write out all elements in the proper order.
        for (int i=0; i<size; i++) {
            s.writeObject(elementData[i]);
        }
        //fail-fast机制,若是序列化后发现数组被修改了(多线程中),就抛出异常
        if (modCount != expectedModCount) {
            throw new ConcurrentModificationException();
        }

 

3.4.2 readObject

private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        elementData = EMPTY_ELEMENTDATA;

        // Read in size, and any hidden stuff
        s.defaultReadObject();

        // Read in capacity
        s.readInt(); // ignored

        if (size > 0) {
            // be like clone(), allocate array based upon size not capacity
            ensureCapacityInternal(size);

            Object[] a = elementData;
            // Read in all elements in the proper order.
            //反序列化的核心
            for (int i=0; i<size; i++) {
                a[i] = s.readObject();
            }
        }
    }

这两个方法都是很好的防止了序列化和反序列化哪些没必要要的数据,所以数组Object才会被声明为:transient 。

-----------------------------------------------------------------------------

不保证代码彻底正确,也不保证代码是最优。

仅仅是根据本身的理解,写出来直观的代码,方便理解。

错误请指出,感激涕零!

相关文章
相关标签/搜索
本站公众号
   欢迎关注本站公众号,获取更多信息