目录
1.线性表
- 什么是数据结构?
- 线型表示具有n个相同类型元素的有限序列(n>=0)
- 生活之中的线型表
2.接口涉及
- 第一种线型表:数组
这里的array是存放在相应的栈空间之中,new出来的数据是存放在相应的堆空间之中,并且new出来的内存地址是连续的。数组存在一个严重缺点,无法进行动态的申请相应的容量(改变这个方式,可以进行自己写一个动态数据)。
- 动态数组的接口设计
- 初始代码块的设计,这里是设计的相应的ArrayList。
public class ArrayList { /** * 清除所有元素 */ public void clear() { } /** * 元素的数量 * @return */ public int size() { return 0; } /** * 是否为空 * @return */ public boolean isEmpty() { return false; } /** * 是否包含某个元素 * @param element * @return */ public boolean contains(int element) { return false; } /** * 添加元素到尾部 * @param element */ public void add(int element) { } /** * 获取index位置的元素 * @param index * @return */ public int get(int index) { return 0; } /** * 设置index位置的元素 * @param index * @param element * @return 原来的元素ֵ */ public int set(int index,int element) { return 0; } /** * 在index位置插入一个元素 * @param index * @param element */ public void add(int index, int element) { } /** * 删除index位置的元素 * @param index * @return */ public int remove(int index) { return 0; } /** * 查看元素的索引 * @param element * @return */ public int indexOf(int element) { return -1; } }
3.简单接口的实现
- 动态数组的设计
public class ArrayList {
private int size;//元素数量
private int[] elements;//存放数组
private static final int DEFAULT_CAPACITY = 10;//默认是相应的为10的存储空间
private static final int ELEMENT_NOT_FOUND = -1;//这里是进行一个return的声明
//提供容量的一个相应的数组
public ArrayList(int capacity)
{
capacity = (capacity < DEFAULT_CAPACITY)? DEFAULT_CAPACITY:capacity;
elements = new int[capacity];
}
/*
//不提供容量的一个数组
public ArrayList()
{
elements = new int[DEFAULT_CAPACITY];//当然这里是不推荐进行这样写入的,一般是进行外部声明,这里是使用的DEFAULT_CAPACITY
}
*/
//上面的相应的构造函数还可以进行下面的写法
public ArrayList()
{
this(DEFAULT_CAPACITY);//直接是使用相应的无参构造函数调用相应的有参构造函数
}
/**
* 清除所有元素
*/
public void clear() {
}
/**
* 元素的数量
* @return
*/
public int size() {
return 0;
}
/**
* 是否为空
* @return
*/
public boolean isEmpty() {
return size == 0;
}
/**
* 是否包含某个元素
* @param element
* @return
*/
public boolean contains(int element) {
return indexOf(element) != ELEMENT_NOT_FOUND;
}
/**
* 添加元素到尾部
* @param element
*/
public void add(int element) {
}
/**
* 获取index位置的元素
* @param index
* @return
*/
public int get(int index) {
if(index < 0 || index >= size)
{
throw new IndexOutOfBoundsException("Index:" + index + ",Size:" + size);//这里是直接使用抛出异常的方式进行相应的处理
//抛出异常的情况就是相应的颜色编程红色
/* 抛出的异常是这样的情况,直接点击相应的位置就是可以得到相应的错误的位置
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index:-10,Size:0
at ArrayList.get(ArrayList.java:74)
at Main.main(Main.java:11)
*/
}
return elements[index];
}
/**
* 设置index位置的元素
* @param index
* @param element
* @return 原来的元素ֵ
*/
public int set(int index,int element) {
if(index < 0 || index >= size)
{
throw new IndexOutOfBoundsException("Index:" + index + ",Size:" + size);//这里是直接使用抛出异常的方式进行相应的处理
//抛出异常的情况就是相应的颜色编程红色
/* 抛出的异常是这样的情况,直接点击相应的位置就是可以得到相应的错误的位置
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index:-10,Size:0
at ArrayList.get(ArrayList.java:74)
at Main.main(Main.java:11)
*/
}
int old = elements[index];
elements[index] = element;
return old;
}
/**
* 在index位置插入一个元素
* @param index
* @param element
*/
public void add(int index, int element) {
}
/**
* 删除index位置的元素
* @param index
* @return
*/
public int remove(int index) {
return 0;
}
/**
* 查看元素的索引
* @param element
* @return
*/
public int indexOf(int element) {
//这个含义就是进行相应的遍历的过程
for(int i= 0; i<size;i++)
{
if(elements[i] == element)
return i;
}
return ELEMENT_NOT_FOUND;
}
}
4.clear
public void clear() {
size = 0;//只要控制相应的size的大小就是可以控制相应的异常的抛出
/*
不能够说它是进行内存的浪费,因为后面还是可能会用,不用销毁相应的内存过程,就是可以不用使用相应的清除掉内存的过程.不用进行清除就是可以不用浪费相应的内存.
*/
}
-
斐波那契数的另一种写法
public static int fib3(int n) { if(n <= 1) return n; int first = 0; int second = 1; for(int i= 0;i < n-1; i++)//或者这里的条件是进行相应的while(n-- > 1),这里的含义就是进行相应的n-1的一个执行过程 { second += first; first = second - first; } return second; }
- 斐波那契数的另一种写法
- 一个数组new出来之后,一般而言,new出来一个对象就是进行相对空间进行一个申请空间的过程。一般而言,在后面我们是要进行一个delete的一个过程,但是在相应的java之中,是不需要这个过程的,直接存在一个垃圾回收的过程。
5.add
/**
* 添加元素到尾部
* @param element
* 一开始的时候相当于size是,依次进行添加的过程
*/
public void add(int element) {
elements[size++] = element;
/*
一开始初始化是进行相应的数组之中的元素是10个,后面才是进行相应的扩容的过程
*/
}
6.打印
- 这里System.out.println()方法是直接进行调用相应的toString的方式进行打印,我们希望将立面的元素进行打印出来,这里的调用方式是如下所示:
@Override
public String toString() {
//size = 3,[99,88,77]
StringBuilder string = new StringBuilder();//这里的StringBuilder是需要转换成为相应的String格式
string.append("size=").append(size).append(",[");
for (int i = 0; i< size;i++ )
{
/* string.append(elements[i]);
if (i!=size-1)//判断是不是最后一个元素,如果要不是最后一个元素,才是可以进行相应的拼接过程使用。
{
string.append(",");
}
*/
if (i!=0)//选择这种方式的原因是他不进行相应的减法运算
{
string.append(",");
}
string.append(elements[i]);
}
string.append("]");
return string.toString();
}
- 流程
7.remove
- 删除操作进行的时候,需要注意最后一个元素的过程使用。
最后一个元素是不需要进行处理的,防止后面是需要进行使用的。并且上面的字节是连续的,因此,使用的过程之中只能是进行相应的元素挪动的过程。
比如我们这里是需要进行挪动的是index为3,那么相应的一个挪动范围是index+1 - size-1
- 具体的挪动的代码是如下所示:
/** * 删除index位置的元素 * @param index * @return */ public int remove(int index) { //首先是对于相应的index进行判断 if(index<0 || index>=size){ throw new IndexOutOfBoundsException("Index超出了范围"); } //记录原始的old元素是什么 int old = elements[index]; //进行相应的一个挪动过程使用 for(int i = index+1;i<size-1;i++) { elements[i - 1] = elements[i]; } size--; return old; }
8.add_index
- 注意挪动的时候,首先进行挪动的是最后面的先进行挪动,注意顺序问题。
- 这里的使用过程是如下所示:
/**
* 在index位置插入一个元素
* @param index
* @param element
*/
public void add(int index, int element) {
//首先是需要进行相应的index的判断.这里的索引的判断是需要进行相应的注意的,是与别的判断是不同的.
//这里的index可以是等于相应的size的大小的.
if(index < 0 || index > size)
{
throw new IndexOutOfBoundsException("Index:" + index + ",Size:" + size);//这里是直接使用抛出异常的方式进行相应的处理
//抛出异常的情况就是相应的颜色编程红色
/* 抛出的异常是这样的情况,直接点击相应的位置就是可以得到相应的错误的位置
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index:-10,Size:0
at ArrayList.get(ArrayList.java:74)
at Main.main(Main.java:11)
*/
}
//挪动的范围 从相应的index - size-1,一定要注意相应的顺序.
for(int i = size-1;i >= index;i--)
{
elements[i+1] = elements[i];
}
elements[index] = element;
size++;
}
-
接下来是进行相应的一个封装异常的过程使用,将上述异常出现的情况直接进行了相应的封装,免去了相应的处理的过程使用。
private void outOfBounds(int index) { throw new IndexOutOfBoundsException("Index:" + index + ",Size" + size); } private void rangeCheck(int index) { if(index < 0 || index >= size) { outOfBounds( index); } } private void rangeCheckForAdd(int index) { if(index < 0 || index > size) { outOfBounds( index); } }
-
将相应的add方法进行相应的修改
/** * 添加元素到尾部 * @param element * 一开始的时候相当于size是,依次进行添加的过程 */ public void add(int element) { // elements[size++] = element; /* 一开始初始化是进行相应的数组之中的元素是10个,后面才是进行相应的扩容的过程 */ add(size,element); }
9.接口测试
- 在开发之中,我们会遇到各种的测试,一般是采用打印的方式。另一种方式是进行抛出异常的方式进行。
- 使用声明一个Asserts工具方式,相应的代码段是如下所示:
public class Asserts { public static void test(boolean value) { try { if (!value) throw new Exception("测试未通过"); } catch (Exception e) { e.printStackTrace(); } } }
测试的主函数如下所示:
10.动态扩容
- 进行扩容的方式,如下所示:
这里是不可以new一个新的数组在后面接着使用,原因是因为new出来的内存空间是随机的吧,不能够保证内存是在它后面的,只能是新申请一个空间。原来的内存空间是没有使用的,需要将原来的内存空间自动进行垃圾回收。
- 创建一个新的方法,是进行扩容的方法.
/** * 保证容量 */ public void ensureCapacity(int capacity) { int oldCapacity = elements.length; if(oldCapacity >= capacity) return; int newCapacity = oldCapacity + (oldCapacity>>1); //这里的含义是原始的容量变成原来的1.5倍 int[] newElements = new int[newCapacity];//新声明一个数组 for(int i = 0 ;i<size;i++) { newElements[i] = elements[i]; } elements = newElements;//原来的elements指向新的进行刚刚创建的elements System.out.println(oldCapacity + "扩容为" + newCapacity); }
- 原始的函数之中需要进行改变.
11.泛型
- 我们在使用的过程之中,希望可以放入一些别的类型的.
这里我们应当注意的是,泛型只能是放入相应的一个对象类型的,不能够放入基本数据类型,比如就是说:如果要是放入相应的int类型就是需要进行更改为Integer(包装类).
- 在使用泛型编写的过程之中,发现出现了这个问题.如下所示
我们知道,所有类型的父类都是Object类型,但是Object类型是不可以进行继承自己的。因此,我们这里使用一个强制类型转换方式进行。如下所示: - 此刻的ArrayList是如下所示:
import com.sun.java.swing.plaf.windows.WindowsDesktopIconUI; import java.net.BindException; import java.util.Arrays; public class ArrayList<E> { /** * 元素的数量 */ private int size; /** * 所有的元素 */ private E[] elements; private static final int DEFAULT_CAPACITY = 10; private static final int ELEMENT_NOT_FOUND = -1; public ArrayList(int capaticy) { capaticy = (capaticy < DEFAULT_CAPACITY) ? DEFAULT_CAPACITY : capaticy; elements = (E[]) new java.lang.Object[capaticy]; } public ArrayList() { this(DEFAULT_CAPACITY); } /** * 清除所有元素 */ public void clear() { for (int i = 0; i < size; i++) { elements[i] = null; } size = 0; } /** * 元素的数量 * @return */ public int size() { return size; } /** * 是否为空 * @return */ public boolean isEmpty() { return size == 0; } /** * 是否包含某个元素 * @param element * @return */ public boolean contains(E element) { return indexOf(element) != ELEMENT_NOT_FOUND; } /** * 添加元素到尾部 * @param element */ public void add(E element) { add(size, element); } /** * 获取index位置的元素 * @param index * @return */ public E get(int index) { rangeCheck(index); return elements[index]; } /** * 设置index位置的元素 * @param index * @param element * @return 原来的元素ֵ */ public E set(int index, E element) { rangeCheck(index); E old = elements[index]; elements[index] = element; return old; } /** * 在index位置插入一个元素 * @param index * @param element */ public void add(int index, E element) { rangeCheckForAdd(index); ensureCapacity(size + 1); for (int i = size; i > index; i--) { elements[i] = elements[i - 1]; } elements[index] = element; size++; } /** * 删除index位置的元素 * @param index * @return */ public E remove(int index) { rangeCheck(index); E old = elements[index]; for (int i = index + 1; i < size; i++) { elements[i - 1] = elements[i]; } elements[--size] = null; return old; } /** * 查看元素的索引 * @param element * @return */ public int indexOf(E element) { if (element == null) { // 1 for (int i = 0; i < size; i++) { if (elements[i] == null) return i; } } else { for (int i = 0; i < size; i++) { if (element.equals(elements[i])) return i; // n } } return ELEMENT_NOT_FOUND; } // public int indexOf2(E element) { // for (int i = 0; i < size; i++) { // if (valEquals(element, elements[i])) return i; // 2n // } // return ELEMENT_NOT_FOUND; // } // // private boolean valEquals(Object v1, Object v2) { // return v1 == null ? v2 == null : v1.equals(v2); // } /** * 保证要有capacity的容量 * @param capacity */ private void ensureCapacity(int capacity) { int oldCapacity = elements.length; if (oldCapacity >= capacity) return; // 新容量为旧容量的1.5倍 int newCapacity = oldCapacity + (oldCapacity >> 1); E[] newElements = (E[]) new Object[newCapacity]; for (int i = 0; i < size; i++) { newElements[i] = elements[i]; } elements = newElements; System.out.println(oldCapacity + "扩容为" + newCapacity); } private void outOfBounds(int index) { throw new IndexOutOfBoundsException("Index:" + index + ", Size:" + size); } private void rangeCheck(int index) { if (index < 0 || index >= size) { outOfBounds(index); } } private void rangeCheckForAdd(int index) { if (index < 0 || index > size) { outOfBounds(index); } } @Override public String toString() { // size=3, [99, 88, 77] StringBuilder string = new StringBuilder(); string.append("size=").append(size).append(", ["); for (int i = 0; i < size; i++) { if (i != 0) { string.append(", "); } string.append(elements[i]); // if (i != size - 1) { // string.append(", "); // } } string.append("]"); return string.toString(); } }
12.对象数组
- 上面代码进行更改之后,出现一个内存管理.
为什么要放入地址,因为如果要是放入的是相应的值的话,会产生一系列的问题,对象的大小是不确定的,放入空间会由对象的大小决定,会产生一定的问题.
- 主函数的测试代码图如下所示:
13.clear_细节
- 对象的销毁过程,如上图所示,将地址位置变成null,就是进行一个断线的过程,数组的内存和对象的内存是不一样的.
- 这里的clear为什么要进行调整的过程,之前为什么不进行调整,以前的时候直接将size变成0,原来的内存是可能使用的.这里的意思就是clear使用过之后,后面还是会进行add的过程. 而 本处如果要是使用对于对象的地址值时,是将原来的地址值进行一个覆盖的过程,然后将原来的对象进行清空.拿下面的代码进行讲解:
- clear方法是如下所示:
/**
* 清除所有元素
*/
public void clear() {
/*
这样的含义就是数组之中都是null,但是数组内存是存在的,不能够进行销毁,
不能够使用element=null,相当于第一根线断了,不能够继续使用.
能循环利用的留下,不能够循环利用的滚蛋.
*/
for (int i = 0; i < size; i++) {
elements[i] = null;
}
size = 0;
}
- 如何证明最终的Person对象是挂了的???在Person代码之中添加如下所示,就是当地址指针不再指向Person对象的时候,调用如下的析构函数,说明对象挂了.
@Override protected void finalize() throws Throwable { super.finalize(); System.out.println("Person对象挂了"); }
我们发现在使用clear之后,并没有出现垃圾回收,因此,在clear之后可以使用一个提醒机制.相应的代码是如下所示:System.gc();//这里的含义就是对于垃圾回收之后,直接调用上面所谓的"对象挂了"的函数.
14.remove_细节
- 处理的过程中,将此处的地址值进行删除,后面的地址值依次向前移动.最后的地址值设置为null.
- remove对应的代码是如下所示:
/**
* 删除index位置的元素
* @param index
* @return
*/
public E remove(int index) {
rangeCheck(index);
E old = elements[index];
for (int i = index + 1; i < size; i++) {
elements[i - 1] = elements[i];
}
elements[--size] = null;
return old;
}
15.equals
- 在原始的Person之中添加重写equals方法,相应的代码是如下所示:
@Override//使用重写的方式进行equals方式的实现,一般对于对象是不进行地址的比较的,都是使用对象之中相应的属性进行比较,如果要是元素之中的两个元素是一样的就是可以判断是一致的. public boolean equals(Object o) { Person person = (Person)o; return this.age == person.age; }
如果要是不进行重写equals方法就是直接进行使用地址比较的方式.如果不是对象是Integer,相当于重写equals,内部直接进行比较相应的数值是否相等,并不是进行地址的比较.
-
IndexOf()方式代码是如下所示:
/** * 查看元素的索引 * @param element * @return */ public int indexOf(E element) { for (int i = 0; i < size; i++) { if (element.equals(elements[i])) return i; // n } return ELEMENT_NOT_FOUND; }
16.null值处理
- 如果null值是可以存的,add代码是不用进行改正的.如果要是不能够存取空null值,这里在add方法之中就是需要进行一个添加操作.
添加上面所示这一句就是可以的.
- 如果传入对象是null的,直接查询第一个null的位置.因此,Index函数是需要进行分开处理的.
/** * 查看元素的索引 * @param element * @return */ public int indexOf(E element) { if (element == null) { // 1 for (int i = 0; i < size; i++) { if (elements[i] == null) return i; } } else { for (int i = 0; i < size; i++) { if (element.equals(elements[i])) return i; // n } } return ELEMENT_NOT_FOUND; }
17.ArrayList源码分析
- 直接在IDEA页面上敲击java.util.ArrayList<E>点击进入源码之中进行查看.代码之中modCount是在迭代器之中才是有用的,在一般的过程之中是没有那么大的作用.
- == 是进行比较的是内存地址是否相等,一般我们是使用equals进行重写复用的方式进行.
- 重写的改写
@Override//使用重写的方式进行equals方式的实现,一般对于对象是不进行地址的比较的,都是使用对象之中相应的属性进行比较,如果要是元素之中的两个元素是一样的就是可以判断是一致的. public boolean equals(Object o) { if (o == null) return false; if (o instanceof Person) { Person person = (Person) o; return this.age == person.age; } return false;//这里是比较的两种类型是不同的. }