什么是列表
列表是一种数据项构成的有限序列,即按照一定的线性顺序,排列而成的数据项的集合,在这种数据结构上进行的基本操作包括对元素的的查找,插入,和删除。
列表的两种主要表现是数组和链表,栈和队列是两种特殊类型的列表。
学过 C 语言的同学基本都知道,我们在学习列表的时候会定义一个结构体,然后对这个结构体进行各种操作,由于阿导后期从事 java 这条路,C 语言与我渐行渐远,所以下面阿导会通过 java 语言来阐述列表。
结构
对于列表而言,一般来说只需要两个字段,第一是存储数据的字段,这里用数组进行表示;第二是记录数据的数量字段。由于 java 中数组的长度需要提前声明,因此为了防止数组长度不够,我们需要添加一个辅助字段来判断是否需要对数组进行扩容,那么一个比较简单的列表数据结构就出来了,如下:
public class DaoList<E> {
/**
* 存储列表内容
*/
private Object[] data;
/**
* 存储列表大小
*/
private int size = 0;
/**
* 存储列表容量
*/
private int length = 5;
}
列表的数据结构相对是比较简单的,当然仅仅只是声明了结构是无法将其用到实际开发当中去,下面阿导给出一些对列表常见的操作。
操作
添加元素
:public boolean add(E e)
添加元素,首先需要判断存储元素的数组容量是否够用,然后将元素添加到数组末尾,元素大小加一即可。
插入元素
:public boolean add(int index,E e)
插入元素,第一步要判断插入的位置是否合理,然后腾出插入的位置(如何保证数据不丢失才是关键),最后将元素插入到指定的位置即可,另外元素大小加一。
修改元素
:public void set(E e,int index)
首先需要查找到需要修改元素的下标,若存在则覆盖旧值,否则不做任何操作。
判断指定元素是否存在
:public int indexOf(E e) 或 public boolean contains(E e)
这个比较简单,只需要遍历数组,然后查询到返回下标,若查询不到返回 -1,若不需要返回下标,只需要返回存在与否,使用 contains(E e) 更为合适。
获取指定下标的元素
:public E get(int index)
首先判断下标是否越界,然后直接将对应的下标元素返回即可
元素是否为空
:public boolean isEmpty()
这里直接通过判断其元素大小是否为空就完了。
删除元素
:public E remove(E e) 或 public E remove(int index)
首先需要判断其元素的下标是否存在,若存在则删除,并将数组数据向前移动到删除的位置,这里需要注意的是如何保证数据覆盖过度的问题。
清空所有元素
:public void clear()
通过遍历将数组每个下标一一置空,然后将元素大小置为0。
转成数组
:public Object[] toArray()
这里需要注意的是不要将数组原样返回,因为数组是引用类型,为了保证数据安全性,需要拷贝一个副本给调用方。
遍历元素
:public Iterator iterator()
这个可以直接借助 Iterator ,通过一个内部类实现其 hasNext() 和 next() 方法即可,有兴趣自己写迭代器的可参照阿导之前写过的【迭代器模式】。
完整示例
package com.dao.datastructure.list;
import java.util.Iterator;
/**
* 线性表-列表
*
* @author 阿导
* @CopyRight 万物皆导
* @Created 2019-11-18 13:47:00
*/
public class DaoList<E> {
/**
* 存储列表内容
*/
private Object[] data;
/**
* 存储列表大小
*/
private int size = 0;
/**
* 存储列表容量
*/
private int length = 5;
/**
* 默认表长度给五
*
* @return
* @author 阿导
* @time 2019/11/18 :00
*/
public DaoList() {
this(5);
}
/**
* 指定长度
*
* @param length
* @return
* @author 阿导
* @time 2019/11/18 :00
*/
public DaoList(int length) {
if (length < 0) {
length = this.length;
} else {
this.length = length;
}
this.data = new Object[length];
}
/**
* 添加元素,将指定元素添加到列表尾部
*
* @param e
* @return boolean
* @author 阿导
* @time 2019/11/18 :00
*/
public boolean add(E e) {
this.expansion(1);
this.data[this.size++] = e;
return true;
}
/**
* 将元素添加到指定位置
*
* @author 阿导
* @time 2019/11/18 :00
* @param index
* @param e
* @return boolean
*/
public boolean add(int index,E e){
// 下标越界判断
this.arrayIndexOutOfBoundsException(index);
// 是否需要进行扩容
this.expansion(1);
// 后移一位,留出当前位置插入
int pox = this.size++;
while (pox>index){
this.data[pox] = this.data[--pox];
}
this.data[index] = e;
return true;
}
/**
* 判断是否
*
* @param e
* @return int
* @author 阿导
* @time 2019/11/18 :00
*/
public int indexOf(E e) {
// 当前指针
int pox = 0;
// 遍历其大小
while (pox < this.size) {
// 第一步要对 null 进行单独处理,防止空指针,然后判断是否相等
if((e == null && this.data[pox] == null)||
e!=null && e.equals(this.data[pox])){
return pox;
}
pox++;
}
// 未查询到返回 -1
return -1;
}
/**
* 是否包含某个元素
*
* @author 阿导
* @time 2019/11/18 :00
* @param e
* @return boolean
*/
public boolean contains(E e){
return this.indexOf(e)>-1;
}
/**
* 判断是否为空
*
* @author 阿导
* @time 2019/11/18 :00
* @return boolean
*/
public boolean isEmpty(){
return this.size==0;
}
/**
* 返回当前列表大小
*
* @author 阿导
* @time 2019/11/18 :00
* @return int
*/
public int size(){
return this.size;
}
/**
* 转化成数组
*
* @author 阿导
* @time 2019/11/18 :00
* @return E[]
*/
public Object[] toArray(){
Object[] dataTemp = new Object[this.size];
System.arraycopy(this.data, 0, dataTemp, 0, this.data.length);
return dataTemp;
}
/**
* 获取指定位置元素
*
* @author 阿导
* @time 2019/11/18 :00
* @param index
* @return E
*/
public E get(int index){
// 下标越界判断
this.arrayIndexOutOfBoundsException(index);
// 获取指定位置数据
return this.getData(index);
}
/**
* 获取指定位置数据
*
* @author 阿导
* @time 2019/11/18 :00
* @param index
* @return E
*/
private E getData(int index) {
return (E)this.data[index];
}
/**
* 若下标越界,抛出异常即可
*
* @author 阿导
* @time 2019/11/18 :00
* @return void
* @param index
*/
private void arrayIndexOutOfBoundsException(int index) {
if(index>=this.size||index<0){
throw new ArrayIndexOutOfBoundsException("数组越界!!!");
}
}
/**
* 指定位置设置值
*
* @author 阿导
* @time 2019/11/18 :00
* @param e
* @param index
* @return boolean
*/
public void set(E e,int index){
this.arrayIndexOutOfBoundsException(index);
this.data[index]=e;
}
/**
* 移除某个位置的元素
*
* @author 阿导
* @time 2019/11/18 :00
* @param index
* @return E
*/
public E remove(int index){
this.arrayIndexOutOfBoundsException(index);
// 删除之前获取元素
E e = this.getData(index);
// 然后将数组下标向前移一位
int pox=index;
while (pox<this.size-1){
this.data[pox]=this.data[++pox];
}
// 末尾置空
this.data[--this.size]=null;
// 返回指定的元素
return e;
}
/**
* 移除某个元素
*
* @author 阿导
* @time 2019/11/18 :00
* @param e
* @return boolean
*/
public E remove(E e){
int index = this.indexOf(e);
// 未查询到不做任何处理,直接返回
if(index==-1){
return e;
}
// 移除吧
this.remove(index);
// 继续执行,直到里面所有的元素都删除
return remove(e);
}
/**
* 清空
*
* @author 阿导
* @time 2019/11/18 :00
* @return boolean
*/
public void clear(){
int pox=0;
// 遍历置空
while (pox<this.size){
this.data[pox]=null;
}
this.size=0;
}
/**
* 判断是否需要进行扩容,扩容方案有很多,直接向右移1个位加上目前的长度
*
* @return void
* @author 阿导
* @time 2019/11/18 :00
*/
private void expansion(int num) {
// 之所以判断是否需要扩容,是判断其需要添加的元素数量和当前大小之和是否大于当前长度
if (this.size+num > this.length) {
this.length = this.size+num + (this.length >> 1);
//数组动态态扩容
Object[] dataTemp = new Object[this.length];
//数组复制
System.arraycopy(this.data, 0, dataTemp, 0, this.data.length);
//改变引用
this.data = dataTemp;
}
}
/**
* 迭代器
*
* @author 阿导
* @time 2019/11/18 :00
* @return java.util.Iterator<E>
*/
public Iterator<E> iterator(){
/**
* 创建一个局部内部类,实现Iterator
*/
class MyIterator implements Iterator<E>{
/**
* 数组的索引
*/
private int index;
/**
* 检查是否有下一个元素
*
* @author 阿导
* @time 2019/11/18 :00
* @return boolean
*/
@Override
public boolean hasNext() {
return index < size ? true : false;
}
/**m
* 获取数据
* @return
*/
@Override
public E next() {
return getData(this.index++);
}
}
return new MyIterator();
}
}
后话
离别总是伤感的,列表的阐述已告一段落,其实在 java 中,我们都清楚并熟练的使用列表 List,其实现类有很多,其中底层和阿导实现方式相同的常见的类是 ArrayList、Vector,这两个类也是很多面试官特别喜欢问的,其实这两个实现类主要区别是 Vector 做了线程同步处理,其它的实现基本一致,因此在多线程的环境下建议使用 Vector,反之使用 ArrayList。
最后不知道大家有没有注意到,在列表声明的时候,如果能预先知道数组的大小,不妨在声明的时候,就将容量设置好,这样就避免了数组扩容导致数组大量数据拷贝,进而引发性能问题。
不知各位看官对列表是否有一定的了解,如有疑问,欢迎留言,如有不当之处,请多多包涵和指教!