List ADT实现之增长数组实现(ArrayList)

List ADT有两种流行的实现方式,Java中ArrayList类提供了可增长数组的实现,LinkedList提供了双链表实现。

使用ArrayList的有点在于,对get和set操作花费常数时间。其缺点是新元素的插入和元素的删除付出的代价非常昂贵,除非变动实在末端进行。

为说明增长数组实现的一些思想,本篇文章将编写ArrayList的简单实现。为避免与类库中的ArrayList类相混淆,这里将把实现的类叫做MyArrayList,即MyArrayList是独立的。

MyArrayList的一些细节:

  1. 维护数组的元素(elementData),数组的容量(elementData.length),数组当前的存储的元素个数(size)。
  2. 提供一种机制来动态改变数组的容量。通过建立新的数组,将老数组的元素拷贝到新数组中,然后允许虚拟机回收老数组。
  3. 提供set和get操作的实现。
  4. 提供基本操作,例如:size(),isEmpty(),和Clear()。提供remove(...)操作和add(...)操作 。
  5. MyArrayList实现Iterable接口,并提供了一个实现Iterator接口的实现类MyListIterator,该类实现了hasNext(),next()和remove()操作。最后通过实现的iterator放回一个Iterator实例。

代码####

public class MyArrayList<E> implements Iterable<E> {

    private static final int DEFULT_CAPASITY = 10;

    private int size; // 维护数组当前元素的个数
    private E[] elementData; // 存储当前List的元素

    // 构造器
    public MyArrayList() {
        doClear();
    }

    // 清空List
    public void clear() {
        doClear();
    }

    // 清空List操作,设置size为0,elementData容量为默认值10
    private void doClear() {
        size = 0;
        ensureCapacity(DEFULT_CAPASITY);
    }

    // 获取size
    public int size() {
        return size;
    }

    // 判断是否为空
    public boolean isEmpty() {
        return size == 0;
    }

    // 为了避免浪费空间,设置容量为size
    public void trimToSize() {
        ensureCapacity(size);
    }

    // 通过索引获取元素
    public E get(int index) {
        CheckRange(index);
        return elementData[index];
    }

    // 修改某个位子的元素
    public E set(int index, E element) {
        CheckRange(index);
        E old = elementData[index];
        elementData[index] = element;
        return old;
    }

    // 保证对List的操作时有足够的空间。比如add操作,如果数组的容量和size相同,则没有位子可以添加元素。
    // 传入一个新的容量minCapacity,表示需要的最小容量。
    // 如果size>=newCapacity,则说明容量足够,不作处理。
    // 如果size<newCapacity,则将elementData扩容至minCapacity。
    @SuppressWarnings("unchecked")
    public void ensureCapacity(int minCapacity) {
        if (size >= minCapacity) {
            return;
        }

        E[] old = elementData; // 将elementData拷贝到old
        elementData = (E[]) new Object[minCapacity]; // 把elementData扩容至minCapacity

        for (int i = 0; i < size; i++) {
            elementData[i] = old[i]; // 还原elementData原来的元素
        }

    }

    // 在List的末尾添加元素,实际调用了add(int index, E element)方法。
    public void add(E element) {
        add(size, element);
    }

    // 在某个位子添加元素。
    public void add(int index, E element) {
        CheckRangeForAdd(index); // 数组越界检查
        // 如果数组的容量等于size,即数组被填满,则需要扩容
        if (elementData.length == size) {
            ensureCapacity(size + 1); // 扩容一个元素的空间
        }
        for (int i = size; i > index; i--) {
            elementData[i] = elementData[i - 1]; // index及其后面的元素后移
        }
        elementData[index] = element;
        size++;
    }

    // 删除某位子的元素
    public void remove(int index) {
        CheckRange(index); // 数组越界检查
        for (int i = index; i < size - 1; i++) {
            elementData[i] = elementData[i + 1]; // 索引大于index的元素迁移
        }
        size--;
    }

    // 越界检查,智能访问0~size-1的范围
    private void CheckRange(int index) {
        if (index < 0 || index >= size) {
            throw new IndexOutOfBoundsException();
        }
    }

    // 针对add操作的数组越界检查,因为可以在List末尾添加元素,所以其访问范围为0~size
    private void CheckRangeForAdd(int index) {
        if (index < 0 || index > size) {
            throw new IndexOutOfBoundsException();
        }
    }

    // iterator方法,返回一个MyIterator实例
    public Iterator<E> iterator() {
        return new MyIterator();
    }

    // MyArrayList的内部类,实现了Iterator接口,用来实现遍历List的操作
    private class MyIterator implements Iterator<E> {
        // 遍历数组,默认从0开始
        private int currentIndex = 0;

        // 如果没有超过size,则说明还有下一元素
        public boolean hasNext() {
            return currentIndex < size;
        }

        // 每一次调用都返当前元素,比如第一调用next()返回第一元素,第二次调用则返回第二个元素
        public E next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            return elementData[currentIndex++];
        }

        // 删除当前元素。实际上是调用了MyAarrayList的remove方法。
        @SuppressWarnings("unused")
        public void remove() {
            MyArrayList.this.remove(--currentIndex);
        }

    }
}

测试代码####

public static void main(String[] args) {
        MyArrayList<String> myArrayList = new MyArrayList<String>();
        Iterator<String> sIterator = myArrayList.iterator();

        // 在末尾添加元素
        System.out.println("添加元素:b,c,d");
        myArrayList.add("b");
        myArrayList.add("c");
        myArrayList.add("d");

        // 遍历数组元素
        System.out.print("iterator遍历List:");
        while (sIterator.hasNext()) {
            System.out.print(sIterator.next());
        }

        System.out.println();

        // size()
        System.out.println();
        System.out.println("此时List中元素的个数为:" + myArrayList.size());

        System.out.println();

        // 在指定位置添加元素,并输出结果
        System.out.println("在List首部添加元素:a");
        myArrayList.add(0, "a");

        System.out.print("iterator遍历List:");
        sIterator = myArrayList.iterator();
        while (sIterator.hasNext()) {
            System.out.print(sIterator.next());
        }

        System.out.println();

        // 删除指定位子元素(删除d)
        System.out.println();
        System.out.println("删除index为3的元素(d)");
        myArrayList.remove(3);
        System.out.print("iterator遍历List:");
        sIterator = myArrayList.iterator();
        while (sIterator.hasNext()) {
            System.out.print(sIterator.next());
        }

        System.out.println();
        System.out.println();

        // set()和get()
        System.out.println("修改List的第一个元素为z.");
        myArrayList.set(0, "z");
        System.out.println("获取第一个元素" + myArrayList.get(0));

        System.out.println();

        // iterator.remove()
        System.out.println("测试iterator的remove方法:遍历List,每次遍历删除当前元素");
        Iterator<String> rIterator = myArrayList.iterator();
        while (rIterator.hasNext()) {
            if (rIterator.next() != null) {
                rIterator.remove();
            }
        }
        System.out.println("删除后的List长度为" + myArrayList.size());
    }

测试结果####

测试结果

本篇只给出了ArrayList的一些基本操作,如果大家想更深入的了解,可以去查看ArrayList的源码,下面是一篇ArrayList源码分析很好的文章:
http://blog.csdn.net/jzhf2012/article/details/8540410

大家可以思考两个问题:

  1. 为什么MyArrayList有了remove方法,为什么iterator中还要实现remove方法。
  2. 为什么MyIterator要作为MyArrayList的内部类,还有其他实现方式吗?
    如果有答案,欢迎大家在评论区讨论。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,165评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,503评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,295评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,589评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,439评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,342评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,749评论 3 387
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,397评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,700评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,740评论 2 313
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,523评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,364评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,755评论 3 300
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,024评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,297评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,721评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,918评论 2 336

推荐阅读更多精彩内容