第五章 ArrayList与顺序表

这篇具有很好参考价值的文章主要介绍了第五章 ArrayList与顺序表。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1. 线性表

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

2. 顺序表

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

2.1 接口的实现

import java.util.Arrays;

public class MyArrayList {

    private int[] elem;
    private int usedSize;

    private static final int initCapacity = 2;

    public MyArrayList() {
        // 指定容量
        this.elem = new int[initCapacity];
    }

    public MyArrayList(int initCapacity) {
        // 指定容量
        this.elem = new int[initCapacity];
    }

    // 打印顺序表,注意:该方法并不是顺序表中的方法,为了方便看测试结果给出的
    public void display() {

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

    // 判断顺序表是否满了
    public boolean isFull() {

        return this.usedSize == this.elem.length;
    }

    // 新增元素,默认在顺序表最后新增
    public void add(int data) {
        // 如果满了,需要扩容
        if (isFull()) {
            this.elem = Arrays.copyOf(this.elem,
                    2 * this.elem.length);
        }
        this.elem[this.usedSize] = data;
        this.usedSize++;
    }

    // 在 pos 位置新增元素
    public void add(int pos, int data) {
        // 判断pos位置是否正确
        if (pos < 0 || pos > this.usedSize) {
            throw new RuntimeException("pos: " + pos + "位置错误!");
//            System.out.println("pos位置错误!");
//            return;
        }

        // 如果满了,需要扩容
        if (isFull()) {
            this.elem = Arrays.copyOf(this.elem,
                    2 * this.elem.length);
        }

        for (int i = this.usedSize - 1; i >= pos; i--) {
            this.elem[i + 1] = this.elem[i];
        }
        this.elem[pos] = data;
        this.usedSize++;
    }

    // 判定是否包含某个元素
    public boolean contains(int toFind) {

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

    // 查找某个元素对应的位置
    public int indexOf(int toFind) {

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

    // 获取 pos 位置的元素
    public int get(int pos) {

        checkPos(pos);
        return this.elem[pos];
    }

    // 给 pos 位置的元素设为 value
    public void set(int pos, int value) {

        checkPos(pos);
        this.elem[pos] = value;
    }

    private void checkPos(int pos) {

        if (pos < 0 || pos >= this.usedSize) {
            throw new RuntimeException(pos + "位置错误!");
        }
    }

    //删除第一次出现的关键字key
    public void remove(int toRemove) {

        int index = indexOf(toRemove);
        if (index == -1) {
            System.out.println(toRemove + "不存在!");
            return;
        }
        for (int i = index; i < this.usedSize - 1; i++) {
            this.elem[i] = this.elem[i + 1];
        }
        this.usedSize--;
    }

    // 获取顺序表长度
    public int size() {

        return this.usedSize;
    }

    // 清空顺序表
    public void clear() {

        this.usedSize = 0;
    }
}
public class PosOutOfBoundsException extends RuntimeException {

    public PosOutOfBoundsException() {

    }

    public PosOutOfBoundsException(String message) {

        super(message);
    }
}

3. ArrayList简介

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

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

4. ArrayList使用

4.1 ArrayList的构造

**方法 ** 解释
ArrayList() 无参构造
ArrayList(Collection<? extends E> c) 利用其他 Collection 构建 ArrayList
ArrayList(int initialCapacity) 指定顺序表初始容量
public static void main(String[] args) {
    // ArrayList创建
    // 构造一个空的列表
    ArrayList<Integer> list = new 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);
}
public static void main(String[] args) {
    List<Integer> list = new ArrayList<>();

    list.add(1);
    list.add(2);
    list.add(3);
    list.add(4);
    list.remove(2);     // 删除下标为2
    list.remove(new Integer(2));    // 删除元素为2

    System.out.println(list);   // [1, 4]
}

4.2 ArrayList常见操作

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

**方法 ** 解释
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 subList(int fromIndex, int toIndex) 截取部分 list
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);   // [JavaSE, JavaWeb, JavaEE, JVM, 测试课程]

    // 获取list中有效元素个数
    System.out.println(list.size());    // 5

    // 获取和设置index位置上的元素,注意index必须介于[0, size)间
    System.out.println(list.get(1));    // JavaWeb
    list.set(1, "JavaWEB");
    System.out.println(list.get(1));    // JavaWEB

    // 在list的index位置插入指定元素,index及后续的元素统一往后搬移一个位置
    list.add(1, "Java数据结构");
    System.out.println(list);   // [JavaSE, Java数据结构, JavaWEB, JavaEE, JVM, 测试课程]

    // 删除指定元素,找到了就删除,该元素之后的元素统一往前搬移一个位置
    list.remove("JVM");
    System.out.println(list);   // [JavaSE, Java数据结构, JavaWEB, JavaEE, 测试课程]

    // 删除list中index位置上的元素,注意index不要超过list中有效元素个数,否则会抛出下标越界异常
    list.remove(list.size()-1);
    System.out.println(list);   // [JavaSE, Java数据结构, JavaWEB, JavaEE]

    // 检测list中是否包含指定元素,包含返回true,否则返回false
    if(list.contains("测试课程")){
        list.add("测试课程");
    }

    // 查找指定元素第一次出现的位置:indexOf从前往后找,lastIndexOf从后往前找
    list.add("JavaSE");
    System.out.println(list);   // [JavaSE, Java数据结构, JavaWEB, JavaEE, JavaSE]
    System.out.println(list.indexOf("JavaSE")); // 0
    System.out.println(list.lastIndexOf("JavaSE")); // 4

    // 使用list中[0, 2)之间的元素构成一个新的SubList返回,但是和ArrayList共用一个elementData数组
    List<String> ret = list.subList(0, 4);
    System.out.println(ret);    // [JavaSE, Java数据结构, JavaWEB, JavaEE]

    ret.set(1, "java data structure");
    System.out.println(ret);    // [JavaSE, java data structure, JavaWEB, JavaEE]

    list.clear();
    System.out.println(list.size());    // 0
}

【注意】:
使用subList, list1和list2指向同一个数组

public static void main(String[] args) {

    List<Integer> list1 = new ArrayList<>();
    list1.add(1);
    list1.add(2);
    list1.add(3);
    list1.add(4);
    list1.add(5);

    List<Integer> list2 = list1.subList(1, 3);  // [0,3)
    System.out.println(list2);  // [2, 3]
    list2.set(0, 99);
    System.out.println(list2);  // [99, 3]

    System.out.println(list1);  // [1, 99, 3, 4, 5]
}

image.png

4.3 ArrayList的遍历

ArrayList 可以使用三方方式遍历:for循环+下标、foreach、使用迭代器:

public static void main(String[] args) {

    List<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(2);
    list.add(3);
    list.add(4);
    list.add(5);
    // 1. 使用下标+for遍历
    for (int i = 0; i < list.size(); i++) {
        System.out.print(list.get(i) + " ");
    }
    System.out.println();
    
    // 2. 借助foreach遍历
    for (Integer integer : list) {
        System.out.print(integer + " ");
    }
    System.out.println();
    
    // 3. 使用迭代器遍历
    Iterator<Integer> it = list.listIterator();
    while (it.hasNext()) {
        System.out.print(it.next() + " ");
    }
    System.out.println();
}

注意:

  1. ArrayList最常使用的遍历方式是:for循环+下标 以及 foreach;
  2. 迭代器是设计模式的一种。

4.4 ArrayList的扩容机制

下面代码有缺陷吗?为什么?

public static void main(String[] args) {
	List<Integer> list = new ArrayList<>();
    
	for (int i = 0; i < 100; i++) {
		list.add(i);
	}
}

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;
}

image.png
总结】:文章来源地址https://www.toymoban.com/news/detail-462363.html

  1. 检测是否真正需要扩容,如果是调用grow准备扩容;
  2. 预估需要库容的大小;
    • 初步预估按照1.5倍大小扩容;
    • 如果用户所需大小超过预估1.5倍大小,则按照用户所需大小扩容;
    • 真正扩容之前检测是否能扩容成功,防止太大导致扩容失败;
  3. 使用copyOf进行扩容。
  4. 当调用不带参数的构造方法时,默认在第一次add时分配大小为10的内存。

到了这里,关于第五章 ArrayList与顺序表的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Linear Probing | 线性探测】深度学习 线性层

    【Linear Probing | 线性探测】深度学习 线性层 自监督模型评测方法 是测试预训练模型性能的一种方法,又称为linear probing evaluation 训练后,要评价模型的好坏,通过将最后的一层替换成线性层。 预训练模型的表征层的特征固定,参数固化后未发生改变,只通过监督数据去训练

    2024年02月15日
    浏览(41)
  • 线性代数——线性方程组和矩阵(Linear and Matrices)

    1.Identify which of the following equations are linear equations: (判断哪些是线性方程) 只有(4)是,一般形式如下 特征:每一项都是一次的,也不代幂什么的 线性方程组(System of linear equations) ai,j是系数(i代表是第几个方程里,j是代表在方程里的第几个),b1是右端项,xj是未

    2023年04月08日
    浏览(43)
  • 机器学习之线性判别分析(Linear Discriminant Analysis)

    线性判别分析(Linear Discriminant Analysis,简称LDA)是一种经典的监督学习算法,也称\\\"Fisher 判别分析\\\"。LDA在模式识别领域(比如人脸识别,舰艇识别等图形图像识别领域)中有非常广泛的应用。 LDA的核心思想是给定训练样本集,设法将样例投影到一条直线上。使得同类样例的

    2024年02月15日
    浏览(38)
  • c# WPF项目XAML 设计界面报错: 已添加具有相同键的项目

    现象如下: VS2017打开 WPF项目,XAML 设计界面不能显示,报错如下: System.ArgumentException 已添加了具有相同键的项。 在 System.ThrowHelper.ThrowArgumentException(ExceptionResource resource) 在 System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add) 在 System.Collections.Generic.Dictionary`2.Ad

    2024年01月18日
    浏览(57)
  • Python第五章作业

    目录 第1关 回文素 第2关 反素数 第3关 哥德巴赫猜想 第4关 货币转换 第5关 个人信息提取 第6关 身份证号基本信息 第7关 各位数字之和为5的数 第8关 字符串长度 第9关 字符串加密 第10关 输出单词 第11关 字符大小写转换 第12关 查找指定字符 第13关 随机密码生成器

    2024年02月04日
    浏览(59)
  • 第五章-课后习题

    5.1 题目:我国1949-2008年每年铁路货运量数据如表5-9所示: 请选择适当的模型拟合该序列,并预测2009-2013年我国铁路货运量。 SAS 程序 data a; input volume@@; year=intnx( \\\"year\\\" , \\\'01jan1949\\\'d ,_n_- 1 ); format year year4. ; cards ; 54167 55196 56300 57482 58796 60266 61465 62828 64653 65994 67207 66207 65859 67295 69

    2024年02月04日
    浏览(39)
  • 【OpenCV】第五章: 几何变换

    第五章: 几何变换 1、什么是图像的几何变换? 图像的几何变换就是将一组图像数据经过某种数学运算,映射成另外一组图像数据的操作。所以, 几何变换的关键就是要确定这种空间映射关系。 几何变换又称空间变换。对于图像数据来说,就是将一幅图像中的坐标位置映射到

    2024年02月03日
    浏览(38)
  • 第五章 函数和代码复用

    5.1 函数的基本使用 函数是一段具有特定功能的、可重用的语句组,通过函数名来表示和调用。经过定义,一组语句等价于一个函数,在需要使用这组语句的地方,直接调用函数名称即可。因此,函数的使用包括两部分: 函数的定义 和 函数的使用 。 使用函数主要有两个目的

    2024年02月06日
    浏览(38)
  • linux第五章(系统维护)

    一。进程管理命令 1.ps命令:显示进程信息 2.pstree命令:以树状形式进行展示 3.top命令:查看进程信息 4.kill命令:结束某个进程 二。磁盘管理 1.lsblk:对磁盘的使用情况进行查看 2.df:查看占系统的多少数据 3.du:查看文件或者目录在磁盘的占用量,不是真实大小 4.fdisk:查看磁

    2024年01月19日
    浏览(45)
  • 第五章 结构化设计

    一种软件开发活动,定义实现需求规约所需的软件结构。 结构化设计分为: (1)总体设计:确定系统的整体模块结构,即系统实现所需要的软件模块以及这些模块之间的调用关系。 (2)详细设计:详细描述模块。 体系结构设计(MSD) 接口设计 数据设计 实现软件设计的目标对结

    2024年02月08日
    浏览(60)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包