Java集合:关于 ArrayList 的内容盘点

本篇内容包括:ArrayList 概述、ArrayList 的扩容机制(包含源码部分)、如何在遍历 ArrayList 时正确的移除一个元素、ArrayList 的构造方法及常用方法、关于 Array 与 ArrayList 的区别、关于 CopyOnWriteArrayList、关于 Fail Fast 与 Fail Safe 机制!


文章目录

    • 一、ArrayList 概述
    • 二、ArrayList 的扩容
        • 1、ArrayList 的扩容机制(源码)
        • 2、在遍历 ArrayList 时移除一个元素
    • 三、ArrayList 的使用
        • 1、构造方法
        • 2、常用方法
    • 四、相关知识点
        • 1、关于 Array 与 ArrayList 的区别
        • 2、关于 CopyOnWriteArrayList
        • 3、关于 Fail Fast
        • 4、关于 Fail Safe


一、ArrayList 概述

ArrayList 是最常用的 List 实现类,内部是通过数组实现的,它允许对元素进行快速随机访问。数组的缺点是每个元素之间不能有间隔,当数组大小不满足时需要增加存储能力,就要将已经有数组的数据复制到新的存储空间中。当从 ArrayList 的中间位置插入或者删除元素时,需要对数组进行复制、移动、代价比较高。因此,它适合随机查找和遍历,不适合插入和删除。

ArrayList 是基于数组实现的,相当于动态数组,相当于动态数组,其容量能动态增长,类似于 C 语言中的动态申请内存,动态增长内存。

ArrayList 的每个实例都有一个容量,该容量是指用来存储列表元素的数组的大小。它总是大于等于列表的大小。随着向 ArrayList 中不断添加元素,其容量也自动增长。自动增长会带来数据向新数组的重新拷贝,因此,如果可预知数据量的多少,可在构造 ArrayList 时指定其容量。

ArrayList 在被添加大量元素前,应用程序可以使用 ensureCapacity() 操作来指定 ArrayList 实例的容量,这可以减少递增式再分配的数量。

ArrayList 是非线程安全的,只能在单线程环境下使用,多线程环境下可以考虑用 Collections.synchronizedList(List l) 函数返回一个线程安全的 ArrayList 类,也可以使用 java.util.concurrent 并发包下的 CopyOnWriteArrayList 类。


二、ArrayList 的扩容

1、ArrayList 的扩容机制(源码)

ArrayList 底层是一个 Object 数组 elementData,用于存放插入的数据:

private transient Object[] elementData;		// 存储ArrayList中的元素
/*** 定义元素个数*/
private int size();

我们知道,数组需要使用着一块连续的内存空间,因此数组的大小一旦被规定就无法改变。那如果我们不断的往里面添加数据的话,ArrayList 是如何进行扩容的呢 ?

public boolean add(E e) {// 确认elementData容量是否足够ensureCapacityInternal(size + 1);  // 第一次调用add()方法时,size=0elementData[size++] = e;return true;}

ArrayList 添加元素时会先调用 ensureCapacityInternal(int minCapacity) 方法,对数组容量进行检查,判断剩余空间是否足够,不够时则进行扩容

private void ensureCapacityInternal(int minCapacity) {// 如果elementData为"{}"即第一次调用add(E e),重新定义minCapacity的值,赋值为DEFAULT_CAPACITY=10// 即第一次调用add(E e)方法时,定义底层数组elementData的长度为10if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);}// 判断是否需要扩容ensureExplicitCapacity(minCapacity);
}
private void ensureExplicitCapacity(int minCapacity) {modCount++;// 第一次进入时,minCapacity=10,elementData.length=0,对数组进行扩容// 之后再进入时,minCapacity=size+1,elementData.length=10(每次扩容后会改变),// 需要minCapacity>elementData.length成立,才能扩容if (minCapacity - elementData.length > 0){grow(minCapacity);}
}

ArrayList 通过 grow(minCapacity) 方法对数组进行扩容

private void grow(int minCapacity) {// 将数组长度赋值给oldCapacityint oldCapacity = elementData.length;// 将oldCapacity右移一位再加上oldCapacity,即相当于newCapacity=1.5oldCapacity(不考虑精度损失)int newCapacity = oldCapacity + (oldCapacity >> 1);// 如果newCapacity还是小于minCapacity,直接将minCapacity赋值给newCapacityif (newCapacity - minCapacity < 0)newCapacity = minCapacity;// 特殊情况:newCapacity的值过大,直接将整型最大值赋给newCapacity,// 即newCapacity=Integer.MAX_VALUEif (newCapacity - MAX_ARRAY_SIZE > 0)newCapacity = hugeCapacity(minCapacity);// 将elementData的数据拷贝到扩容后的数组elementData = Arrays.copyOf(elementData, newCapacity);
}// 如果大于临界值,进行整型最大值的分配
private static int hugeCapacity(int minCapacity) {if (minCapacity < 0) {// overflowthrow new OutOfMemoryError();}return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}

总结:ArrayList 在添加元素时,会进行一个判断,当「元素个数+1> 当前数组长度(size + 1 > elementData.length)」时,进行扩容,扩容后的数组大小是原大小的 1.5 倍(oldCapacity + (oldCapacity >> 1))。最后将旧数组进行复制(调用 Arrays.copyof(),再调用 System.arraycopy() ),达到扩容的目的,此时新旧列表的 size 大小相同,但 elementData 的长度即容量不同。

2、在遍历 ArrayList 时移除一个元素

在遍历 ArrayList 时移除一个元素,这是一个比较经典的面试题,这里最常用的有 2 种方式:

方式一:在 for 循环中使用倒序遍历 remove 删除元素.

假设按照从 0size-1 下标来删有相邻且相同的两个元素,删除第一个,数组长度会 -1 并且所有元素往前移动一位,那么第二个就到第一个元素的位置,此时控值 for 循环的下标 i 已经 +1 ,相当于直接就跳过了第二个重复元素,而倒叙可以避免此类情况。

方式二:使用迭代器遍历 ArrayList 并删除元素(推荐)。

Eg:

List<String> strs = new ArrayList<>();
strs.add("1")
strs.add("2")
strs.add("3")
strs.add("4")
strs.add("5")
strs.add("6")Iterator<String> iter = strs.iterator();
while(iter.hasNext()) {if (iter.next().toString().equals("1")) {iter.remove();}
}
System.out.println(strs);

三、ArrayList 的使用

1、构造方法

方法名方法说明
public ArrayList()无参构造函数,此构造函数用于创建一个空列表,其初始容量足以容纳10个元素
public ArrayList(int initialCapacity)此构造函数用于创建具有初始容量的空列表
public ArrayList(Collection<? extends E> c)此构造函数用于创建包含指定集合的元素的列表

2、常用方法

方法名方法说明
boolean add(E e)此方法将指定的元素追加到此列表末尾
void add(int index, E element)此方法将指定的元素插入此列表中的指定位置
boolean addAll(Collection<? extends E> c)此方法按指定集合迭代器的返回顺序将指定集合中所有元素加到列表末尾
boolean addAll(int index, Collection<? extends E> c)此方法从指定位置开始将指定集合中的所有元素插入此列表
E get(int index)此方法返回此列表中指定位置的元素
E set(int index, E element)此方法返回此列表中指定位置的元素,并使用参数中的元素进行替换
E remove(int index)此方法返回此列表中指定位置的元素,并删除此指定位置的元素
boolean remove(Object o)此方法从该列表中删除指定元素的第一个匹配项(如果存在)
void clear()此方法将从此列表中删除所有元素
Object clone()此方法返回此ArrayList实例的浅表副本
boolean contains(Object o)如果此列表包含指定的元素,则此方法返回true
boolean isEmpty()如果此列表为空,则此方法返回true
void ensureCapacity(int minCapacity)此方法增加了此列表的容量
int size()此方法返回此列表中的元素数
Object[] toArray()此方法以适当的顺序(从第一个元素到最后一个元素)返回包含此列表中所有元素的数组
<T> T[] toArray(T[] a)此方法以适当的顺序(从第一个元素到最后一个元素)返回包含此列表中所有元素的数组; 返回数组的运行时类型是指定数组的运行时类型
void trimToSize()此方法将此ArrayList实例的容量修剪为列表的当前大小
void sort(Comparator<? super E> c)此方法对列表内对象,以指定方式进行排序
List<E> subList(int fromIndex, int toIndex)此方法将截取集合的一部分并返回一个List集合

四、相关知识点

1、关于 Array 与 ArrayList 的区别

  • (包含类型)Array 既可以包含基本类型,也可以包含对象类型;而 ArrayList 只能包含对象类型。
  • (实例声明)Array 作为变量在声明的时必须进行实例化(至少得初始化数组的大小),而 ArrayList 可以只是先声明。
  • (初始大小)Array 对象创建后的数组大小是固定的,而 ArrayList 的大小可以动态指定,也就是说该对象的空间可以任意增加。
  • (方法特性)Arraylist 提供了更多的方法和特性,比如添加全部addAll(),删除全部removeAll(),返回迭代器iterator()等等。

2、关于 CopyOnWriteArrayList

Java 并发包中的并发 List 只有 CopyOnWriteArrayList。CopyOnWriteArrayList 是一个线程安全的 ArrayList,对其进行的修改操作都是在底层的一个复制数组(快照)上进行的,也就是使用了写时复制策略。

写时复制(CopyOnWrite,简称 COW)思想是计算机程序涉及领域中的一种优化策略。其核心思想是,如果多个调用者(Callers)同时要求相同的资源(如内存或者磁盘上的数据存储),他们会共同获取相同的指针指向相同的资源,直到某个调用者视图修改资源内容时,系统才会真正复制一份专用的副本给调用者,而其他调用者所见到的最初的资源仍然保持不变。这个过程对其他调用者都是透明的。样做的好处就是可以对 CopyOnWrite 容器进行并发的读而不需要加锁,因为当前容器不会被修改。

从 Jdk1.5 开始 Java 并发包里提供了两个使用 CopyOnWrite 机制实现的并发容器,它们是 CopyOnWriteArrayList 和 CopyOnWriteArraySet。

CopyOnWriteArrayList 中 add 方法添加的时候是需要加锁的,保证同步,避免了多线程写的时候复制出多个副本。读的时候不需要加锁,如果读的时候有其他线程正在向 CopyOnWriteArrayList 添加数据,还是可以读到旧的数据。

写时复制的缺点:

  • 内存占用问题。由于 CopyOnWrite 的写时复制机制,在进行写操作的时候,内存里会同时驻扎两个对象的内存。
  • CopyOnWrite 容器不能保证数据的实时一致性,可能读取到旧数据。

3、关于 Fail Fast

Fail Fast 是 Java 集合的一种错误机制。当多个线程对同一个集合进行操作时,就有可能会产生 fast-fail 事件。例如:当线程 A 正通过 iterator 遍历集合,另一个线程 B 修改了集合的内容,此时 modCount(记录集合操作过程的修改次数)会加 1,不等于 expectedModCount,那么线程 A 访问集合的时候,就会抛出 Concurrent Modification Exception,产生 fast-fail 事件。边遍历边修改集合也会产生 fast-fail 事件。

解决方法:

  • 使用 Colletions.synchronizedList 方法或在修改集合内容的地方加上 synchronized。这样的话,增删集合内容的同步锁会阻塞遍历操作,缺点是会影响性能。
  • 使用 CopyOnWriteArrayList 来替换 ArrayList。在对 CopyOnWriteArrayList 进行修改操作的时候,会拷贝一个新的数组,对新的数组进行操作,操作完成后再把引用移到新的数组。

4、关于 Fail Safe

Fail Safe 也是 Java 集合的一种机制,采用安全失败机制的集合容器(Eg:CopyOnWriteArrayList)在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。

  • 原理:由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发 Concurrent Modification Exception
  • 缺点:基于拷贝内容的优点是避免了 Concurrent Modification Exception,但同样地,迭代器并不能访问到修改后的内容,即:迭代器遍历的是开始遍历那一刻拿到的集合拷贝,在遍历期间原集合发生的修改迭代器是不知道的。

Ps:java.util.concurrent 包下的容器都是 Fail Safe 的,可以在多线程下并发使用,并发修改。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/535534.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java集合:关于 LinkedList 的内容盘点

本篇内容包括&#xff1a;LinkedList 的概述、LinkedList 的结构既双向链表实现与LinkedList-Node 结构、LinkedList 的使用&#xff08;构造方法&常用方法&#xff09;、关于 Queue 队列的介绍、关于 ArrayList 和 LinkedList 的区别以及算法&#xff1a;翻转链表&#xf…

shell自动化巡检

#!/bin/bash #主机信息每日巡检IPADDR$(ifconfig eth0|grep inet addr|awk -F [ :] {print $13}) #环境变量PATH没设好&#xff0c;在cron里执行时有很多命令会找不到 export PATH/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/root/bin source /etc/profile…

Java集合:关于 Vector 的内容盘点

Vector 与 ArrayList 一样&#xff0c;也是通过数组实现的&#xff0c;不同的是它支持线程的同步&#xff0c;即某一时刻只有一个线程能够写 Vector&#xff0c;避免多线程同时写而引起的不一致性&#xff0c;但实现同步需要很高的花费&#xff0c;因此&#xff0c;访问它比访问…

memcached 的基本命令

memcached 的基本命令(安装、卸载、启动、配置相关)&#xff1a; -p 监听的端口 -l 连接的 IP 地址, 默认是本机 -d start 启动 memcached 服务 -d restart 重起 memcached 服务 -d stop|shutdown 关闭正在运行的 memcached 服务 -d install 安装 memcached 服务 -d uninstall …

Java集合:关于 HashSet 的内容盘点

哈希表存放的是哈希值&#xff0c; HashSet 存储元素的顺序并不是按照存入时的顺序&#xff08;和 List 显然不同&#xff09; 而是按照哈希值来存的所以取数据也是按照哈希值取得。 &#xff5e; 本篇内容包括&#xff1a;HashSet 概述、HashSet 与 HashMap 的关系以及HashSet…

mysql备份脚本

#!/bin/bash #保留备份个数&#xff0c;会删除时间较早的.dump备份 number3 #设置备份保存路径&#xff0c;yourpath替换成自己的备份保存路径 backup_diryourpath #日期格式 dddate %Y%m%d #备份工具 toolmysqldump #数据库用户名 usernameroot #数据库密码&#xff0c;由于密…

Java集合:关于 TreeSet 的内容盘点

TreeSet() 是使用二叉树的原理对新 add() 的对象按照指定的顺序排序&#xff08;升序、降序&#xff09;&#xff0c;每增加一个对象都会进行排序&#xff0c;将对象插入的二叉树指定的位置&#xff1b; ~ 本篇内容包括&#xff1a;TreeSet 概述、TreeSet 的使用以及其他知识点…

python求素数

口求100内的素数 -个数能被从2开始到自己的平发根的正整数整数整除,就是合数 import math n100 for X in range(2, n): for i in range(2, math.ceil(math.sqrt(x))): if x %i 0: break else: print(x)口求100内的素数 合数一定可以分解为几个质数的乘积 import math n100 pri…

svn钩子脚本

REP0S"$1" REV"$2"export LANGen_US.UTF-8 LOGPATH"/app/log" [ !-d ${LOGPATH}] && mkdir $[LOGPATH) -p #update content from svn↓14 SVN/usr/bin/svn↓ SVN update --username test --password test /data/ if[ $? -eq ] then /us…

shell判断字符串是否为数字

#1.组合语法判断1: [ -n "echo $num|sed s/[0-9]//g" -a -n "echo $2|sed s/[0-9]//g"] &&\echo”两个参数都必须为数字”&& exit 1#2.组合语法判断2:[ -n "echo $num|sed s/[0-9]//g" -a -n "echo $2|sed s/[0-9]//g&…

MySQL:DQL 数据查询语句盘点

本篇内容包括&#xff1a;DQL 的简介、SELECT 语句、WHERE 条件语句、JOIN 连接查询(多表查询)和分组、过滤、排序、分页、子查询的使用。 一、DQL 简介 DQL&#xff08;Data QueryLanguage&#xff09;语句&#xff0c;即数据查询语句 常用的语句关键字有&#xff1a;SELECT…

MySQL:DML 数据操作语句盘点

本篇内容包括&#xff1a;DML 的简介、INSERT 命令、UPDATE 命令、DELETE 命令以及 TRUNCATE 命令的使用。 一、DML 简介 DML&#xff08;Data Manipulation Language&#xff09;语句&#xff0c;即数据操作语句&#xff0c;用于操作数据库对象中所包含的数据。 常用关键字包…

MySQL:DDL 数据定义语句盘点

本篇内容包括&#xff1a;DDL 的简介、SHOW 查看语句、CREATE 创建语句、ALTER 修改语句以及 DROP 删除语句的使用。 一、DDL 简介 DDL&#xff08;Data Definition Language&#xff09;&#xff0c;即数据定义语句&#xff0c;功能就是定义数据库DATabase、表table、索引ind…

MySQL:DCL 数据控制语句盘点

本篇内容包括:DCL 简介、GRANT、REVOKE、COMMIT、ROLLBACK、SAVEPOINT、LOCK命令的使用。 一、DCL 简介 DCL&#xff08;Data Control Language&#xff09;语句&#xff0c;即数据控制语句&#xff0c;用于设置或更改数据库用户或角色权限的语句 常用关键字包括&#xff1a;…

oracle迁移父子数据

现有需求如下&#xff0c;业务组织单元表中id字段数据在另外一个系统全部重复&#xff0c;但需要将此业务单元组织导入另一系统 业务组织单元表Isc_Specialorg_Unit 表中存在ID字段为子节点数据&#xff0c;parent_id为父节点数据&#xff0c;orgpath为组织路径 现在做如下操…

批量更新数据库数据

"update isc22.isc_user t set t.saphrid "&E1&"where t.id "&B1&";"

oracle控制文件

控制文件是数据库里面非常重要的一类文件,它记录了当前实例连接的数据库的结构和行为&#xff0c;并维护数据库的一致性。 初始化参数文件中描述其位置&#xff0c;很小的:二进制文件,一般不要超过100mmount读open一直在用 控制文件只能连接一个database丢失要恢复 …

oracle表空间

概念 表空间和数据文件 ●表空间是逻辑存储概念&#xff0c;一个表空间是一个或多个数据文件的逻辑集合 ●存储对象(表、索引)逻辑的存储在表空间上&#xff0c;而存储对象的数据物理的存放在数据文件上 ●数据库至少需要一个叫做system的表空间&#xff0c;也就是系统表空间 ●…

oracle日志

日志分类 redo log files联机日志或重做日志 archived log files归档日志 1184198alert log files 告警日志 trace files user_ _dump_ _dest 用户信息日志如跟踪会话日志 background dump_ dest进程日志还有其他一-些不常用的日志 v$database的log_mode 数据库归档模式…

MySQL:分库分表知识点盘点

本篇内容包括&#xff1a;数据库瓶颈、分库分表以及分库分表相关问题 一、数据库瓶颈 不管是IO瓶颈&#xff0c;还是CPU瓶颈&#xff0c;最终都会导致数据库的活跃连接数增加&#xff0c;进而逼近甚至达到数据库可承载活跃连接数的阈值。在业务Service来看就是&#xff0c;可用…