前提概念:
1.泛型:
【允许在编写代码时定义一些可变部分】
“泛型是程序设计语言的一种特性。
允许程序员在强类型程序设计语言中编写代码时定义一些可变部分,那些部分在使用前必须作出指明。各种程序设计语言和其编译器、运行环境对泛型的支持均不一样。将类型参数化以达到代码复用提高软件开发工作效率的一种数据类型。泛型类是引用类型,是堆对象,主要是引入了类型参数这个概念。”
2.迭代器
概念: 迭代器(iterator)是一种对象,它能够用来遍历标准模板库容器中的部分或全部元素,每个迭代器对象代表容器中的确定的地址。
注意事项: 每一种容器型都必须提供自己的迭代器。事实上每一种容器都将其迭代器以嵌套的方式定义于内部。因此各种迭代器的接口相同,型号却不同。这直接导出了泛型程序设计的概念:所有操作行为都使用相同接口,虽然它们的型别不同。
特点:
- 迭代器是可以返回相同类型值的有序序列的一段代码;
- 迭代器可用作方法、运算符或get访问器的代码体;
- 迭代器不是一种成员,它只是实现函数成员的方式,理解这一点是很重要的,一个通过迭代器实现的成员,可以被其他可能或不可能通过迭代器实现的成员覆盖和重载;
3.StringBuilder
概念: StringBuilder是一个可变的字符序列。
特点: 此类提供一个与 StringBuffer 兼容的 API,但不保证同步。该类被设计用作 StringBuffer 的一个简易替换,用在字符串缓冲区被单个线程(StringBuffer与其相似,是多线程时使用)使用的时候
常用方法:
append(String str)/append(Char c):字符串连接
toString():返回一个与构建起或缓冲器内容相同的字符串
setCharAt(int i, char c):将第 i 个代码单元设置为 c(可以理解为替换)
insert(int offset, String str)/insert(int offset, Char c):在指定位置之前插入字符(串)
4.内置数组特点:
- 长度确定后不可更改
- 只能存储同一类型的数据
- 每个存储空间大小一致且地址连续
- 提供角标访问元素
5.封装动态数组
- 属性:
数组有效元素个数
数组的最大容量
数组的存储容器 - 行为:
增,删,改,查,其他
6.线性表的定义
零个或多个数据元素的有限序列
7.线性表的顺序存储结构
- 接口List的定义
- 顺序存储结构ArrayList的定义(增删查改,扩容,缩容)
接口List的定义
- 定义线性表的接口List
- List支持泛型E --> 该List线性表中所存储的具体数据类型由外界决定
编写list的有关功能:如图
package DS01;
public interface List<E> extends Iterable<E>{ //支持泛型<E>,E是一个类型
//获取线性表中元素的有效个数(public abstract 权限+类型)
int getSize();
//判断线性表是否为空表
boolean isEmpty();
//在线性表指定的index角标处插入元素e
void add(int index, E e);
//在线性表的表头处插入元素e
void addFirst(E e);
//在线性表的表位处插入元素e
void addLast(E e);
//获取线性表中指定角标index处的元素
E get(int index);
//获取表头元素
E getFirst();
//获取表尾元素
E getLast();
//修改线性表中指定角标index处的元素为新元素e
void set(int index, E e);
//判断线性表中是否包含元素e
boolean contains(E e);
//查找元素e的角标(从左到又默认第一个出现的元素角标)
int find(E e);
//删除并返回线性表中指定角标index处的元素
E remove(int index);
//删除并返回表头元素
E removeFirst();
//删除并返回表尾元素
E removeLast();
//删除指定元素e
void removeElement(E e);
//清空线性表
void clear();
}
顺序存储结构ArrayList的定义(增删查改,扩容,缩容)
在该文件内实现接口List中的功能
如图:
package DS01;
import java.util.Iterator;
public class ArrayList<E> implements List<E>{ //要实现自己的List(DS01)
/*
导包,将自己原来写的【执行泛型】,两边都要写E
*/
public int getSize;
//创建E类型的一维数组
private E[] date;
//维护元素个数
private int size;
//默认最大容量
private static int DEFAULT_CAPACITY=10;
//创建一个默认大小的顺序表
public ArrayList(){
date=new E[D]
this(DEFAULT_CAPACITY);
}
- 创建线性表List的顺序存储结构实现ArrayList
- public class ArrayList < E > implements List < E > 表示该ArrayList 继承于 List 的有关功能
//创建一个用户指定容量链表的顺序表
public ArrayList(int capacity){
if(capacity<=0){
throw new IllegalArgumentException("非法输入"+capacity);
}
date=(E[])(new Object[capacity]);
size=0;
}
- 判定用户输入的指定容量是否合法
- 若不合法,提示用户
- 若合法–>创建一个与用户传入数组相同大小空间的顺序表
- 并初始化数组的长度size等于0(此时只针对创建空间,并未赋值)
//用户传入一个数组,将其封装成一个顺序表
public ArrayList(E[] date){
if(date==null){
throw new IllegalArgumentException("数组不能为空");
}
//this(data.length); 只能在第一行
this.date=(E[])(new Object[date.length]); //前一个只能在第一行,无法重新定义长度,所以重新创建一个
for (int i = 0; i < date.length; i++) { //依次遍历复制,将其封装成一个顺序表
this.date[i]=date[i];
}
size=date.length;
}
为了防止外部的数据改变导致内部数据改变,此时不能直接将this.date=date(因为外部的date可以被轻易的修改,而此时内部date指向的地址的值也被修改,造成数据隐患)
this(data.length); 该语句用于创造指定的空间,由于在其之前必须判断输入数组是否为空,但该语句只能在第一行,所以不推荐使用,而使用this.date=(E[])(new Object[date.length]);
- 判断传入数组是否为空
- 根据所传的数组创建相应空间
- 依次遍历,将所传数组的值封装成一个顺序表
- 改变顺序表size的值
//得到顺序表的长度
@Override
public int getSize() {
return size;
}
//判断其是否为空
@Override
public boolean isEmpty() {
return size==0;
}
该两步较为简单直接,
得到长度时直接返回顺序表长度size即可;
是否为空直接利用size是否为0判断即可。
//在用户输入的某个角标处,增加一个值
@Override
public void add(int index, E e) {
if(index<0||index>size){ //可以等于size,此时直接在最后添加即可
throw new IllegalArgumentException("角标越界");
}
if(size==date.length){
//扩容
resize(date.length*2);
}
for (int i = size; i >index ; i--) { //往后移
date[i]=date[i-1];
}
date[index]=e;
size++;
}
//在表头增加一个元素
@Override
public void addFirst(E e) {
add(0,e);
}
//在表尾增加一个元素
@Override
public void addLast(E e) {
add(size,e);
}
- 判断添加的位置是否不符合要求(即无法连成一个顺序表);等于size也可,即直接在顺序表之后添加
- 若角标没有越界,此时判断是否顺序表满??
—> 若满,则需要扩容,再进行添加;
—> 若没有满,则直接进行第三步添加步骤。- 将顺序表中的每一个元素往后移(为了不覆盖值,给即将要添加的元素腾出地方),定义指针找到需要移动的元素的位置
- 遍历结束后指针所指向的地方,即为要添加元素的地方,直接进行添加即可
- 及时更新size
【后续的在表头和表尾添加元素时,直接调用 add() 即可】
//当顺序表满时,给顺序表进行扩容/缩容
private void resize(int newlength) {
E[] newdate = (E[])(new Object[newlength]); //创建一个新数组
for (int i = 0; i < size ; i++) {
newdate[i]=date[i]; //传值
}
date=newdate;
}
在删除和增加时均要改变顺序表长度
- 创建一个新的数组,该数组的长度等于所要求传进来的新值
- 依次遍历,进行传值
- 将新建数组全部赋给原数组(偷梁换柱)
//获取某个角标位置上的数字
@Override
public E get(int index) {
if(isEmpty()){
throw new IllegalArgumentException("线性变为空");
}
if(index<0||index>=size){
throw new IllegalArgumentException("角标越界");
}
return date[index];
}
//获取第一位数字
@Override
public E getFirst() {
return get(0);
}
//获取最后一位数字
@Override
public E getLast() {
return get(size-1);
}
获取某角标的位置的数字
- 首先进行数组的判断,判断其是否为空
- 其次进行输入角标是否符合要求的判断:若小于0或者大于等于元素长度,则直接输出角标越界即可
- 若以上条件均不符合,即用户要求所要查找的元素在该数组之中,则直接根据角标进行访问并输出
【获取第一位和最后一位,直接利用get() 即可】
//将某个角标位置所在元素改用户输入值
@Override
public void set(int index, E e) {
if(isEmpty()){
throw new IllegalArgumentException("错误,为空");
}
if(index<0||index>=size){
throw new IllegalArgumentException("角标越界");
}
date[index]=e;
}
- 与获取角标位置的元素相同,先判空,再进行输入角标合法判断
- 通过角标直接访问并修改对应元素
//判断某元素是否在该顺序表内
@Override
public boolean contains(E e) {
return find(e)!=-1;
}
//判断在该顺序表内是否能找到该值
@Override
public int find(E e) {
if(isEmpty()){
throw new IllegalArgumentException("错误,为空");
}
for (int i = 0; i < size; i++) {
if(date[i].equals(e)){
return i;
}
}
return -1;
}
- 首先进行顺序表是否为空的判断
- 若不为空,则利用for循环依次遍历,进行查找对比其 内容( = =比较其地址)
- 比较结束,找到其值返回该元素角标,若没有找到,返回-1
【contains() 函数与之类似,可直接调用find();
若返回不是-1 ,则表示找到该元素】
//删除指定角标位置的元素,并返回所删除元素的值
@Override
public E remove(int index) {
if(isEmpty()){
throw new IllegalArgumentException("错误,为空");
}
if(index<0||index>size-1){
throw new IllegalArgumentException("角标越界");
}
E ret =date[index]; //把要删除的元素赋给一个新的类型
for (int i = index+1; i <size ; i++) {
date[i-1]=date[i];
}
size--;
//前面是满足的条件后面是最小值
if(size<=date.length/4&&date.length/2>=10){
size=date.length/2;
}
return ret;
}
@Override
public E removeFirst() {
return remove(0);
}
@Override
public E removeLast() {
return remove(size-1);
}
- 首先进行数组是否为0和角标是否合法的判断
- 因为要返回所被删除的元素,所以先将要删除元素存放起来
- 从要删除角标的下一位依次遍历,进行覆盖
- 及时更新size
- 进行是否要缩容的判断
若此时满足现在实际长度小于开辟空间的1/4,则值得进行缩容;并且此时也规定,数组最小长度为10,缩容后不能小于最小长度- 满足上述缩容条件即可进行缩容,并进行输出,返回所删除元素
【removeLast() 与 removeFirst() 相似,直接根据需求调用remove() 即可】
//删除指定元素
@Override
public void removeElement(E e) {
int index = find(e);
if(index!=-1){
remove(index);
}else{
throw new IllegalArgumentException("元素不存在");
}
}
- 首先判断是否在该顺序表中存在,调用find() 进行查找
- 若没有该元素,则返回错误
若有该元素,调用remove() 进行删除
@Override
public void clear() {
size=0;
date = (E[])(new Object[DEFAULT_CAPACITY]);
}
//返回最大容量
public int getcapactiy(){
return date.length;
}
清空:直接让size长度等于0,并且进行初始化将元素清空(直接令长度为0,但表中元素依旧存在)
最大容量:返回其长度即可
//将表进行输出
public String toString(){
StringBuilder sb=new StringBuilder();
//String.format 格式化输出(使用指定的格式字符串和参数返回一个格式化字符串)
sb.append(String .format("ArrayList: %d/%d\n",size,date.length));
sb.append('[');
if(isEmpty()){
sb.append(']');
}else{
for (int i = 0; i < size ; i++) {
sb.append(date[i]);
if(i==size-1){
sb.append(']');
}else{
sb.append(',');
}
}
}
//sb内的内容转化为字符串
return sb.toString();
}
【此处输出不使用字符串遍历进行添加输出是因为】:
若用字符串遍历进行添加,会生成一堆字符串(字符串添加的本质是不断创新新的字符串)
StringBuilder 是一个可变的字符序列 append 追加
String.format 格式化输出(使用指定的格式字符串和参数返回一个格式化字符串)
- 首先输出该顺序表中实际存在的元素和所拥有空间
- 输出“ [ ”
- 判断是否为空,若为空,直接输出“ ] ”
- 若不为空,遍历输出该位置的值
- 进行结尾判断,输出“ ] ”
- 由于要返回一个字符串,所以无法直接将StringBuilder的sb对象输出,此时StringBuilder本身中有一个可将其内容转化为String的输出方式,调用即可
//返回当前数据结构的一个迭代器对象
@Override
public Iterator<E> iterator() {
return new ArrayListIterator();
}
//此时不写<E> 若有<E>会与上面的冲突
private class ArrayListIterator implements Iterator{
//迭代器从头开始
private int index=-1;
//是否有下一个
@Override
public boolean hasNext() {
return index<size-1;
}
//移到下一个,并把下一个返回
@Override
public E next() {
index++;
return date[index];
}
}
}
迭代器用于在没有角标支持的环境下遍历元素,【每一个list都需要被迭代】
迭代器创建后,首先判断是否有下一个元素,若有,则移动到下一个元素并进行输出,若没有,则终止迭代
【需要利用接口】
简单TestArrayList实现
package DS01;
import java.util.Iterator;
public class TestArrayList {
public static void main(String[] args) {
int[] a=new int[]{1,2,3,4};
for (int num:a) {
System.out.print(num+" ");
}
ArrayList<Integer> list=new ArrayList<>();
for(int i=1;i<=5;i++){
list.addFirst(i);
}
for(int i=6;i<=10;i++){
list.addLast(i);
}
System.out.println();
for(int i=0;i<list.getSize();i++){
System.out.print(list.get(i)+" ");
}
/*
但凡能够被foreach循环迭代的对象
都是具有可迭代性的
*/
System.out.println();
for (Integer i:list) {
System.out.print(i+" ");
}
//将子类的iterator返回给父类,相当于是一个多态的实现
Iterator<Integer> it=list.iterator();
while(it.hasNext()){
System.out.print(it.next()+" ");
}
}
}