模拟实现顺序表ArrayList2(三级)

package com.learn.datastructure;

/**
 * 线性接口表
 * 我怎么觉得这些方法我们都学过,
 * 是不是都学过,大同小异,
 * 注意这是一个接口,和存储结构无关
 * 无论是顺序表还是链表,都要把这些功能给我实现,
 * 首先我们来讲顺序表啦,
 * @author Leon.Sun
 *
 */
public interface List {
	
	// 返回线性表的大小,即数据元素的个数。
	/**
	 * 线性表里又几个元素
	 * @return
	 */
	public int size();
	
	// 返回线性表中序号为 i 的数据元素
	/**
	 * 获取第i个元素
	 * @param i
	 * @return
	 */
	public Object get(int i);

	// 如果线性表为空返回 true,否则返回 false。
	/**
	 * 线性表是不是空的,
	 * @return
	 */
	public boolean isEmpty();

	// 判断线性表是否包含数据元素 e
	/**
	 * 线性表是不是包括某个元素
	 * 是不是查找
	 * @param e
	 * @return
	 */
	public boolean contains(Object e);

	// 返回数据元素 e 在线性表中的序号
	/**
	 * 某个元素在线性表的索引
	 * @param e
	 * @return
	 */
	public int indexOf(Object e);

	// 将数据元素 e 插入到线性表中 i 号位置
	/**
	 * 添加
	 * 这是加到指定位置,线性表的插入操作
	 * @param i
	 * @param e
	 */
	public void add(int i, Object e);

	// 将数据元素 e 插入到线性表末尾
	/**
	 * 这两个添加有什么区别,
	 * 这是加到最后,又插入就有添加
	 * @param e
	 */
	public void add(Object e);

	// 将数据元素 e 插入到元素 obj 之前
	/**
	 * 在谁谁之前加
	 * @param obj
	 * @param e
	 * @return
	 */
	public boolean addBefore(Object obj, Object e);

	// 将数据元素 e 插入到元素 obj 之后
	/**
	 * 在谁谁之后加
	 * 这个大家自己都可以来写
	 * @param obj
	 * @param e
	 * @return
	 */
	public boolean addAfter(Object obj, Object e);

	// 删除线性表中序号为 i 的元素,并返回之
	/**
	 * 删除第几个,这是删除第几个元素
	 * 比如我删除第5个元素
	 * @param i
	 * @return
	 */
	public Object remove(int i);

	// 删除线性表中第一个与 e 相同的元素
	/**
	 * 删除指定值的元素
	 * 比如我删除值是30的元素
	 * @param e
	 * @return
	 */
	public boolean remove(Object e);

	// 替换线性表中序号为 i 的数据元素为 e,返回原数据元素
	/**
	 * 修改,把第几个元素改成新的值
	 * @param i
	 * @param e
	 * @return
	 */
	public Object replace(int i, Object e);

}
package com.learn.datastructure;

import java.util.Arrays;

/**
 * 现在这个代码有没有缺点,有
 * 
 * 这里头有两个属性,一个是elementData,一个是size
 * 
 * 我们只写了添加,那删除的时候呢,那更新的时候呢,他不用扩容,
 * 他只要后移前移,删除的话前移就可以了,不要扩容,
 * @author Leon.Sun
 *
 */
public class ArrayList implements List {
	
	private Object[] elementData;
	
	private int size;
	
	/**
	 * 构造方法,默认长度是4,JAVA的源码默认是0
	 */
	public ArrayList() {
		this(4);		
		// elementData = new Object[] {};
		
	}
	
	/**
	 * 可以指定默认长度
	 * @param initialCapacity
	 */
	public ArrayList(int initialCapacity) {
		/**
		 * 这一个给他赋值了,给他分配了4个空间,
		 */
		elementData = new Object[initialCapacity];
		
		// size = 0;
	}

	/**
	 * 获取元素个数都非常的简单
	 */
	@Override
	public int size() {
		return size;
	}

	/**
	 * 我们也简单的模拟了获取第i个 元素
	 * 当然要考虑完整的话,就考虑到越界了
	 * 我们还自定义了一个异常
	 */
	@Override
	public Object get(int i) {
//		if(i<0 || i>=size) {
//			throw new RuntimeException("数组索引越界异常:" + i);
//		}
		
		if(i<0 || i>=size) {
			throw new MyArrayIndexOutOfBoundsException("数组索引越界异常:" + i);
		}
		
		return elementData[i];
	}

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

	@Override
	public boolean contains(Object e) {
		return false;
	}

	@Override
	public int indexOf(Object e) {
		return 0;
	}

	/**
	 * 数组满了要扩容这段代码还是要有,这是我们讲的数组一个扩容的原理
	 */
	@Override
	public void add(int i, Object e) {
		
		/**
		 * 要是想更严谨一些,i加到最后就是size,
		 * 判断一下,i的位置要正确,
		 * 如果i小于0,或者大于size
		 */
		if(i<0 || i>size) {
			/**
			 * 把我们自定义的数组再写一下
			 * 索引就是i
			 */
			throw new MyArrayIndexOutOfBoundsException("数组指针越界异常!" + i);
		}
		
		
		/**
		 * 首先数组满了就要先扩容,扩容之后才考虑添加的问题,
		 */
		if(size==elementData.length) {
			grow();
		}
		
		/**
		 * 假如在678这个位置要添加一个666,首先把678后面的元素都往后移一个位置
		 * 然后空出678这个位置,把666放进去,移动的首先从最后一个位置往后移动
		 * 不能直接移动678,因为直接移动是把678后面一个元素直接覆盖了,不可以的,
		 * 后移元素,从最后一个元素开始,后面i后面的元素,并从最后一个元素开始
		 * 
		 */		
		for(int j=size;j>i;j--) {
			elementData[j] = elementData[j-1];
		}
		
		/**
		 * 给数组的第i个位置赋值
		 */
		elementData[i] = e;
		
		size++;
	}

	/**
	 * 做这个操作是有前提的
	 * 
	 * 这个add只是往最后加的
	 */
	@Override
	public void add(Object e) {
		
		/**
		 * 这个添加到最后是添加到指定位置的特殊情况,
		 * 加到最后就是加到size,
		 */
		this.add(size, e);
		
		/**
		 * 如果数组满了,就扩容就可以了
		 */
//		if(size==elementData.length) {
//			/**
//			 * 新创建一个新的数组,长度是旧数组的2倍,
//			 * 写elementData.length这个更容易理解
//			 */
//			Object[] newArr = new Object[elementData.length*2];
//			/**
//			 * 将旧数组的数据拷贝到新数组,
//			 */
//			for(int i=0;i<size;i++) {
//				newArr[i] = elementData[i];
//			}
//			/**
//			 * 这个时候elementData还是指向原来的数组,这样添加元素还是往旧数组里面添加,
//			 * 这个时候堆里面的对象不能再指向旧数组地址0X2012,应该写0X3012了,
//			 * 这样就指向了新数组0X3012了,这个时候就会往新数组的后面添加了,这个时候就是把栈里的newArr变量
//			 * 指向堆里的地址,这个就是一个赋值语句,让elementData指向新数组,
//			 * newArr的赋给elementData
//			 * 
//			 * 我们这里一共写了三条语句,实际上这一条语句用一条语句就可以搞定了,
//			 */
//			elementData = newArr;
			
			/**
			 * 旧数组是谁,旧数组是elementData
			 * 新数组长度是多少newLength,然后在赋值给elementData
			 * 把旧的数组扩容到原来的2倍,让旧的引用指向它,扩容并复制,然后再指向,只要这一条语句就够了
			 * 我们是扩了一倍,我们看一下真正的ArrayList是扩了多少呢,
			 * JDK里面的int newCapacity = oldCapacity + (oldCapacity >> 1);
			 * JDK是每次增长50%,java.util.ArrayList每次增长50%,
			 * 增长50%是比较合适的,增加1倍可能会导致浪费,
			 */
			// elementData = Arrays.copyOf(elementData, elementData.length*2);
			
			/**
			 * 越来越像真正的ArrayList
			 */
//			grow();
//		}
		
		/**
		 * 我们每次都输出一下数组的长度,我要看一下这个length等于几
		 * 每次添加都会输出
		 */
//		System.out.println("length="+elementData.length);
		
		
//		elementData[size] = e;
//		
//		size++;
		
		// elementData[size++] = e;
	}
	
	/**
	 * JDK里面的是叫grow
	 */
	private void grow() {
		elementData = Arrays.copyOf(elementData, elementData.length*2);
	}

	@Override
	public boolean addBefore(Object obj, Object e) {
		return false;
	}

	@Override
	public boolean addAfter(Object obj, Object e) {
		return false;
	}

	@Override
	public Object remove(int i) {
		return null;
	}

	@Override
	public boolean remove(Object e) {
		return false;
	}

	@Override
	public Object replace(int i, Object e) {
		return null;
	}
	
	/**
	 * 我们要产生[123,321,456,678,....]这个结构
	 * 
	 */
	@Override
	public String toString() {
		/**
		 * 如果长度等于0,你还那么费劲干嘛
		 * 如果size等于0,一个元素也没有
		 */
		if(size==0) {
			return "[]";
		}
		
		/**
		 * 创建一个StringBuilder,因为它便于追加
		 * 这个应该有一个初始值"["
		 */
		StringBuilder builder = new StringBuilder("[");
		
		/**
		 * 开始循环了
		 */
		for(int i=0;i<size;i++) {
			builder.append(elementData[i]);
			if(i!=size-1) {
				builder.append(",");
			}
		}
		
		/**
		 * 他应该有个结尾的值
		 */
		builder.append("]");
		
		return builder.toString();
	}
	
}
package com.learn.datastructure;

/**
 * 他们在内存里面是怎么实现的,首先画一个栈内存,然后要画一个堆内存
 * 
 * @author Leon.Sun
 *
 */
public class TestArrayList {

	public static void main(String[] args) {
		
		// java.util.ArrayList list;
		
		/**
		 * 当我们执行这行的时候,我们new ArrayList,
		 * 在堆里面创建了一个对象,这里面有两个属性,
		 * 第一个属性是elementData,第二个属性是size,
		 * elementData这个初始值是null,size这个初始值是0,
		 * 我们这个对象在堆里的地址是0X1012,当我们执行这个无参构造方法的时候是空的
		 * 里面调用了this(4),在这里面分配了连续的空间,4个空间,这个地址叫0X2012,
		 * 我们熟练以后就不用写这个地址了,你看他就这么指向了,结果这个数组就不为空,
		 * 就指向了这个数组,然后我们在栈里开辟一个空间,main方法在栈里面,里面定义一个变量,
		 * 这个变量叫list,他的值是0X1012,栈里面的list变量指向堆里面的0X1012,
		 * 堆里面创建了一个对象指向了堆里面的一个数组,当然目前这个数组里面都是有值的,
		 * 初始值是null,因为是Object,对象都是null,我加123,456,678,什么意思,
		 * 我们看一下add里面的代码,size是0,先把123加到这儿,把123压到数组0这个位置
		 * size++这样size就变成1了,当我们加789的时候行不行,不行了,为什么这个时候不行了,
		 * 因为这个数组已经满了,下面就要扩容了,扩容的条件是什么,数组满了,你怎么知道数组满了,
		 * 就是size等于elementData.length,这就满了,下面一个如何扩容,扩容的策略,
		 * 4个不够了,下次变成6个,还是8个,还是100个,总得有一个特点吧,比如增长一倍,那就是4变成8,
		 * 还有增长一半,就是4个变6个了,扩容的步骤,不可以在原来的数组后面直接加2个,第一步,我们是调用的add方法
		 * 我们在栈里面分配一个空间,这是add方法,第一步不是四个吗,再创建一个新的数组,这个长度我们就写成8呗,
		 * 给新数组的地址X3012,在栈里面定义一个新的变量newArr,给一个地址0X3012,扩容的第二步复制原来数组
		 * 的内容到新数组,后边还剩下4个,是不是有空间了吗
		 * 
		 * 刚才我们添加第5个就报错,现在我们看一下,没报错,长度变成5了,
		 */
		List list = new ArrayList();
		
		 list.add(123);
		 list.add(321);
		 list.add(456);
		 list.add(678);
		 /**
		  * 当我们的元素添加超过4个的时候,马上就报错了
		  * 这个是数组要动态的扩容,这个扩容我们已经看的很清楚了
		  */
		 list.add(789);
		 list.add(111);
		 list.add(222);
		 list.add(111);
		 /**
		  * 超过8个的时候又会扩容,
		  */
		 list.add(222);
		 
		 /**
		  * 我在678这个位置要新加一个数
		  * 
		  * 我们加到20,我们没有20个元素
		  * 就会报我们自定义的数组异常错误
		  * 核心就是数组的一个扩容
		  * 包括元素的前移和后移
		  * 我们扩展了自定义异常,还有一些方法的提取
		  * 然后结合了ArrayList真正的源码来写的
		  * 对ArrayList底层的原理有一个掌握了
		  */
		 list.add(20, 666);
		
		System.out.println(list.size());
		
		System.out.println(list.isEmpty());
		
		System.out.println(list.get(2));
		
		System.out.println(list.get(0));
		
		/**
		 * 这个8我们先改成3
		 */
		System.out.println(list.get(3));
		
		/**
		 * 输出一下list,因为你直接调用list相当于直接调用toString
		 * 我想把所有的元素都输出出来,按照顺序,没有重写它的toString方法
		 * 
		 */
		System.out.println(list);
		
	}	
	
}
package com.learn.datastructure;

/**
 * 这个叫自定义异常,他要继承RuntimeException
 * 这里面也非常的简单,只要实现两个构造方法,
 * 我们要一个无参的,和一个带有异常信息的
 * @author Leon.Sun
 *
 */
public class MyArrayIndexOutOfBoundsException extends RuntimeException {

	public MyArrayIndexOutOfBoundsException() {
		super();
	}

	public MyArrayIndexOutOfBoundsException(String message) {
		super(message);
	}

}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值