ArrayList与顺序表

2023-11-19

目录

​编辑

一、线性表

二、顺序表 

1、接口的实现

(1)打印顺序表

(2)新增元素

(3)判定是否包含某个元素

(4)查找某个元素对应的位置下标

(5)获取 pos 位置的元素

(6)获取顺序表长度

(7)给 pos 位置的元素设为 value【更新的意思 】

(8)在 pos 位置新增元素

(9)删除第一次出现的关键字key

(10)清空顺序表

三、ArrayList简介

 四、ArrayList使用

1、ArrayList的构造

2、ArrayList常见操作

3、ArrayList的遍历

4、ArrayList的扩容机制 

五、ArrayList的具体使用

六、ArrayList的问题及思考


一、线性表

线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列...
线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。

二、顺序表 

顺序表是用一段 物理地址连续 的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数据的增删查改。

1、接口的实现

(1)打印顺序表

注意:该方法并不是顺序表中的方法,为了方便看测试结果给出的

  public void display() {
        for (int i = 0; i < this.usedSize; i++) {
            System.out.print(this.elem[i] +" ");
        }
        System.out.println();
    }

(2)新增元素

默认在数据 最后新增

 public void add(int data) {
        //首先得判断满的情况
        if(isFull()) {
            resize();
        }
        this.elem[usedSize] = data;
        usedSize++;
    }
    public boolean isFull() {
        return usedSize == elem.length;
    }

(3)判定是否包含某个元素

  public boolean contains(int toFind) {
        for (int i = 0; i < this.usedSize; i++) {
            if(elem[i] == toFind) {
                return true;
            }
        }
        return false;
    }

(4)查找某个元素对应的位置下标

  public int indexOf(int toFind) {
        for (int i = 0; i < this.usedSize; i++) {
            if(elem[i] == toFind) {
                return i;
            }
        }
        return -1;
    }

(5)获取 pos 位置的元素

  public int get(int pos) {
        if(!checkPos(pos)) {
            throw new PosOutBoundsException("get 获取数据时,位置不合法!");
        }
        return elem[pos];
    }

(6)获取顺序表长度

  public int size() {
        return this.usedSize;
    }

(7)给 pos 位置的元素设为 value【更新的意思 】

public void set(int pos, int value) {
        if(!checkPos(pos)) {
            throw new PosOutBoundsException("set 数据时,位置不合法!");
        }
        this.elem[pos] = value;
    }

    private boolean checkPos(int pos) {
        if(pos < 0 || pos >= usedSize) {
            return false;
        }
        return true;
    }

(8)在 pos 位置新增元素

 public void add(int pos, int data) {
        if(pos < 0 || pos > this.usedSize) {
            throw new PosOutBoundsException("add 元素的时候,pos位置不合法!");
        }
        if(isFull()) {
            resize();
        }
        //挪数据
        for (int i = this.usedSize-1; i >= pos ; i--) {
            this.elem[i+1] = this.elem[i];
        }
        //存数据
        this.elem[pos] = data;
        this.usedSize++;
    }

(9)删除第一次出现的关键字key

   public void remove(int toRemove) {
        if(isEmpty()) {
            return;
        }
        int index = indexOf(toRemove);
        if(index == -1) {
            return;//没有你要删除的数字
        }
        for (int i = index; i < usedSize-1; i++) {
            this.elem[i] = this.elem[i+1];
        }
        usedSize--;
        //elem[us] = null;
    }

(10)清空顺序表

 public void clear() {
        /*for (int i = 0; i < usedSize; i++) {
            this.elem[i] = null;
        }*/
        usedSize = 0;
    }

三、ArrayList简介

在集合框架中,ArrayList是一个普通的类,实现了List接口,具体框架图如下:

 【说明

1. ArrayList 是以 泛型 方式实现的, 使用时必须要先实例化
2. ArrayList 实现了 RandomAccess 接口,表明 ArrayList 支持随机访问
3. ArrayList 实现了 Cloneable 接口,表明 ArrayList 是可以 clone
4. ArrayList 实现了 Serializable 接口,表明 ArrayList 是支持序列化的
5. Vector 不同, ArrayList不是线程安全的,在单线程下可以使用,在多线程中可以选择Vector或者 CopyOnWriteArrayList
6. ArrayList 底层是一段连续的空间,并且可以动态扩容,是一个 动态 类型的顺序表

 四、ArrayList使用

1、ArrayList的构造

方法
解释
ArrayList ()
无参构造
ArrayList (Collection<? extends E> c)
利用其他 Collection 构建 ArrayList
ArrayList (int initialCapacity)
指定顺序表初始容量

现在,让我们来尝试创建一个顺序表:

public static void main(String[] args) {
// ArrayList创建,推荐写法
// 构造一个空的列表
        List<Integer> list1 = new ArrayList<>();
// 构造一个具有10个容量的列表
        List<Integer> list2 = new ArrayList<>(10);
        list2.add(1);
        list2.add(2);
        list2.add(3);
// list2.add("hello"); // 编译失败,List<Integer>已经限定了,list2中只能存储整形元素
// list3构造好之后,与list中的元素一致
        ArrayList<Integer> list3 = new ArrayList<>(list2);
// 避免省略类型,否则:任意类型的元素都可以存放,使用时将是一场灾难
        List list4 = new ArrayList();
        list4.add("111");
        list4.add(100);
        }

2、ArrayList常见操作

ArrayList 虽然提供的方法比较多,但是常用方法如下所示,需要用到其他方法时,可以自行查看 ArrayList 的帮助文档。

现在,我们来试着使用一下这些方法来完成一段代码的编写:

public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("JavaSE");
        list.add("JavaWeb");
        list.add("JavaEE");
        list.add("JVM");
        list.add("测试课程");
        System.out.println(list);
// 获取list中有效元素个数
        System.out.println(list.size());
// 获取和设置index位置上的元素,注意index必须介于[0, size)间
        System.out.println(list.get(1));
        list.set(1, "JavaWEB");
        System.out.println(list.get(1));
// 在list的index位置插入指定元素,index及后续的元素统一往后搬移一个位置
        list.add(1, "Java数据结构");
        System.out.println(list);
// 删除指定元素,找到了就删除,该元素之后的元素统一往前搬移一个位置
        list.remove("JVM");
        System.out.println(list);
// 删除list中index位置上的元素,注意index不要超过list中有效元素个数,否则会抛出下标越界异常
        list.remove(list.size()-1);
        System.out.println(list);
        // 检测list中是否包含指定元素,包含返回true,否则返回false
        if(list.contains("测试课程")){
        list.add("测试课程");
        }
// 查找指定元素第一次出现的位置:indexOf从前往后找,lastIndexOf从后往前找
        list.add("JavaSE");
        System.out.println(list.indexOf("JavaSE"));
        System.out.println(list.lastIndexOf("JavaSE"));
// 使用list中[0, 4)之间的元素构成一个新的SubList返回,但是和ArrayList共用一个elementData数组
        List<String> ret = list.subList(0, 4);
        System.out.println(ret);
        list.clear();
        System.out.println(list.size());
        }

3、ArrayList的遍历

ArrayList 可以使用三方方式遍历: for 循环 + 下标、 for each 、使用迭代器
使用for循环遍历:
for (int i = 0; i < list.size(); i++) {
        System.out.print(list.get(i) + " ");
        }
        System.out.println();

使用for each遍历:

for (Integer integer : list) {
        System.out.print(integer + " ");
        }
        System.out.println();

使用迭代器遍历:

Iterator<Integer> it = list.listIterator();
        while(it.hasNext()){
        System.out.print(it.next() + " ");
        }
        System.out.println();
        }
注意:
1. ArrayList 最常使用的遍历方式是: for 循环 + 下标 以及 fo reach
2. 迭代器是设计模式的一种。

4、ArrayList的扩容机制 

ArrayList 是一个动态类型的顺序表,即:在插入元素的过程中会自动扩容。以下是 ArrayList 源码中扩容方式:
Object[] elementData; // 存放元素的空间
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 默认空间
private static final int DEFAULT_CAPACITY = 10; // 默认容量大小
public boolean add(E e) {
        ensureCapacityInternal(size + 1); // Increments modCount!!
        elementData[size++] = e;
        return true;
        }
private void ensureCapacityInternal(int minCapacity) {
        ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
        }
private static int calculateCapacity(Object[] elementData, int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        return Math.max(DEFAULT_CAPACITY, minCapacity);
        }
        return minCapacity;
        }
private void ensureExplicitCapacity(int minCapacity) {
        modCount++;
// overflow-conscious code
        if (minCapacity - elementData.length > 0)
        grow(minCapacity);
        }
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
// 获取旧空间大小
        int oldCapacity = elementData.length;
// 预计按照1.5倍方式扩容
        int newCapacity = oldCapacity + (oldCapacity >> 1);
// 如果用户需要扩容大小 超过 原空间1.5倍,按照用户所需大小扩容
        if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
// 如果需要扩容大小超过MAX_ARRAY_SIZE,重新计算容量大小
        if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
// 调用copyOf扩容
        elementData = Arrays.copyOf(elementData, newCapacity);
        }
private static int hugeCapacity(int minCapacity) {
// 如果minCapacity小于0,抛出OutOfMemoryError异常
        if (minCapacity < 0)
        throw new OutOfMemoryError();
        return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
        }

那么现在,我们对ArrayList源码中扩容的部分进行一个总结:

【总结】
1. 检测是否真正需要扩容,如果是调用 grow 准备扩容
2. 预估需要扩容的大小
        初步预估按照1.5 倍大小扩容
        如果用户所需大小超过预估1.5 倍大小,则按照用户所需大小扩容
        真正扩容之前检测是否能扩容成功,防止太大导致扩容失败
3. 使用 copyOf 进行扩容

五、ArrayList的具体使用

在学习完ArrayList之后,我们可以使用ArrayList来完成对一些特定情况的使用,现在,我们来尝试使用ArrayList来编写一个简单的洗牌算法

大致思路如下:

我们可以先创建一个Card类,其中属性为牌的花色和牌面的数值,然后再将一整副牌都存进一个借用ArrayList创建的顺序表中,其次借用Random将扑克牌进行打乱处理,此时为了能够将牌顺利的发给三个人,我们可以在为每个人单独创建一个顺序表,并且再用一个顺序表来存储它们三个人分别的顺序表

代码的具体实现如下:

public class Card {
    public int rank; // 牌面值
    public String suit; // 花色
    @Override
    public String toString() {
        return String.format("[%s %d]", suit, rank);
    }
}
import java.util.List;
        import java.util.ArrayList;
        import java.util.Random;
public class CardDemo {
    public static final String[] SUITS = {"♠", "♥", "♣", "♦"};
    // 买一副牌
    private static List<Card> buyDeck() {
        List<Card> deck = new ArrayList<>(52);
        for (int i = 0; i < 4; i++) {
            for (int j = 1; j <= 13; j++) {
                String suit = SUITS[i];
                int rank = j;
                Card card = new Card();
                card.rank = rank;
                card.suit = suit;
                deck.add(card);
            }
        }
        return deck;
    }
    private static void swap(List<Card> deck, int i, int j) {
        Card t = deck.get(i);
        deck.set(i, deck.get(j));
        deck.set(j, t);
    }
    private static void shuffle(List<Card> deck) {
        Random random = new Random(20190905);
        for (int i = deck.size() - 1; i > 0; i--) {
            int r = random.nextInt(i);
            swap(deck, i, r);
        }
    }
    public static void main(String[] args) {
        List<Card> deck = buyDeck();
        System.out.println("刚买回来的牌:");
        System.out.println(deck);
        shuffle(deck);
        System.out.println("洗过的牌:");
        System.out.println(deck);
// 三个人,每个人轮流抓 5 张牌
        List<List<Card>> hands = new ArrayList<>();
        hands.add(new ArrayList<>());
        hands.add(new ArrayList<>());
        hands.add(new ArrayList<>());
        for (int i = 0; i < 5; i++) {
            for (int j = 0; j < 3; j++) {
                hands.get(j).add(deck.remove(0));
            }
        }
        System.out.println("剩余的牌:");
        System.out.println(deck);
        System.out.println("A 手中的牌:");
        System.out.println(hands.get(0));
        System.out.println("B 手中的牌:");
        System.out.println(hands.get(1));
        System.out.println("C 手中的牌:");
        System.out.println(hands.get(2));
    }
}

六、ArrayList的问题及思考

1. ArrayList 底层使用连续的空间,任意位置插入或删除元素时,需要将该位置后序元素整体往前或者往后搬移,故时间复杂度为O(N)
2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
3. 增容一般是呈 2 倍的增长,势必会有一定的空间浪费。例如当前容量为 100 ,满了以后增容到 200 ,我们再继续插入了5 个数据,后面没有数据插入了,那么就浪费了 95 个数据空间。
那么如何来解决这些问题呢?这就将与我们后面即将学到的链表有关了!
欢迎阅读下一期的链表博客
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

ArrayList与顺序表 的相关文章

随机推荐

  • 继电器、并联的二极管和驱动三极管选型实战演练

    继电器选型原则 继电器的选用原则参见下表 在表中 必须确定 栏中有 号的项目被确定之后 就可选定一款继电器 如果有进一步的要求 需要进一步考虑 参考 栏中有 号的相应项目 下面对表格中的所有参数进行详细说明 触点 1触点负载 确定继电器所能
  • 一篇文章了解爬虫技术现状

    本文全面的分析了爬虫的原理 技术现状 以及目前仍面临的问题 如果你没接触过爬虫 本文很适合你 如果你是一名资深的虫师 那么文末的彩蛋你可能感兴趣 需求 万维网上有着无数的网页 包含着海量的信息 无孔不入 森罗万象 但很多时候 无论出于数据分
  • list【2】模拟实现(含迭代器实现超详解哦)

    模拟实现list 引言 实现概述 list迭代器实现 默认成员函数 operator 与 operator gt operator 与 operator operator 与 operator 迭代器实现概览 list主要接口实现 默认成员
  • pnpm修改设置下载包的存储路径

    要修改 pnpm 存储依赖的路径 可以使用 pnpm 的 store 配置选项 通过更改 store 配置 可以指定 pnpm 存储依赖的目录位置 这在希望将依赖存储在不同磁盘分区 不同的硬盘驱动器或其他自定义位置时很有用 步骤 1 打开命
  • 9.2 流程分析

    介绍了系统文件加密和文件解密的流程 那么我们本例主要涉及两个核心函数个函数Encrypt File和Decrypt File 使用Encrypt File函数完成文件加密功能 Decrypt File函数完成文件解密功能 下面介绍这两个函数
  • 跟着官网编写一个LLVMPass

    官网地址 https llvm org docs WritingAnLLVMPass html introduction what is a pass 一 创建文件 1 项目结构为 llvm project lib Transforms H
  • TscanCode C/C++静态分析开源分析工具安装与使用

    TscanCode是腾讯静态分析团队开发的一款开源免费的C C 静态分析工具 由于其比较简单实用 准确率较高 并且扫描C C 代码不需要进行编译 所以个人觉得对C C 项目开发挺有帮助的 就简单介绍一下该工具的安装与使用 1 Tscanco
  • 文件包含漏洞-日志注入

    文件目录 一 文件包含漏洞 1 文件包含概述 2 文件包含类型 二 文件包含 日志注入 1 日志注入概述 2 环境准备 3 配置环境 4 模拟网站环境 三 日志注入流程 一 文件包含漏洞 1 文件包含概述 文件包含漏洞是 Web 应用程序中
  • springboot的优化

    在SpringBoot的Web项目中 默认采用的是内置Tomcat 当然也可以配置支持内置的jetty 内置有什么好处呢 在SpringBoot的Web项目中 默认采用的是内置Tomcat 当然也可以配置支持内置的jetty 内置有什么好处
  • 互联网JAVA面试常问问题(三)

    一 volatile原理和使用场景 volatile 原理 volatile变量进行写操作时 JVM会向处理器发送一条Lock前缀的指令 将这个变量所在缓存行的数据写会到系统内存 Lock前缀指令实际上相当于一个内存屏障 也成内存栅栏 它确
  • LED用DMX512协议整个系统怎么连接?

    提问1 EIA485规范只支持 雏菊链 或每段上最多以32个 单元负载 所构成的串行网络 DMX512不是可以支持512个通道吗 那是不是说 超过32个的情况下需要使用中继 提问2 控制器 接收端1 接收端2 接收端n 电阻 GND 这样的
  • BIO、NIO、AIO理解

    一 到底什么是BIO NIO AIO 这些可以理解为是Java语言对操作系统的各种IO模型的封装 程序员在使用这些API的时候 不需要关系操作系统层面的知识 也不需要根据不同操作系统编写不同的代码 只需要使用Java的API就可以了 二 B
  • Eclipse搭建stm32+jlink开发环境全攻略(进阶篇一)

    Eclipse搭建stm32 jlink开发环境全攻略 进阶篇 一 本篇开始讲解一些比较实用的东西 在前面的两章中 我们讲解了eclipse开发stm32的大部分问题 然而 在实际使用过程中 我们仍然会遇到一些不太理想的地方 比如 ecli
  • Leetcode力扣题解 - 30.串联所有单词的子串

    地址 30 串联所有单词的子串 力扣 LeetCode 一 思路 本题关键点是 1 所有关键词长度一致 2 匹配的是所有关键词连接起来的 大体思路 那么我们就可以从字符串头开始 每次只匹配关键词总长度个字符 如果匹配成功 在返回的数组中保存
  • HDMI中的视频时序分析

    一 前言 建立层次观念 说到时序 我们首先想到的例子是IIC SPI 串口等接口的例子 以我们之前的理解 时序就是传输线上电平随时间变化的顺序 但是但是但是 在HDMI这里 我们应该建立一个新的观念 即时序不一定对应到物理层 即传输线上 这
  • python--- end=“ , 单独的一行print()是什么意思

    有如下一道练习题 编写代码打印出下列图形 代码如下 for i in range 4 for j in range 5 print end print 其中end 意思是为末尾end传递一个空字符串 这样print函数不会在字符串末尾添加一
  • 工频干扰频谱测量_【鼎阳硬件智库译文

    英文原文 by Mratin Miller 汪进进 译 鼎阳硬件设计与测试智库发起人之一 简介 多通道串行数据链路容易受到串扰的影响 这些串扰可能来自于相邻通道 也可能是外部的干扰源 Aggressor 其结果是增加了受干扰通道 Victi
  • leetcode数组刷题总结与分析

    文章目录 小结 数组中元素的计算 子序列 任意元素 题目一 两数之和 题目15 三数的和 17 四数之和 16 最接近三数之和 167 两数之和 输入有序数组 560 和为k的子数组 523 连续的子数组的和 53 最大子数组和 713 乘
  • Shell脚本到底是什么高大上的技术吗?

    本文介绍shell脚本知识 学习前最好有linux命令知识储备 一篇文章看完 下次找工作时简历上请写上会shell脚本 栓Q shell脚本是什么 shell脚本就是一个包含shell命令的脚本 常说的linux命令 也可以认为是shell
  • ArrayList与顺序表

    目录 编辑 一 线性表 二 顺序表 1 接口的实现 1 打印顺序表 2 新增元素 3 判定是否包含某个元素 4 查找某个元素对应的位置下标 5 获取 pos 位置的元素 6 获取顺序表长度 7 给 pos 位置的元素设为 value 更新的