Java实现动态数组【数据结构与算法】

1、数组

类型固定、长度固定

连续的内存空间

顺序存储、随机读取

查询快、新增删除慢。 最好初始化的时候就指定数组大小。这样就可以避免一定的数组扩容出现的内存消耗。

import java.util.Arrays;
import java.util.Iterator;

/**
 * @author Administrator
 * @date 2022-09-11 16:56
 * 实现一个数组
 */
public class MyArray implements Iterable {

    private Object[] elementData;   // Object存放数据

    public MyArray(int capacity)    // 构造方法 初始化容量大小
    {
        // 指定长度 初始化数组 new 出一块空间
        elementData = new Object[capacity];
    }

    /**
     * 直接添加新元素
     * @param element
     * @return
     */
    public boolean add (E element)
    {
        int size = elementData.length;  // 获取当前数组大小
        int newCapacity = size+1;   // 扩容+1
        // 此处发生性能消耗,新增数据时,需要扩容,整体数据需要复制迁移,实际上arraylist是1.5扩容!
        elementData = Arrays.copyOf(elementData,newCapacity); // 把旧的空间复制一份到新的空间并+1
        elementData[size]=element;
        return true;
    }

    /**
     * set 方法 根据索引位置新增元素
     * @param index
     * @param element
     * @return
     */
    public E set (int index ,E element)
    {
        E oldValue = (E) elementData[index];    // 获取旧位置的元素值
        elementData[index] = element;           // 新值覆盖旧值
        return oldValue;          // 返回旧值

    }
    public E get (int index)
    {
        return (E) elementData[index]; // 返回对应索引位置的值
    }

    @Override
    public Iterator iterator(){
        return new MyIterator();
    }

    class MyIterator implements Iterator{
        int index = 0;
        @Override
        public boolean hasNext() {
            return index != elementData.length;
        }

        @Override
        public E next() {
            return (E) elementData[index++];    // 返回下一个元素值并+1
        }

        @Override
        public void remove() {
            //
        }
    }

    public static void main(String[] args) {
        MyArray myArray= new MyArray(10);   // 初始化一个容量为10的数组
        myArray.set(0,"q");
        myArray.set(2,"w");
        myArray.add("新增");
        Iterator iterator = myArray.iterator();     // 使用迭代器
        while (iterator.hasNext()){
            System.out.println(iterator.next());
        }
    }
}

Java实现动态数组【数据结构与算法】

1.1、关于arraylist初始容量和扩容

ArrayList 新增元素的方法有两种,一种是直接将元素加到数组的末尾,另外一种是添加元素到任意位置。

arraylist默认构造器,在不指定大小的时候默认容量为 10。

在超出容量之后,每次扩容为当前容量大小的1.5倍+1。

1.2、关于迭代器

集合的顶层接口 Collection继承 Iterable接口。在 Iterable接口中有一个 Iterator方法,它返回一个 Itertator对象

public interface Iterable {
    /**
     * Returns an iterator over elements of type {@code T}.

     *
     * @return an Iterator.

     */
    Iterator iterator();
}
public interface Iterator {
    boolean hasNext();

    E next();

    default void remove() {
        throw new UnsupportedOperationException("remove");
    }
}

迭代器遍历中 调用集合revome()方法触发异常 java.util.ConcurrentModificationException 集合中并发修改的异常.

因为迭代器只负责遍历,它使用的仍然是集合本身的数据,在List集合实现的时候 数组的长度size会因为remove发生变化的,同时元素的索引值也会因为remove( )方法的调用而发生变化。那么在遍历的时候的remove就需要对这个点进行复刻,而且如果在迭代器里使用了List原生的remove方法,那么就会引起数值不同步的问题。

ArrayList集合的 iterator()方法中,是通过返回 Itr对象来获得迭代器的。 ItrArrayList的一个内部类,它实现了 Iterator接口, 代码如下:

   private class Itr implements Iterator {
        int cursor;       // index of next element to return
        int lastRet = -1; // index of last element returned; -1 if no such
        int expectedModCount = modCount;

        Itr() {}

        public boolean hasNext() {
            return cursor != size;
        }

        @SuppressWarnings("unchecked")
        public E next() {
            checkForComodification();
            int i = cursor;
            if (i >= size)
                throw new NoSuchElementException();
            Object[] elementData = ArrayList.this.elementData;
            if (i >= elementData.length)
                throw new ConcurrentModificationException();
            cursor = i + 1;
            return (E) elementData[lastRet = i];
        }

        public void remove() {
            if (lastRet < 0)
                throw new IllegalStateException();
            checkForComodification();

            try {
                ArrayList.this.remove(lastRet);
                cursor = lastRet;
                lastRet = -1;
                expectedModCount = modCount;
            } catch (IndexOutOfBoundsException ex) {
                throw new ConcurrentModificationException();
            }
        }

注意以下的三个属性:

cursor 索引下标,表示下一个可以访问的元素的索引,默认值为 0 lastRet 索引下标,表示上一个元素的索引,默认值为
-1

expectedModCount 对集合修改的次数,初始值为
0

我们知道: List的add和remove调用会增加modCount的值。也就是这两个操作会被计入对集合的修改次数。

在迭代器的源码中,有一个方法是用来判断 modCount 和 expectModCount 的值是否相等的,其中modCount的值来自List,expectModCount 是迭代器内定义的变量。那为什么要这么设计呢?

因为arraylist是线程不安全的。

结合iterrator的next方法,我们可以看到, 如果没有这个校验某个线程删除了list的一个元素,此时next方法不知道size变更了,依然去取数组里的数据,会导致数据为null或ArrayIndexOutOfBoundsException异常等问题。

ConcurrentModificationException发生在Iterator( )和next( )方法实现中,每次调用都会检查容器的结构是否发生变化,目的是为了避免共享资源而引发的潜在问题。
观察HashMap和ArrayList底层Iterator#next(), 可以看到fast-fail只会增加或者删除(非Iterator#remove())抛出异常;改变容器中元素的内容不存在这个问题(主要是modCount没发生变化)。
在单线程中使用迭代器,对非线程安全的容器,但是只能用Iterator和remove;否则会抛出异常。
在多线程中使用迭代器,可以使用线程安全的容器来避免异常。
使用普通的for循环遍历,效率虽然比较低下,但是不存在ConcurrentModificationException异常问题,用的也比较少。

所以说如果在使用迭代器的时候,用到了List自带的remove方法,那么modCount改变了,但是迭代器内定义的变量expectedCount却没有改变,这样就会被抛出异常。

综上:我们在使用迭代器的时候,不要混用List本身的remove方法。

Iterator接口有四个方法,hasNext、next、remove和forEachRemaining
其中forEachRemaining是java1.8新增的
这个方法是针对集合中剩余元素的操作
剩余的含义是没有被iterator.next()遍历过的元素

1.3、为什么迭代器在调用remove之前要先调用next

当使用Iterator迭代访问Collection集合元素时,Collection集合里的元素不能被改变, 只有通过Iterator的remove()方法删除上一次next()方法返回的集合元素才可以;否则会引发java.util.ConcurrentModificationException异常。

查看next方法的源码可以看到 return (E) elementData[lastRet = i];这样一行代码,这行代码表示next方法在让数组下标cursor向后移动一位的同时,还会把lastRet的值变成当前返回的元素下标,这样remove方法就可以根据这个下标完成对元素的删除。

Original: https://www.cnblogs.com/rainbow-1/p/16690478.html
Author: 靠谱杨
Title: Java实现动态数组【数据结构与算法】

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/611563/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • redis研究记录

    redis研究记录 1 概述目前多数的NoSql数据库本质上都是键值对形式,Redis也不例外。作为缓存数据库的一种,和Memcached相比,有以下几种主要的优点:(1)速度上,…

    Linux 2023年5月28日
    068
  • EmuELEC 4.3 安装和乐视手柄 LeWGP-201 evremap问题解决

    一年多前安装了EmuELEC3.9之后, 就一直没有再更新过, 平时玩玩小游戏也很正常. 昨天心血来潮想把吃灰的乐视手柄用起来, 结果发现3.9里面没有evremap 命令. 猜测…

    Linux 2023年5月27日
    0227
  • Wine 运行百度云盘 中文乱码解决;wine中文乱码解决;fedora 34 运行百度网盘;

    今天需要下个 imagenet 的 ILSVRC2012 数据集,找到了网友在百度网盘中分享的下载好的; 但是因为本人使用的是 fedora 34 系统,所以尝试下载 百度网盘 l…

    Linux 2023年6月14日
    093
  • Log4j 2 日志框架

    Apache Log4j 2 是对 Log4j 的升级,它比其前身 Log4j 1.x 提供了显着改进,并提供了 Logback 中可用的许多改进,同时修复了 Logback 架构…

    Linux 2023年6月8日
    079
  • WPF 已知问题 资源字典树引用与资源寻找的坑

    大家都知道,在 WPF 里面,可以让资源字典合并其他资源字典,从而定义出资源字典引用树。然而在资源字典引用树里面,如果没有理清关系,将可以作出一个超级复杂的引用关系网。如果在性能优…

    Linux 2023年6月6日
    094
  • MariaDB 安装和配置

    一、MariaDB数据库管理系统是MySQL的一个分支,主要由开源社区在维护,采用GPL授权许可。 1、关闭selinux ①修改selinux的配置文件 [root@localh…

    Linux 2023年6月7日
    087
  • 【演讲】2020年马云谈疫情过后的新风口

    2020年马云谈疫情过后的新风口 【关键词】:疫情、新风口、数字化趋势、传统行业转型、教育 一、演讲背景 背景 2020线上智博会,马云8分钟演讲30次提到数字化 原视频 2020…

    Linux 2023年6月13日
    0102
  • 记录XorDDos木马清理步骤

    1.检查 查看定时任务文件发现有两个异常定时任务 [root@manage ~]# cat /etc/crontab user-name command to be execute…

    Linux 2023年6月7日
    086
  • 保罗·艾伦的故事

    上周,保罗·艾伦逝世。《财新周刊》约我写一篇纪念文章,发表在他们杂志上面 一些个人新闻:最近,我了解到我在2009年与之抗争的非霍奇金淋巴瘤已经复发。我已经开始治疗,我的医生很乐观…

    Linux 2023年6月14日
    092
  • 自动化服务器巡检的实现过程

    由于上级的工作安排,从今年5月开始,每天都要做一些服务器信息检查。 [En] Due to the work arrangement of the superior, it is …

    Linux 2023年5月27日
    085
  • Linux快速安装流量监控工具(实用版)

    前言: Linux流量监控工具,在此我推荐两种分别为: 1、nload(推荐)因为个人看着舒服点😂 2、iftop 你可以选择上面两种中的任何一种。下面是这两个版本的简介和安装教程…

    Linux 2023年5月27日
    076
  • 数据结构 栈与队列

    cpp;gutter:true;</p> <h1>include</h1> <h1>include</h1> <h…

    Linux 2023年6月13日
    076
  • 聊聊.netcore采坑那一些事之系统时间and文件路径

    聊聊 .netcore 采坑那一些事之系统时间and 文件路径 Hi,小伙伴大家好,最近工作比较忙,很久没有和大家分享点东西了。这个周末都加了两天班。公司的新项目都是采用.netc…

    Linux 2023年6月14日
    074
  • CNN卷积神经网络的构建

    1.卷积神经网络由输入层,卷积层,激活函数,池化层,全连接层组成. input(输入层)–conv(卷积层)–relu(激活函数)–pool(池…

    Linux 2023年6月6日
    079
  • spring boot设置日志打印为控制台输出和文件输出

    日志打印 sources里建 logback-spring.xml ${CONSOLE_LOG_PATTERN} ${CONSOLE_LOG_CHARSET} ${FILE_LOG…

    Linux 2023年6月7日
    0103
  • Java面向对象之各种变量详解

    在Java中一定有很多变量让大家头疼,成员变量、类变量、局部变量等等,今天就来分别认识认识他们吧! Java面向对象之各种变量详解 前言 在 Java语言中, 根据定义变量位置的不…

    Linux 2023年6月13日
    071
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球