Android中需要了解的数据结构(一)

Java容器类

Java容器类是java提供的工具包,包含了常用的数据结构:集合、链表、队列、栈、数组、映射等。
Java容器主要可以划分为4个部分:List列表、Set集合、Map映射、工具类(Iterator迭代器、Enumeration枚举类、Arrays和Collections)

通过上图,可以把握两个基本主体,即Collection和Map。

  • Colletcion是一个接口,是高度抽象出来的集合,它包含了集合的基本操作和属性。Collection包含了List和Set两大分支。
  • List是一个有序的队列,每一个元素都有它的索引。第一个元素的索引值是0。List的实现类有LinkedList, ArrayList, Vector, Stack。
  • Set是一个不允许有重复元素的集合。 Set的实现类有HastSet和TreeSet。HashSet依赖于HashMap,它实际上是通过HashMap实现的;TreeSet依赖于TreeMap,它实际上是通过TreeMap实现的。
  • Map是一个映射接口,即key-value键值对。Map中的每一个元素包含“一个key”和“key对应的value”。 AbstractMap是一个抽象类,它实现了Map中大部分的API。而HashMap,TreeMap,WeakHashMap都是继承AbstractMap。Hashtable虽然继承Dictionary,但是实现的Map接口。
  • Iterator是遍历集合的工具,即我们通常通过Iterator迭代器来遍历集合。我们说Collection依赖于Iterator,是因为Collection的实现类都要实现iterator()函数,返回一个Iterator对象。ListIterator是专门为遍历List而存在的。
  • Arrays和Collections是操作数组、集合的两个工具类。

Collection接口

public interface Collection extends Iterable {}
复制代码

它是一个接口,是高度抽象出来的集合,它包含了集合的基本操作:添加、删除、清空、遍历(读取)、是否为空、获取大小、是否保护某元素等等。
在Java中所有实现了Collection接口的类都必须提供两套标准的构造函数,一个是无参,用于创建一个空的Collection,一个是带有Collection参数的有参构造函数,用于创建一个新的Collection,这个新的Collection与传入进来的Collection具备相同的元素。
例如ArrayList:

    public ArrayList() {
        throw new RuntimeException("Stub!");
    }

    public ArrayList(Collection c) {
        throw new RuntimeException("Stub!");
    }
复制代码

List接口

public interface List extends Collection {} List是一个继承于Collection的接口,List是集合的一种。List是有序的队列,List中每一个元素都有一个索引;第一个元素索引值是0,往后就依次+1,List中允许有重复的元素。 既然List是继承于Collection接口,它自然就包含了Collection中的全部函数接口;由于List是有序队列,它也额外的有自己的API接口。主要有“添加、删除、获取、修改指定位置的元素”、“获取List中的子队列”等。

    // Collection的API
    abstract boolean         add(E object)
    abstract boolean         addAll(Collection collection)
    abstract void            clear()
    abstract boolean         contains(Object object)
    abstract boolean         containsAll(Collection collection)
    abstract boolean         equals(Object object)
    abstract int             hashCode()
    abstract boolean         isEmpty()
    abstract Iterator     iterator()
    abstract boolean         remove(Object object)
    abstract boolean         removeAll(Collection collection)
    abstract boolean         retainAll(Collection collection)
    abstract int             size()
    abstract  T[]         toArray(T[] array)
    abstract Object[]        toArray()
    // 相比与Collection,List新增的API:
    abstract void                add(int location, E object)
    abstract boolean             addAll(int location, Collection collection)
    abstract E                   get(int location)
    abstract int                 indexOf(Object object)
    abstract int                 lastIndexOf(Object object)
    abstract ListIterator     listIterator(int location)
    abstract ListIterator     listIterator()
    abstract E                   remove(int location)
    abstract E                   set(int location, E object)
    abstract List             subList(int start, int end)
复制代码

实现List接口的集合主要有:ArrayList、LinkedList、Vector、Stack。

ArrayList

public class ArrayList extends AbstractList implements List,
    RandomAccess, Cloneable, Serializable {}
复制代码

ArrayList 是一个数组队列,相当于动态数组。与Java中的数组相比,它的容量能动态增长。它继承于AbstractList,实现了List, RandomAccess, Cloneable, java.io.Serializable这些接口。

  • RandmoAccess为List提供快速访问功能。在ArrayList中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。
  • ArrayList中的操作不是线程安全的,所以为了防止意外的非同步访问,最好在创建时声明:List list = Collections.synchronizedList(new ArrayList(...));

ArrayList有七个字段加一个定义在AbstractList的modCount:

    private static final long serialVersionUID = 8683452581122892189L;
    private static final int DEFAULT_CAPACITY = 10;
  
    private static final Object[] EMPTY_ELEMENTDATA = {};
    
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    
    // Android-note: Also accessed from java.util.Collections
    transient Object[] elementData; // non-private to simplify nested class access
    
    private int size;
    /**
     * 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;
    
    protected transient int modCount = 0;
复制代码

ArrayList的默认容量DEFAULT_CAPACITY为10,EMPTY_ELEMENTDATADEFAULTCAPACITY_EMPTY_ELEMENTDATA是两个常量。

    // 默认构造函数
    public ArrayList() {
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }
    // initialCapacity是ArrayList的默认容量大小。当由于增加数据导致容量不足时,容量会添加上一次容量大小的一半。
    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);
            }
        }
    // 创建一个包含collection的ArrayList
    public ArrayList(Collection 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;
            }
        }
复制代码

当使用有参构造函数,并且initialCapacity为0或者Colletion中没有元素的时候,返回的就是EMPTY_ELEMENTDATA。当使用默认构造函数publicArrayList(),返回DEFAULTCAPACITY_EMPTY_ELEMENTDATA。 这两个数组都是空的并不会存放值。当第一次往ArrayList添加元素的时候,其实是将元素存放到elementData中,所以真正用来存放元素的是elementData。
add方法:

    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!
        elementData[size++] = e;
        return true;
    }
   
    public void add(int index, E element) {
        if (index > size || index < 0)
            throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

        ensureCapacityInternal(size + 1);  // Increments modCount!!
        System.arraycopy(elementData, index, elementData, index + 1,
                         size - index);
        elementData[index] = element;
        size++;
    }
    public boolean addAll(Collection c) {
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
        System.arraycopy(a, 0, elementData, size, numNew);
        size += numNew;
        return numNew != 0;
    }
    public boolean addAll(int index, Collection c) {
        rangeCheckForAdd(index); //判断索引位置是否正确
        Object[] a = c.toArray();
        int numNew = a.length;
        ensureCapacityInternal(size + numNew);  // Increments modCount
         //将ArrayList容器从index开始的所有元素向右移动到index+numNew的位置,从而腾出numNew长度的空间放c
        int numMoved = size - index;
        if (numMoved > 0)
            System.arraycopy(elementData, index, elementData, index + numNew,numMoved);
        System.arraycopy(a, 0, elementData, index, numNew);
        size += numNew;
        return numNew != 0;
    }
复制代码

add(E e)将元素直接添加到列表的尾部。另外3种通过System.arraycopy() 将数组进行拷贝。
add(int index, E element)通过将index的位置空出来,进行数组数据的右移,这是非常麻烦和耗时的,所以如果指定的数据集合需要进行大量插入(中间插入)操作,需要考虑性能的消耗。
addAll(Collection c)按照指定 collection 的迭代器返回的元素顺序,将该 collection 中的所有元素添加到此列表的尾部。
addAll(int index, Collection c)从指定的位置开始,将指定 collection 中的所有元素插入到此列表中。
remove方法:

    public E remove(int index) {
        rangeCheck(index);
        modCount++;
        E oldValue = elementData(index);
        //向左移的位数,下标从0开始,需要再多减1
        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;
    }
    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()方法用于移除指定位置的元素,和remove方法类似,区别是void类型
                    fastRemove(index);
                    return true;
                }
        }
        return false;
    }
    protected void removeRange(int fromIndex, int toIndex) {
        modCount++;
        int numMoved = size - toIndex;
        System.arraycopy(elementData, toIndex, elementData, fromIndex,numMoved);
        // clear to let GC do its work
        int newSize = size - (toIndex-fromIndex);
        for (int i = newSize; i < size; i++) {
            elementData[i] = null;
        }
        size = newSize;
    }
    public boolean removeAll(Collection c) {
        //Checks that the specified object reference is not null
        Objects.requireNonNull(c);
        //false是移除相同元素,方法retainAll中置为true,是保留相同元素
        return batchRemove(c, false);
    }
    
    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++)
                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;
    }
复制代码

扩容

    public void ensureCapacity(int minCapacity) {
        int minExpand = (elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA)
            ? 0: DEFAULT_CAPACITY;
        if (minCapacity > minExpand) {
            ensureExplicitCapacity(minCapacity);
        }
    }

    private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);
    }

    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;
        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);
    }    
复制代码

ArrayList每次新增元素时都会需要进行容量检测判断,若新增元素后元素的个数会超过ArrayList的容量,就会进行扩容操作来满足新增元素的需求。所以当我们清楚知道业务数据量或者需要插入大量元素前,可以使用ensureCapacity来手动增加ArrayList实例的容量,以减少递增式再分配的数量。

迭代效率


    public static void loopOfFor(List list){
        int value;
        int size = list.size();
        // 基本的for
        for (int i = 0; i < size; i++)
        {
            value = list.get(i);
        }
    }
    /**
     * 使用forecah方法遍历数组
     * @param list
     */
    public static void loopOfForeach(List list){
        int value;
        // foreach
        for (Integer integer : list)
        {
            value = integer;
        }
    }
    /**
     * 通过迭代器方式遍历数组
     * @param list
     */
    public static void loopOfIterator(List list){
        int value;
        // iterator
        for (Iterator iterator = list.iterator(); iterator.hasNext();)
        {
            value = iterator.next();            
        }
    }
复制代码

在遍历ArrayList中,效率最高的是loopOfFor,loopOfForeach和loopOfIterator之间关系不明确,但在增大运行次数时,loopOfIterator效率高于loopOfForeach。

LinkedList

    public class LinkedList extends AbstractSequentialList implements List, 
    Deque, Cloneable, Serializable {}
复制代码

LinkedList继承于AbstractSequentialList,实现了List, Deque, Cloneable, java.io.Serializable这些接口。
AbstractSequentialList继承AbstractList,在功能上,最大限度地减少了实现受“连续访问”数据存储所需的工作。
简单的说是你的列表需要快速的添加删除数据等,用此抽象类,若是需要快速随机的访问数据等用AbstractList抽象类。

同ArrayList一样,LinkedList中的操作不是线程安全的,所以为了防止意外的非同步访问,最好在创建时声明: List list = Collections.synchronizedList(new LinkedList(...));

LinkedList实现了一个双向列表,由first字段和last字段指向列表的头部和尾部。列表的每个节点是一个Node对象。

   private static class Node {
        E item;
        Node next;
        Node prev;
        Node(Node prev, E element, Node next) {
            this.item = element;
            this.next = next;
            this.prev = prev;
        }
    }
复制代码
      // 默认构造函数:创建一个空的链表
      public LinkedList() {
        header.next = header.previous = header;
      }
  
      // 包含“集合”的构造函数:创建一个包含“集合”的LinkedList
      public LinkedList(Collection c) {
          this();
          addAll(c);
      }
      
      public boolean addAll(Collection c) {
        return addAll(size, c);
     }
     public boolean addAll(int index, Collection c) {
        //若插入的位置小于0或者大于链表长度,则抛出IndexOutOfBoundsException异常
        checkPositionIndex(index);
        
        Object[] a = c.toArray();
        int numNew = a.length;//插入元素个数
        if (numNew == 0)
            return false;
        Node pred, succ;     //定义前导与后继
        if (index == size) {    //如果在队尾插入
            succ = null;    //后继置空
            pred = last;    //前导指向队尾元素last
        } else {            //在指定位置插入
            succ = node(index); //后继指向该位置
            pred = succ.prev;   //先导指向前一个元素
        }
        for (Object o : a) {
            @SuppressWarnings("unchecked") E e = (E) o;
            Node newNode = new Node<>(pred, e, null);//创建一个新节点,指定先导,后继置空
            if (pred == null)//如果先导不存在
                first = newNode;//表头first指向此节点
            else
                pred.next = newNode;//先导存在,则将其next指向新节点
            pred = newNode;//先导移动,继续创建新节点
        }
        if (succ == null) {
            last = pred;
        } else {
            pred.next = succ;
            succ.prev = pred;
        }
        size += numNew;
        modCount++;
        return true;
    }

复制代码

LinkedList提供了一系列API用于插入和删除元素。 例linkFirst(),linkLast(),linkBefore(), unlinkFirst(),unlinkLast(),unlink()
在get、set、add、remove方法中都用到了一个 node方法,它将输入的index与链表长度的1/2进行对比,小于则从first开始操作,否则从last开始操作,节省一般的查找时间。

    /**
     * Returns the (non-null) Node at the specified element index.
     */
    Node node(int index) {
        // assert isElementIndex(index);

        if (index < (size >> 1)) {
            Node x = first;
            for (int i = 0; i < index; i++)
                x = x.next;
            return x;
        } else {
            Node x = last;
            for (int i = size - 1; i > index; i--)
                x = x.prev;
            return x;
        }
    }
复制代码

LinkedList通过代价较低在List中间进行插入和移除,提供了优化的顺序访问,但是在随机访问方面相对较慢。

上面都提到了ArrayList、LinkedList都是非线程安全的,面对多线程对操作时,可能会产生的fail-fast事件,抛出异常java.util.ConcurrentModificationException。而ConcurrentModificationException是在操作Iterator时抛出的异常。Iterator里定义了一个叫expectedModCount的变量,初始化等于modCount的值。从ArrayList源码可以看到各种操作都会修改modCount的值。 解决方案用CopyOnWriteArrayList代替ArrayList

    //CopyOnWriteArrayList
     public boolean add(E e) {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            Object[] elements = getArray(); //copy一份原来的array
            int len = elements.length;
            Object[] newElements = Arrays.copyOf(elements, len + 1); 
            newElements[len] = e; //在copy的数组上add
            setArray(newElements); //原有引用指向修改后的数据
            return true;
        } finally {
            lock.unlock();
        }
     }
复制代码

CopyOnWriteArrayList在各种操作中都是先copy一份原来的array,然后操作,最后将原有的数据引用指向修改后的数据。

Vector

  public class Vector extends AbstractListimplements List, RandomAccess, Cloneable,
       java.io.Serializable{}
复制代码

与ArrayList相似,但是Vector是同步的。所以说Vector是线程安全的动态数组。它的操作与ArrayList几乎一样。

Vector,ArrayList与LinkedList区别,应用场景是什么?

  • Vector实现了基于动态Object数组的数据结构,线程安全,可以设置增长因子,效率比较低,不建议使用。
  • ArrayList实现了基于动态Object数组的数据结构,非线程安全,地址连续,查询效率比较高,插入和删除效率比较低。适合查询操作频繁的场景。
  • LinkedList实现了基于链表的数据结构,非线程安全,地址不连续,查询效率比较低,插入和删除效率比较高。适合插入和删除操作频繁的场景。

转载于:https://juejin.im/post/5cac034fe51d456e425dd964

你可能感兴趣的:(Android中需要了解的数据结构(一))