当前位置: 首页 > news >正文

ArrayList知识点(面试)

        上一篇我们说了hashmap的相关知识点,这一篇我们再说一些ArrayList的相关知识,因为ArrayList也是我们项目中比较常用的。

ArrayList(底层是数组)

底层工作原理
  • 首先,在构造ArrayList的时候会先看有没有指定容量,如果没有,则会先构造一个空数组,如果有,则会根据指定容量创建数组。

  • 在插入数据的时候,会先判断当前数组是否有足够的空间插入新数据,如果没有则会按照1.5倍的扩容标准进行扩容,同时如果容量足够,或再判断插入的位置有没有越界,以及该位置是否有元素

  • 在获取元素的时候同样也是先判断当前下标是否有越界,没有才能从数组中获取元素。

扩容机制
  1. 首先,ArrayList初始化的长度为10,如果插入的数据长度超过10则会自动触发扩容机制

  2. ArrayList会创建一个比原来数组长1.5倍的新数组,然后通过Arrays.copyof方法将原来的数组copy到新数组中

  3. 最后将新元素插入到新数组中

线程不安全是怎么解决的

先看一段代码

 @Testvoid testArrayList() {List<Integer> list = new ArrayList<>(12);list.add(1);list.add(2);list.add(3);System.out.println(list);//        遍历元素Thread t1 = new Thread(() -> {for (Integer integer : list) {System.out.println(integer);}});//              新增元素Thread t2 = new Thread(() -> {for (int i = 4; i < 8; i++) {list.add(i);}});t1.start();t2.start();}

以上代码运行后结果如下:

ArrayList在遍历的时候会去调用Collection中的next方法,然后通过checkForComodification()方法检查modCount和expectedModeCount是否相等,若不等则抛出ConcurrentModificationException,在上面的代码中我们对于list在遍历的同时也在增加数组中对的元素,modCount和expectedModeCount肯定不相等啦,所以就报错了。

modCount表示集合被修改的次数,每修改一次次数加1,而expectedModeCount是迭代器的属性,在迭代器创建时被赋予和modCount一样的值

其实在这里也是用到了fail-fast(快速失效)系统,这里简单说明一下这个是什么

fail-fast和fail-safe是什么意思?

fail-fast简单通俗的来讲就是在做系统设计的时候考虑异常情况,如果有异常情况,则抛出异常并且不会执行后面的代码

 public int divide(int dividend,int divisor){if(divisor == 0){throw new RuntimeException("divisor can't be zero");}return dividend/divisor;}

        以上的代码中,如果divisor为0,我们就会抛出一个异常并终止程序继续运行下面的代码,这样做的好处是一方面可以停止运行复杂的代码,另一方面,可以将一些异常单独进行处理。

        fail-safe就是为了避免触发fail-fast机制引发出来的异常,用java中一些带有fail-safe机制的集合类来控制。

        这样的集容器在遍历的时候会先拷贝一份出来而不是直接在原集合上进行操作,java.util.concurrent下的包都是fail-safe机制的,可以在多线程下进行并发修改(remove/add),就比如下面要说到的CopyOnWriteArrayList也是这种机制

        但是,通过这样的方式虽然是可以防止ConcurrentModificationException异常的,但是不能通过迭代器遍历元素,即元素中的元素如果发生改变,迭代器是不知道的,迭代器拿到的只是刚开始集合中的元素。

所以这个时候我们可以对其进行加锁

使用synchronized进行加锁

@Testvoid testArrayList() {List<Integer> list = new ArrayList<>(12);list.add(1);list.add(2);list.add(3);System.out.println(list);//        遍历元素Thread t1 = new Thread(() -> {synchronized (list){for (Integer integer : list) {System.out.println(integer);}}​});//              新增元素Thread t2 = new Thread(() -> {synchronized (list) {for (int i = 4; i < 8; i++) {list.add(i);}}});t1.start();t2.start();}

使用ReentrantLock进行加锁

 @Testvoid testArrayList() {List<Integer> list =new ArrayList<>(12);list.add(1);list.add(2);list.add(3);System.out.println(list);Lock lock=new ReentrantLock();// 遍历元素Thread t1 = new Thread(() -> {//            加锁lock.lock();for (Integer integer : list) {System.out.println(integer);}//                释放锁lock.unlock();});// 新增元素Thread t2 = new Thread(() -> {//            加锁lock.lock();for (int i = 4; i < 8; i++) {list.add(i);}//            释放锁lock.unlock();});t1.start();t2.start();}

        或者可以使用Collections.synchronizedxxxx来解决,把创建list的那行代码改成List<Integer> list = Collections.synchronizedList(new ArrayList<>())就行了

或者使用 CopyOnWriteArrayList来实现

  @Testvoid testArrayList() {List<Integer> list = new CopyOnWriteArrayList<>();list.add(1);list.add(2);list.add(3);System.out.println(list);// 遍历元素Thread t1 = new Thread(() -> {for (Integer integer : list) {System.out.println(integer);}});// 新增元素Thread t2 = new Thread(() -> {for (int i = 4; i < 8; i++) {list.add(i);}});t1.start();t2.start();}

这里扩展一下什么是Copy-On-Write?

        简称COW,其基本思路是一开始大家共享一个内容,后来如果有人来修改这个资源,这时就会先copy一份出来(延时懒惰策略),在这份中进行修改,修改完成后再把原来的容器指向这份新的容器。

序列化是怎么解决的

        在序列化中,如果被序列化的类中定义了readObject和writeObject方法,则虚拟机就会尝试去通过用户自定义的这两个方法进行序列化,对于自定义的序列化方法好处是可以在序列化的过程中动态修改序列化的值

        如果类中没有定义,则会用ObjectOutPutStream中默认的DefaultReadStream和DefaultWriteStream方法。

好了,现在再来说一下面试中最常被问到的内容

ArrayList和LinkList有什么区别?
  • 从地址上来说,ArrayList底层是一个数组,因此对于数组这种数据结构的话,它的地址是连续的,而对于LinkList来说,它的底层是一个循环双向链表的结构,地址就不是连续的。

  • 从操作上来讲,ArrayList在查询元素方面快于LinkList,且时间复杂度可以达到O(1),而LinkList的时间复杂度达到了O(n);但是在修改元素和增加元素上来讲,ArrayList时间复杂度达到了O(n),LinkList时间复杂度为O(1).

相关文章:

  • C语言入门系列:数据类型转换
  • 【深度学习】实现基于MNIST数据集的TensorFlow/Keras深度学习案例
  • Vue-内容渲染,属性渲染指令
  • 【深度学习】GPT-3,Language Models are Few-Shot Learners(一)
  • ShareX,屏幕截图、屏幕录制和文件共享,还提供了丰富的高级功能和自定义选项
  • 建造者模式(大话设计模式)C/C++版本
  • 35.简易远程数据框架的实现
  • Leetcode85
  • 软件测试笔记
  • IPv6 中 MAC 33:33 的由来
  • VisualBox 虚拟机 Ubunut 18.04 在大显示器上黑屏的问题
  • 【LinuxC语言】网络编程的本质
  • 动态ARP
  • TCP 协议详解:三次握手与四次挥手
  • 一篇快速教你如何创建专业级数据可视化库
  • 002-读书笔记-JavaScript高级程序设计 在HTML中使用JavaScript
  • 8年软件测试工程师感悟——写给还在迷茫中的朋友
  • Bootstrap JS插件Alert源码分析
  • C++入门教程(10):for 语句
  • Java编程基础24——递归练习
  • java多线程
  • Java多线程(4):使用线程池执行定时任务
  • Java反射-动态类加载和重新加载
  • java架构面试锦集:开源框架+并发+数据结构+大企必备面试题
  • MySQL QA
  • MYSQL如何对数据进行自动化升级--以如果某数据表存在并且某字段不存在时则执行更新操作为例...
  • Python连接Oracle
  • springMvc学习笔记(2)
  • Unix命令
  • vue-cli在webpack的配置文件探究
  • 多线程 start 和 run 方法到底有什么区别?
  • 构造函数(constructor)与原型链(prototype)关系
  • 经典排序算法及其 Java 实现
  • 嵌入式文件系统
  • 如何实现 font-size 的响应式
  • 为视图添加丝滑的水波纹
  • 好程序员大数据教程Hadoop全分布安装(非HA)
  • 通过调用文摘列表API获取文摘
  • ​​​​​​​​​​​​​​汽车网络信息安全分析方法论
  • ​​快速排序(四)——挖坑法,前后指针法与非递归
  • ​DB-Engines 11月数据库排名:PostgreSQL坐稳同期涨幅榜冠军宝座
  • # .NET Framework中使用命名管道进行进程间通信
  • (20)目标检测算法之YOLOv5计算预选框、详解anchor计算
  • (C++)栈的链式存储结构(出栈、入栈、判空、遍历、销毁)(数据结构与算法)
  • (cos^2 X)的定积分,求积分 ∫sin^2(x) dx
  • (done) 声音信号处理基础知识(2) (重点知识:pitch)(Sound Waveforms)
  • (Java入门)学生管理系统
  • (TOJ2804)Even? Odd?
  • (黑马点评)二、短信登录功能实现
  • (每日持续更新)信息系统项目管理(第四版)(高级项目管理)考试重点整理 第13章 项目资源管理(七)
  • (文章复现)基于主从博弈的售电商多元零售套餐设计与多级市场购电策略
  • (续)使用Django搭建一个完整的项目(Centos7+Nginx)
  • (原創) 如何刪除Windows Live Writer留在本機的文章? (Web) (Windows Live Writer)
  • (转)PlayerPrefs在Windows下存到哪里去了?
  • (转)Sublime Text3配置Lua运行环境