数据结构与算法--使用Java实现ArrayList

本篇博客所涉及到的代码,均已上传到github:
项目github链接
本篇博客涉及代码github链接

本篇博客要点如下:

数组简介

使用Java代码实现ArrayList

一. 数组简介

众所周知,ArrayList的底层是数组.
因此,在实现ArrayList之前,我们有必要对数组做一个基本的了解

数组概念

数组是用于储存多个相同类型数据的有限序列

从概念中,我们能够得到数组具有以下特性:
1.数据类型相同 : 这意味着内存分配时,数组里的每个元素占据相同的内存空间
2.有限 : 数组的元素个数有限
3.序列 : 顺序性,除了首尾元素,每个元素都有唯一的直接前驱和直接后继

数组存储结构

数组的存储可以用以下图片来直接描述:
在这里插入图片描述
从图片中,我们能够直观的看到,数组的元素在内存中连续,只存储数据,不存储地址.
实际上: 当我们执行new 操作创建数组时, 它存储在堆中. 同时还在栈内存中定义一个特殊的变量,这个变量的取值等于数组在堆内存中的首地址,栈中的这个变量就成了数组的引用变量,引用变量实际上保存的是数组在堆内存中的地址
那么这种存储结构具有什么优点和缺点呢?
优点:
1.根据索引查询性能快
假设我们要查询第i个索引的数据.
只需要使用数组的首地址 + i * 每个元素在内存中占用的空间,时间复杂度为T(n)=O(1)
2.节约存储空间
存储时只存储数据项,不存储地址
缺点:
1.插入删除成本高
在某个位置插入删除元素,需要修改该位置及之后全部数据的索引,
平均每次插入删除需要操作(1 + n) / 2次,时间复杂度T(n)=O(n)
2.按值查询效率低
数组的按值查询需要逐个遍历,时间复杂度T(n)=O(n)
3.数组没有存储满时,浪费存储空间
假设我们定义了100个元素的数组,在堆中会为这个数组开辟100个内存空间,
但如果我们只存储了10个数据,相当于剩下的90个空间会被浪费

二.使用Java代码实现ArrayList

基于上述的了解: 我们可以开始试着手写一个简单的ArrayList

首先明确, 集合的常用操作:
集合大小,非空判断,增加元素,删除元素,修改元素,获取元素
我们把上面列举出来的每一种操作封装为一个方法,因此可以得到一个接口,如下:

List接口
public interface List {
    // 返回线性表的大小,即数据元素的个数
    int size();
    // 返回线性表序号为i的数据元素
    Object get(int i);
    // 判断线性表是否包含数据元素e
    boolean contains(Object e);
    // 线性表为空返回true,否则返回false
    boolean isEmpty();
    // 返回数据元素e在线性表中的序号
    int indexOf(Object e);
    // 将数据元素e插入到线性表中的i号位置
    void add(int i, Object e);
    // 将元素e插入到线性表末尾
    void add(Object e);
    // 将元素e插入到元素obj之前
    boolean addBefore(Object obj, Object e);
    // 将元素e插到元素obj之后
    boolean addAfter(Object obj, Object e);
    // 删除线性表中序号为i的元素,并返回
    Object remove(int i);
    // 删除线性表中第一个与a相同的元素
    boolean remove(Object e);
    // 替换线性表中序号为i的数据元素为e,返回原数据元素
    Object replace(int i, Object e);
}
实现ArrayList

除了List接口中的方法,我们还需要两个成员变量来表示
ArrayList的底层存储结构和大小

 private Object[] elementData; //底层是一个数据,没有确定长度
 private int size; // 不是数组分配了几个空间,而是元素的个数
构造方法

我们常用的ArrayList构造方法有两种:
第一种初始化的时候指定大小
第二种初始化的时候不指定大小,默认的大小为10
基于此,我这里提供了如下的两个构造方法

  public ArrayList(int intialCapacity) {
        // 给数组分配指定数量的空间
        elementData = new Object[intialCapacity];
        // 指定顺序表的元素个数,默认为0
        // size = 0;
    }

    public ArrayList() {
        // 没有指定长度,默认长度为10
        this(10);
        // 没有指定长度,长度为0
        elementData = new Object[]{10};
    }
获取集合大小

成员变量size表示集合大小,返回它即可

  @Override
    public int size() {
        return size;
    }
获取集合元素

获取集合指定索引的元素

    @Override
    public Object get(int i) {
    	// 对输入参数进行判断(以免引起数组越界)
        if (i < 0 || i >= size) {
            throw new RuntimeException("数组索引越界异常: " + i);
        }
        return elementData[i];
    }

获取指定元素第一次出现的索引

 @Override
    public int indexOf(Object e) {
        for (int i = 0; i < size; i++) {
        	// 要首先判断输入参数为null的情况,否则可能会引起空指针异常
            if (e == null) {
                if (elementData[i] == null) {
                    return i;
                }
            } else {
                if (e.equals(elementData[i])) {
                    return i;
                }
            }
        }
        // 遍历结束后还没有找到,返回-1
        return -1;
    }
判断集合是否包含指定元素
   @Override
    public boolean contains(Object e) {
      // 这里我最初是采用遍历的方法进行判别的,后面想到,我在之前实现的indexOf方法
      // 试想,若集合包含某个元素,则该元素在集合里对应的索引应该大于等于0
      // 所以,我们在这里复用上面的indexOf方法,实现如下:
      return indexOf(e) >= 0;
    }
判断集合是否为空
@Override
    public boolean isEmpty() {
    	// 集合为空,代表集合大小为0
        return size == 0;
    }
集合数据添加

添加元素有两种情况,一种是添加到集合的最后,一种是添加到集合指定索引位置

// 先来看比较简单的插入到最后的情况
// 首先要判断集合是不是已经满了,如果满了的话,要对数组进行扩容,不然会出现数组越界问题
@Override
    public void add(Object e) {
        // 这里采用Arrays的数组拷贝方法来扩容, 让elementData指向扩容后的数组(我这里扩容二倍)
        if (size == elementData.length) {
            elementData = Arrays.copyOf(elementData, elementData.length * 2);
        }
        // 添加元素到最后
        elementData[size] = e;
        // 元素个数+1
        size ++;
    }
  // 添加到指定索引, 这里除了要给指定索引的元素赋值之外,还需要把这个元素之后的元素全部移位
   @Override
    public void add(int i, Object e) {
        if (size == elementData.length) {
            elementData = Arrays.copyOf(elementData, elementData.length * 2);
        }
        // 添加元素到最后
        elementData[size] = e;
        // 元素个数+1
        size ++;

        // 后移i后面元素,从最后一个元素开始
        for (int j = size; j > i; j--) {
            elementData[j] = elementData[j - 1];
        }
        // 给数组的第i个位置赋值
        elementData[i] = e;
    }
集合数据删除
	/*
		删除集合对应索引的元素,返回旧值
	*/
	@Override
    public Object remove(int i) {
        // 索引取值应该合理,否则会引起数组越界
        if (i < 0 || i >= size) {
            throw new IndexOutOfBoundsException("指定的索引越界,集合大小为:" + size + ",您指定的索引大小为:" + i);
        }
        
       Object oldValue = elementData[i];

        int numMoved = size - i - 1;
        if (numMoved > 0)
            System.arraycopy(elementData, i+1, elementData, i,
                    numMoved);
        elementData[--size] = null; // 释放空间

        return oldValue;
    }
   /*
        删除集合里指定的元素
     */
    @Override
    public boolean remove(Object e) {
        // 遍历集合,找寻元素
       for (int i = 0; i < size; i++) {
       	   // 首先仍要考虑空值的情况,防止空指针异常
           if (e == null) {
               if (elementData[i] == null) {
                   remove(i); // 调用上面写的方法删除集合元素
                   return true;
               }
           } else {
               if (e.equals(elementData[i])) {
                   remove(i);
                   return true;
               }
           }
       }
       return false;
    }
集合数据替换
    /*
        将集合指定索引的元素替换为特定的值,并返回原数据元素
    */
    @Override
    public Object replace(int i, Object e) {
        if (i < 0 || i >= size) {
            throw new ArrayIndexOutOfBoundsException("索引越界!");
        }
        Object oldValue = elementData[i];
        elementData[i] = e;
        return oldValue ;
    }

如上,我们通过自己的方式实现了ArrayList的基本功能!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值