zoukankan      html  css  js  c++  java
  • 参考JDK1.8源码,自己写一个类似于ArrayList的动态数组

    1. ArrayList的基本实现原理

    ArrayLiST其内部用一个普通数组来存储数据,当此数组不够容纳新添加的元素的时候,则创建一个更大长度的新数组,并将原来数组中的元素复制到新数组中。

    2.ArrayList主要的全局变量/常量

    除了其他一般的全局变量之外,还有一个继承于父类的 modCount属性,它用来记录集合结构被修改的次数,主要应用在迭代过程中确认没有删除或添加元素的操作,防止出现重复遍历或遍历遗漏错误。
        // 用来储存集合元素的数组
        transient Object[] elementData; 
        //不通过构造方法指定容量时的默认容量(向ArrayList第一次添加元素时,数组elementData的长度)
        private static final int DEFAULT_CAPACITY = 10;
        /*
         * 空数组,如果使用new ArrayList(0)构造方法而不向里面添加元素,
         * 所有这类型的ArrayList对象内部的elementData都会引用这一个静态常量。
         */
        private static final Object[] EMPTY_ELEMENTDATA = {};
         // 集合包含的元素个数
        private int size;
        
        /*修改记数器,ArrayList中不含此属性,它来自于父类AbstractList
         * 当动态数组的结构(添加或删除元素)发生改变时,modCount加1
         */
        protected transient int modCount = 0;
    
        /*
         * 空数组,如果使用new ArrayList()构造方法而不向里面添加元素,
         * 所有这类型的ArrayList对象内部的elementData都会引用这一个静态常量
         */
        private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

    3.ArrayList构造方法

    1)指定初始容量的构造方法

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

    2)无参构造方法,不指定初始容量,使用默认的初始容量DEFAULT_CAPACITY

       public ArrayList() {
            this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
        }

    3)带集合参数的参构造方法,初始化后将含有集合参数对应的所有元素

        public ArrayList(Collection<? extends E> c) {
            elementData = c.toArray();//将集合转为数组
            if ((size = elementData.length) != 0) {  //构造参数集合c含有元素时
                /*
                 * 构造参数集合c内部数组不是Object[],则将其转为Object[]。
                 * 子类型向父类型转换,是安全的转换操作
                 */
                if (elementData.getClass() != Object[].class)
                    elementData = Arrays.copyOf(elementData, size, Object[].class);
            } else {
                 //构造参数集合c不含元素时
                this.elementData = EMPTY_ELEMENTDATA;
            }
        }

    4.ArrayList主要的公共方法

     1)public boolean add(E e) 在尾部添加元素

    先用ensureCapacityInternal(int)方法判断,当前elementData是否能够容纳新元素,若是不能将扩展数组长度,

    然后再进行将新元素添加到elementData数组的下标为size的位置。添加元素后,集合元素个数多了一个,最后将size加1。

          public boolean add(E e) {
              //先检查容量
              ensureCapacityInternal(size + 1); 
              //内部数组elementData的赋值
              elementData[size++] = e;
              return true;
              }

    来看ensureCapacityInternal(int)方法的实现

           private void ensureCapacityInternal(int minCapacity) {
            // 如果是空数组,则找minCapacity 、DEFAULT_CAPACITY中较大的那个数作为最小容量
                if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
                    minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
                }
           //确保显式容量
                ensureExplicitCapacity(minCapacity);
            }

    再看ensureExplicitCapacity(minCapacity)  方法的实现。

            private void ensureExplicitCapacity(int minCapacity) {
                modCount++; // 调用当前方法表明,一定会添加一个元素,那么让modCount加1
    
                /*
                 * 当数组长度确实不满足当前所需的最小容量,必须进行扩容处理。
                 * 直到这步才进行真正的数组扩容。扩容处理比较耗费资源,
                 * 最好一开始指定大致的容量个数,而不至于反复扩容。
                 */
                if (minCapacity - elementData.length > 0)//
                    grow(minCapacity);
            }

    再探真正执行数组扩容处理的方法 grow(int)

          private void grow(int minCapacity) {
                int oldCapacity = elementData.length;
                /*
                 * 新容量(即数组elements的长度),是原容量的1.5倍。
                 *  "oldCapacity >> 1"表示是oldCapacity右移一位,结果是oldCapacity的1/2
                 */
                int newCapacity = oldCapacity + (oldCapacity >> 1); 
                /*
                 * 将容量扩大至原容量的1.5倍还不够时
                 * 就将minCapacity作为新容量
                 */
                if (newCapacity - minCapacity < 0)
                    newCapacity = minCapacity;
                /*
                 * 若数组长度扩展超出int最大的可表示范围(MAX_ARRAY_SIZE= Integer.MAX_VALUE - 8)
                 * hugeCapacity(int)方法的主要代码是
                 *  "(minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE"
                 * 即取两者最接近minCapacity的那个值,以保证数组长度尽可能小
                 */
                if (newCapacity - MAX_ARRAY_SIZE > 0)
                    newCapacity = hugeCapacity(minCapacity);
                //将原数组的元素复制到更大长度的新数组上,并让elementData引用这个新数组。
                elementData = Arrays.copyOf(elementData, newCapacity);
            }

    2)public void add(int index, E element) 在指定下标处添加元素

            public void add(int index, E element) {
                rangeCheckForAdd(index);//检查下标是否越界
    
                ensureCapacityInternal(size + 1);  //同样地检查容量是否够
                //将从index下标后(包括index)的所有元素整体向后移动一个位置
                System.arraycopy(elementData, index, elementData, index + 1,
                                 size - index);
                //将原来index位置上的元素引用替换为新添加的元素引用
                elementData[index] = element;
                size++;
            }

    3) public E remove(int index)  移除指定位置的元素

            public E remove(int index) {
                rangeCheck(index);//检查下标是否越界
                modCount++; //// 移除一个元素,modCount加一;
                E oldValue = elementData(index); //保存原来index位置上的元素
    
                int numMoved = size - index - 1; //计算需要移动的元素个数
                if (numMoved > 0)
                    //将从index+1下标后(包括index+1)的所有元素整体向前移动一位
                    System.arraycopy(elementData, index+1, elementData, index,
                                     numMoved);
                /*
                 * 对于最后一个元素而言它后而已经没有元素了,也就是说没有元素能将最后一个元素覆盖,
                 * 所以要将最后一个元素赋为null,让垃圾回收器去回收它,否则可能造成内存泄漏。
                 */
                elementData[--size] = null; 
    
                return oldValue;
            }

    4) public E 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 {
                    /*
                     * 从头至尾进行遍历,对象o与循环集合时的当前元素相等
                     * 则可移除当前索引位置的元素,将退出循环,返回true。
                     * 若遍历了所有元素都没有一个元素与o相等,则集合中没有这个元素,
                     * 那么移除指定元素失败,返回false
                     */
                    for (int index = 0; index < size; index++)
                        if (o.equals(elementData[index])) {
                            /*
                             * 与remove(int)类似,fastRemove(int)只是少去了边界检查、老值保存操作,
                             * 能更快地移除元素
                             */
                            fastRemove(index); 
                            return true;
                        }
                }
                return false;
            }

     5)public E set(int index, E element)  替换指定下标对应的元素

    public E set(int index, E element) {
    rangeCheck(index); //下标边界检查
    E oldValue = elementData(index); //保存index下标处的原值
    elementData[index] = element;//将新元素的赋值给index下标元素
    return oldValue;
    }

    6)public  E get(int index)  获取指定下标对应的元素

        public E get(int index) {
            rangeCheck(index);
            return elementData(index);
        }

     这里只罗列了6种广泛使用的API,其他的API大多基于这几个方法,或是思路大致相同,我就不一 一列举了。

    5.自定义的简单动态数组

      

    package list;
    
    import java.util.Arrays;
    import java.util.Iterator;
    import java.util.NoSuchElementException;
    
    public class DynamicArray<E> implements Iterable<E> {
        private Object[] elements;// 储存元素的数组
        private int size = 0;// 当前动态数组中所含元素个数
        private static final int DEFAULT_CAPACITY = 16;// 默认的初始容量
        /*
         * 空数组,如果只创建DynamicArray动态数组而不向里面添加元素,
         *  所有这类型的DynamicArray对象内部都会引用这一个静态常量。
         *  这时运用了flyweight享元模式,共享一个对象,节省内存空间
         */
        private static final Object[] EMPTY_ELEMENTS = {};
    
    
        public boolean isEmpty() {
            return size == 0 ;
        }
    
        public DynamicArray() {
            elements = new Object[DEFAULT_CAPACITY];
        }
    
        public DynamicArray(int capacity) {
            if (capacity > 0) {
                elements = new Object[capacity];
            } else if (capacity == 0) {
                elements = EMPTY_ELEMENTS;
            } else {
                throw new IllegalArgumentException("DynamicArray的初始容量不能小于0");
            }
        }
    
      //修改记数器,当动态数组的结构(添加或删除元素)发生改变时,modCount加1
        private int modCount = 0;
    
        public int size() {
            return size;
        }
    
        public boolean add(E e) {
    
            modCount++;// 添加一个元素,modCount加1
            ensureCapacity(size + 1);// 添加元素前先检查数组elements的容量
            elements[size] = e; // 索引和长度相差1,所以不是elements[size+1]=e
            size++;
            return true;
        }
    
        public E set(int index, E e) {
            checkIndex(index);
            @SuppressWarnings("unchecked")
            E oldValue = (E) elements[index];
            elements[index] = e;
            return oldValue;
        }
    
        public boolean insert(int index, E e) {
            checkIndex(index);
    
            ensureCapacity(size + 1);
            int movedNum = size - index;
            System.arraycopy(elements, index, elements, index + 1, movedNum);
            modCount++;// 添加一个元素,modCount加1
            elements[index] = e;
            size++;
            return true;
        }
    
        // 确保最小容量
        private void ensureCapacity(int minCapacity) {
            // 如果是空数组,则找minCapacity 、DEFAULT_CAPACITY中较大的那个数作为最小容量
            if (elements == EMPTY_ELEMENTS) {
                minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
            }
            // 数组elements的长度不够,必须扩展原数组
            if (elements.length < minCapacity) {
                expand(minCapacity);
    
            }
    
        }
    
        // 容量扩展
        private void expand(int minCapacity) {
    
            int oldCapacity = elements.length;
            // 确保minCapacity大于内部数组elements的长度
            if (oldCapacity >= minCapacity) {
                return;
            }
            /*
             * 期望的容量(即数组elements的长度),是原容量的1.5倍,
             *  "oldCapacity >> 1"是右移一位, 值是oldCapacity的1/2
             */
            int desireCapacity = oldCapacity + oldCapacity >> 1;
            /*
             * 扩展后实际的容量 若将新容量扩大至原容量的1.5倍还不够时,就将minCapacity作为新容量
             */
            int newCapacity = desireCapacity > minCapacity ? desireCapacity : minCapacity;
            
            elements = Arrays.copyOf(elements, newCapacity);
        }
    
        public E remove(int index) {
            checkIndex(index);// 检查下标
    
            modCount++;// 移除一个元素,modCount加一;
            @SuppressWarnings("unchecked")
            E removedElement = (E) elements[index];
    
            int movedNum = size - 1 - index;// 要移动的元素个数
            if (movedNum > 0)
                System.arraycopy(elements, index + 1, elements, index, movedNum);
    
       //删除元素成功后,最后一个元素赋空,垃圾回收,集合元素个数减1.
            elements[--size] = null;
            return removedElement;
        }
    
        @SuppressWarnings("unchecked")
        public E get(int index) {
            checkIndex(index);// 检查下标
            return (E) elements[index];
        }
    
        public boolean remove(E e) {
            int index = -1;
            //如果有一个元素和准备移除的对象相等,则将此元素的索引赋为index,并退出循环
            for (int i = 0; i < size; i++) {
                @SuppressWarnings("unchecked")
                E curElement = (E) elements[i];
                if ((curElement == e) || (curElement != null && curElement.equals(e))) {
                    index = i;
                    break;
                }
            }
            //index为-1,表明没有要移除的这个元素
            if (index == -1) {
                return false;
            } else {
                remove(index);
                return true;
            }
        }
    
        private void checkIndex(int index) {
            if (index < 0 || index >= size) {
                throw new IndexOutOfBoundsException(indexOutOfBoundsTip(index));
            }
    
        }
    
        private String indexOutOfBoundsTip(int index) {
            return "Index [" + index + "] , Size [" + size + "]";
        }
    
    
    
        @Override
        public Iterator<E> iterator() {
    
            return new SimpleIterator();
        }
    
        /**
         * 迭代器内部类
         * @author Administrator
         *
         */
        private class SimpleIterator implements Iterator<E> {
            private int desireModCount = modCount;// 预期的的修改次数
            private int nextIndex = 0;// 预期情况下的下个元素的索引
            private int lastIndex = -1;// 实际情况下的下个元素索引
    
            @Override
            public boolean hasNext() {
                if (nextIndex < size) {
                    return true;
                }
                return false;
            }
    
            @SuppressWarnings("unchecked")
            @Override
            public E next() {
                if (nextIndex >= size) {
                    throw new NoSuchElementException("迭代到尾了,没有其它元素了");
                }
                checkModCount();
                lastIndex = nextIndex;// 预期的索引和实际的索引一致
                nextIndex++;// 预期的下个索引自加1
                return (E) elements[lastIndex];
            }
    
            @Override
            public void remove() {
                /*
                 * 在迭代过程中连续多次移除元素,将触发这个异常
                 *  eg:下而的代码将抛出异常
                 *   iterator.remove(); 
                 *   iterator.remove();
                 */
                if (lastIndex == -1) {
                    throw new IllegalStateException("在迭代过程中不能多次移除同一个元素");
                }
                checkModCount();
                DynamicArray.this.remove(lastIndex); // 调用外围类的元素移除方法
                /*
                 * DynamicArray.remove(index )方法会改变成员变量的modCount的值
                 * 重新赋值给SimpleIterator的成员变量desireModCount,
                 *  使desireModCount和 modCount再次相等
                 */
                desireModCount = modCount;
                /*
                 * 当前元素被移除后,后一个元素将顶替当前元素的位置 
                 * 所以下次遍历的索引下标还是当前迭代位置
                 */
                nextIndex = lastIndex;
                /*
                 * 每次移除元素都将lastIndex的值重新赋为-1. 
                 * 可用来检测是否在迭代器中连续执行remove()的非法操作
                 */
                lastIndex = -1;
            }
    
            /*
             * 校验在迭代过程中,是否使用了非迭代的方式改变了当前动态数组的结构(添加或删除)
             */
            private void checkModCount() {
                if (desireModCount != modCount)
                    throw new IllegalStateException("迭代过程中DynamicArray对象的结构被改变了");
            }
    
        }
        
        public static void main(String[] args) {
            DynamicArray<String> dyArray = new DynamicArray<String>();
            dyArray.add("34");
            dyArray.add("45");
            dyArray.add("啥子");
            dyArray.add("哈");
            dyArray.add("he");
            Iterator<String> itor = dyArray.iterator();
            int i = 0;
            while (itor.hasNext()) {
                if (i == 2) {
                    itor.remove();
                }
                i++;
                System.out.println(itor.next());
    
            }
            System.out.println(itor.next());
    
    
        }
    
    }

    参考:《Java编程的逻辑》马俊昌

  • 相关阅读:
    我的博客的定制代码
    在网站中使用Bing Translator插件翻译文章。
    java 爬虫 WebMagic(四)-Scheduler
    java 爬虫 WebMagic(三)-PipeLine
    java 爬虫 WebMagic(二)-PageProcessor
    java 爬虫 WebMagic(一)-Spider
    java 解析json 万能型
    c# 解析Json 万能型
    常用的Linux 命令
    将文件夹和文件提交到git
  • 原文地址:https://www.cnblogs.com/gocode/p/write-one-customize_dynamic_array-by-refer-jdk-source.html
Copyright © 2011-2022 走看看