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