数据结构ArrayList顺序表底层方法的实现

简介: 本文讲解:数据结构ArrayList顺序表底层方法的实现

 image.gif编辑

 

前言

大家好,我还是那个不会打拳的程序猿。本期给大家带来的是数据结构之线性表是什么,顺序表、模拟实现ArrayList中的方法、ArrayList的使用、顺序表的优缺点等等。

image.gif编辑

目录

1.什么是线性表

2.顺序表

2.1模拟实现List接口中的方法

2.1.1创建顺序表

2.1.2遍历顺序表

2.1.3判断顺序表是否满了

2.1.4新添元素

2.1.5判断顺序表是否包含某个元素

2.1.6查找某个元素位置

2.1.7获取某下标的元素

2.1.8修改元素

2.1.9删除第一次出现的关键字

2.1.10获取顺序表的长度

2.1.11清空顺序表

3.ArrayLIst的使用

3.1ArrayList进行构造

3.2ArrayLIst的使用

3.3ArrayList进行遍历

1.什么是线性表

线性表是n具有相同特性的数据元素的有限序列,它在数据结构中时常出现。常见的线性表有:顺序表、链表、栈、队列等等

下图为顺序表的一个简图:

image.gif编辑

下图为链表的一个简图:

image.gif编辑

今天我们主要学习的是顺序表,通过上图我们可以了解到顺序表就是一个数组。没错,它就是一个数组,只是顺序表相对与数组来说更有逻辑性一些。下面我就来讲解顺序表是什么,以及顺序表实现的一些方法。


2.顺序表

顺序表是一组连续存储单元来储数据元素的线性结构,一般使用数组来存储。实现数据的增删改查。

ArrayList是个动态数组,实现List接口,主要用来存储数据,如果存储基本类型的数据,如int,long,boolean,short,byte,那只存储它们对应的包装类。


2.1模拟实现List接口中的方法

接口中的方法有:

public class SeqList {
  // 打印顺序表
  public void display() {  }
   // 新增元素,默认在数组最后新增
  public void add(int data) { }
  // 在某下标位置新增元素
  public void add(int pos, int data) { }
  // 判定是否包含某个元素
  public boolean contains(int toFind) { return true; }
  // 查找某个元素对应的位置
  public int indexOf(int toFind) { return -1; }
  // 获取某下标的元素
  public int get(int pos) { return -1; }
  // 给pos下标位置的元素修改为value
  public void set(int pos, int value) {  }
  //删除第一次出现的关键字key
  public void remove(int toRemove) {  }
  // 获取顺序表长度
  public int size() { return 0; }
  // 清空顺序表
  public void clear() {  }
}

image.gif

那么这些,接口方法就是ArrayList底层的源码实现。当我们实例化一个ArrayList时调用的就是这些方法。比如:

image.gif编辑

因此,我们来实现这些接口方法,让我们理解ArrayList底层是怎样工作的。在以后我们进行调用这些方法时,知道它是用来进行什么样的操作的!


2.1.1创建顺序表

public class ArrayList {
    //常量
    public static final int DEFAULT_SIZE = 10;
    //下标的有效值
    public int useSize;
    //初始化一个数组
    public int[] arr = new int[DEFAULT_SIZE];
    public void myArratList() {
        this.arr = new int[DEFAULT_SIZE];
    }
}

image.gif

以上代码中useSize为有效数据下标值,并且创建了一个arr数组。此时arr数组里面没有任何的元素,我们可以通过add方法来添加,add方法会在下方讲解。


2.1.2遍历顺序表

public void disPlay() {
        //遍历顺序表
        for (int i = 0; i < useSize; i++) {
            System.out.println(arr[i]);
        }
    }

image.gif

遍历数组我们直接使用for循环来遍历即可,但上述代码中我还没添加任何的元素,因此遍历的为空。


2.1.3判断顺序表是否满了

//判断顺序表是否满了
    public boolean isFull() {
        return this.useSize == arr.length;
    }

image.gif

判断顺序表是否满了,我们直接拿当前有效数据下标与数组的总长度进行比较即可,也急速useSize与arr.length进行比较。


2.1.4新添元素

//新添元素
    public void add(int date) {
        if (isFull()) {
            this.arr = Arrays.copyOf(arr,2*arr.length);
        }
        this.arr[useSize] = date;
        useSize++;
    }

image.gif

新添元素,我们得先判断这个数组是否满了。如果满了,我们就得先把数组进行扩容,在扩容的基础上我们才能新添元素。在上述代码中,默认按照顺序表最后一位处增添,还有Arrays.copyOf方法的作用是使数组扩大输入的倍数,如Arrays.copuOf(数组名,扩大倍数);


2.1.5判断顺序表是否包含某个元素

//判断是否包含某个元素
    public boolean contains(int toFind) {
        for (int i = 0; i < this.useSize; i++) {
            if (toFind == arr[i]) {
                return true;
            }
        }
        return false;
    }

image.gif

判断顺序表中是否有某个元素,我们直接通过for循环遍历来寻找即可,通过判断语句依次访问数组中的每个元素有则返回true无则返回false。


2.1.6查找某个元素位置

//查找某个元素的位置(下标)
    public int indexOf(int toFind) {
        for (int i = 0; i < this.useSize; i++) {
            if (toFind == arr[i]) {
                return i+1;
            }
        }
        return -1;
    }

image.gif

查找某个元素的位置也就是下标,跟查找是否有某个元素是一样的。直接用for循环遍历,直至找到该元素返回下标,如果没有则返回-1。因为数组里面是没有下标为-1的元素的。


2.1.7获取某下标的元素

//获取某下标的值
    public int getNum(int pos) {
        isLegal2(pos);
        return this.arr[pos];
    }

image.gif

获取某下标的元素首先我们得先检查这个下标是否合法,一个下标是否合法的体现为,它不能小于0,它不能大于有效下标。因此我们设置一个isLegal2方法来判断。

//判断是否合法
    public void isLegal2(int pos) {
        if (pos <0 || pos>=this.useSize) {
            throw new IndexOutOfException("输入的下标不合法");
        }
    }

image.gif

当我们的下标并不合法时候,我们又可以自定义一个异常来提示这个下标不合法,因此我们可以新建一个名为IndexOutOfException的方法继承RuntimeException(运行时异常)。当我们下标不合法就会抛出该异常。

public class IndexOutOfException extends RuntimeException{
    public IndexOutOfException() {
    };
    public IndexOutOfException(String msg){
        super(msg);
    }
}

image.gif


2.1.8修改元素

//修改元素
    public void set(int pos,int date) {
        isLegal2(pos);
        this.arr[pos] = date;
    }

image.gif

修改元素也是需要判断下标的合法性,因此可以复用上述获取某下标的元素中的isLegal2方法来判断是否合法


2.1.9删除第一次出现的关键字

//删除第一次出现的元素
    public boolean delFirst(int date) {
        int flag = indexOf(date);
        if (flag == -1) {
            return false;
        }
        for (int i = flag; i < useSize; i++) {
            this.arr[flag] = this.arr[flag+1];
        }
        this.useSize--;
        this.arr[useSize] = 0;
        return true;
    }

image.gif

当我们删除了第一次出现的关键字后,我们得把当前的有效下标数进行自减一次。


2.1.10获取顺序表的长度

//获取顺序表长度
    public int size() {
        return this.useSize;
    }

image.gif

获取顺序表的长度也是非常简单的,我们只需要返回有效下标的值即可。主要不要返回数组总长度,因为一个数组里不一定有元素。


2.1.11清空顺序表

//清空顺序表
    public void clearArrayList() {
        for (int i = 0; i < this.useSize; i++) {
            this.arr[i] = 0;
        }
        this.useSize = 0;
    }

image.gif

清空顺序表因为不考虑顺序表里面有引用类型,我们只要把顺序的所有元素置为0就行,当顺序表里面为引用类型时就置为null。


3.ArrayLIst的使用


3.1ArrayList进行构造

方法 解释
ArrayList() 无参进行构造
ArrayList(Collection <? extends E> c) 利用其他的Collection来进行创建ArrayList
ArrayList(int initialCapacity) 指定顺序表初始化内容
public class Test {
    public static void main(String[] args) {
        //不指定长度,最常见的用法
        List<Integer> list1 = new ArrayList<>();
        //指定顺序表的长度为5
        List<Integer> list2 = new ArrayList<>(5);
        list1.add(1);
        //报错,因为list1中的类型为整型不得存放字符串型
        list1.add("abc");
        //list3内容与list1一致
        List<Integer> list3 = new ArrayList<>(list1);
        //没有指明泛型类中的泛型类
        List list4 = new ArrayList<>();
        list4.add(1);
        list4.add("abc");
    }
}

image.gif

image.gif编辑

以上就是对顺序表的几种构造方式。


3.2ArrayLIst的使用

方法 解释
boolean add(E e) 尾插 e(最后一位开始插入)
void add(int index, E element) 将 e 插入到 index 位置
boolean addAll(Collection<? extends E> c) 尾插 c 中的元素
E remove(int index 删除 index 位置元素
boolean remove(Object o) 删除遇到的第一个 o
E get(int index) 获取下标 index 位置元素
E set(int index, E element)  将下标 index 位置元素设置为 element
void clear() 清空所有元素
boolean contains(Object o)  判断 o 是否在线性表中
int indexOf(Object o)  返回第一个 o 所在下标
int lastIndexOf(Object o) 返回最后一个 o 的下标
List<E> subList(int fromIndex, int toIndex)  截取部分 list

以上表格中的方法,我们在2.模拟实现接口中的方法中已经给大家展示到了。 现在我们使用ArrayList来实现一下这些方法:

public class Test {
    public static void main(String[] args) {
        //实例化一个泛型类为Integer的ArrayList
        List<Integer> list = new ArrayList<>();
        //add来添加5个元素
        list.add(11);
        list.add(12);
        list.add(13);
        list.add(14);
        list.add(15);
        //输出该顺序表
        System.out.println(list);
        //将6插入到4下标的位置
        list.add(4,6);
        //输出顺序表
        System.out.println(list);
        //去除4下标位置的元素
        list.remove(4);
        //输出顺序表
        System.out.println(list);
        //得到2下标的值并输出
        System.out.println(list.get(2));
        //把0下标的值修改为9
        list.set(0,9);
        System.out.println(list);
    }
}

image.gif

运行后输出:

image.gif编辑

以上代码,就是对ArrayList中的一些方法进行的一些操作,当然我只是演示了一部分。感兴趣的伙伴可以下来自行测试!


3.3ArrayList进行遍历

ArrayLIst遍历方式有三种:for循环进行遍历、foreach进行遍历、迭代器进行遍历。

(1)for循环遍历

public class Test {
    public static void main(String[] args) {
        //实例化一个Integer类型的list引用
        List<Integer> list = new ArrayList<>();
        //添加5个元素
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        //for循环进行遍历
        for (int i = 0; i < list.size(); i++) {
            System.out.print(list.get(i)+" ");
        }
    }
}

image.gif

运行后输出:

image.gif编辑以上代码,通过list.size()方法来获取顺序表中的有效下标,并且通过get()方法来获取i下标中的元素。


(2)foreach遍历

public class Test {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        //添加5个元素
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        //foreach进行遍历
        for (Integer integer:list) {
            System.out.print(integer+" ");
        }
    }
}

image.gif

运行后输出:

image.gif编辑

foreach就比较方法了,直接用一个Integer类的数据,直接访问了顺序表中的每个元素。


(3)迭代器遍历

public class Test {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        //添加5个元素
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        //使用迭代器实现
        Iterator<Integer> it = list.listIterator();
            while(it.hasNext()){
                System.out.print(it.next() + " ");
            }
                System.out.println();
    }
}

image.gif

运行后输出:

image.gif编辑


通过上文中的接口方法我们可以看到顺序有一些不足,顺序表优缺点:

优点:查找数据非常的方便,当我们查一个数据时我们直接使用下标即可访问。因此它的时间复杂度为O(1)。

缺点:插入和删除、扩容。插入或删除数据非常得麻烦,当我们插入一个数据时,我们必须把插入值的下标往后的下标,依次一个个的往后移。当我们的顺序表满的时候,我们还得进行扩容,扩容会造成我们空间的浪费。比如我们的顺序表长度为100,我进行扩容数组1.5,我只需要存储一个元素,因此浪费了49个空间。

因此,为了插入、删除、扩容更方便的去操作。我们有了链式存储这个数据结构,这个结构我们称为链表。下期我会给大家讲解。


对顺序表接口方法进行讲解是为了我们更好的去使用,也是为了让我们了解到这些接口方法底层是怎样进行操作的,本期博客到这里就结束了,感谢你的阅读

image.gif编辑

下期预告:ArrayList实现大众麻将、扑克牌洗牌操作。

相关文章
|
2月前
|
机器学习/深度学习 算法 数据挖掘
K-means聚类算法是机器学习中常用的一种聚类方法,通过将数据集划分为K个簇来简化数据结构
K-means聚类算法是机器学习中常用的一种聚类方法,通过将数据集划分为K个簇来简化数据结构。本文介绍了K-means算法的基本原理,包括初始化、数据点分配与簇中心更新等步骤,以及如何在Python中实现该算法,最后讨论了其优缺点及应用场景。
146 4
|
1天前
|
机器学习/深度学习 存储 C++
【C++数据结构——线性表】顺序表的基本运算(头歌实践教学平台习题)【合集】
本文档介绍了线性表的基本运算任务,涵盖顺序表和链表的初始化、销毁、判定是否为空、求长度、输出、查找元素、插入和删除元素等内容。通过C++代码示例详细展示了每一步骤的具体实现方法,并提供了测试说明和通关代码。 主要内容包括: - **任务描述**:实现顺序表的基本运算。 - **相关知识**:介绍线性表的基本概念及操作,如初始化、销毁、判定是否为空表等。 - **具体操作**:详述顺序表和链表的初始化、求长度、输出、查找、插入和删除元素的方法,并附有代码示例。 - **测试说明**:提供测试输入和预期输出,确保代码正确性。 - **通关代码**:给出完整的C++代码实现,帮助完成任务。 文档
19 5
|
15天前
|
数据库
数据结构中二叉树,哈希表,顺序表,链表的比较补充
二叉搜索树,哈希表,顺序表,链表的特点的比较
数据结构中二叉树,哈希表,顺序表,链表的比较补充
|
2月前
|
存储 Java 索引
Java中的数据结构:ArrayList和LinkedList的比较
【10月更文挑战第28天】在Java编程世界中,数据结构是构建复杂程序的基石。本文将深入探讨两种常用的数据结构:ArrayList和LinkedList,通过直观的比喻和实例分析,揭示它们各自的优势与局限,帮助你在面对不同的编程挑战时做出明智的选择。
|
2月前
|
存储 算法 安全
2024重生之回溯数据结构与算法系列学习之顺序表【无论是王道考研人还真爱粉都能包会的;不然别给我家鸽鸽丢脸好嘛?】
顺序表的定义和基本操作之插入;删除;按值查找;按位查找等具体详解步骤以及举例说明
|
2月前
|
存储 C语言
【数据结构】顺序表(c语言实现)(附源码)
本文介绍了线性表和顺序表的基本概念及其实现。线性表是一种有限序列,常见的线性表有顺序表、链表、栈、队列等。顺序表是一种基于连续内存地址存储数据的数据结构,其底层逻辑是数组。文章详细讲解了静态顺序表和动态顺序表的区别,并重点介绍了动态顺序表的实现,包括初始化、销毁、打印、增删查改等操作。最后,文章总结了顺序表的时间复杂度和局限性,并预告了后续关于链表的内容。
90 3
|
2月前
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之顺序表习题精讲【无论是王道考研人还真爱粉都能包会的;不然别给我家鸽鸽丢脸好嘛?】
顺序表的定义和基本操作之插入;删除;按值查找;按位查找习题精讲等具体详解步骤以及举例说明
|
3月前
|
Java C++ 索引
让星星⭐月亮告诉你,LinkedList和ArrayList底层数据结构及方法源码说明
`LinkedList` 和 `ArrayList` 是 Java 中两种常见的列表实现。`LinkedList` 基于双向链表,适合频繁的插入和删除操作,但按索引访问元素效率较低。`ArrayList` 基于动态数组,支持快速随机访问,但在中间位置插入或删除元素时性能较差。两者均实现了 `List` 接口,`LinkedList` 还额外实现了 `Deque` 接口,提供了更多队列操作。
33 3
|
3月前
|
存储 缓存 索引
从底层数据结构和CPU缓存两方面剖析LinkedList的查询效率为什么比ArrayList低
本文详细对比了ArrayList和LinkedList的查询效率,从底层数据结构和CPU缓存两个方面进行分析。ArrayList基于动态数组,支持随机访问,查询时间复杂度为O(1),且CPU缓存对其友好;而LinkedList基于双向链表,需要逐个节点遍历,查询时间复杂度为O(n),且CPU缓存对其帮助不大。文章还探讨了CPU缓存对数组增删操作的影响,指出缓存主要作用于读取而非修改。通过这些分析,加深了对这两种数据结构的理解。
59 2
|
3月前
|
存储
ES6中的Set数据结构的常用方法和使用场景
ES6中的Set数据结构的常用方法和使用场景