JAVA 集合
1.框架图
图源:(108条消息) java集合框架_程序员老石的博客-CSDN博客_java集合框架
2.Collection API
public boolean add(E e);添加元素
public void clear();清空
public boolean remove(E e);指定的元素删除掉
public int size();返回集合中元素的个数
public Object[] toArray();把集合中的元素存到数组里面。
3 ArrayList
3.1 特性
ArrayList 的底层是数组队列,相当于动态数组。与 Java 中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用ensureCapacity操作来增加 ArrayList 实例的容量。这可以减少递增式再分配的数量。
- 它继承于 AbstractList,实现了 List, RandomAccess, Cloneable, java.io.Serializable 这些接口。
- ArrayList 插入删除元素的时间复杂度为O(n),求表长以及增加元素,取第 i 元素的时间复杂度为O(1)
- ArrayList 继承了AbstractList,实现了List。它是一个数组队列,提供了相关的添加、删除、修改、遍历等功能。
- ArrayList 实现了RandomAccess 接口, RandomAccess 是一个标志接口,表明实现这个这个接口的 List 集合是支持快速随机访问的。在 ArrayList 中,我们即可以通过元素的序号快速获取元素对象,这就是快速随机访问。
- ArrayList 实现了Cloneable 接口,即覆盖了函数 clone(),能被克隆。
- ArrayList 实现java.io.Serializable 接口,这意味着ArrayList支持序列化,能通过序列化去传输。
- 和 Vector 不同,ArrayList 中的操作不是线程安全的!所以,建议在单线程中才使用 ArrayList,而在多线程中可以选择 Vector 或者 CopyOnWriteArrayList。
3.2 ArrayList 扩容机制
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | private static final int DEFAULT_CAPACITY = 10; //数组默认初始容量 private static final Object[] EMPTY_ELEMENTDATA = {}; //定义一个空的数组实例以供其他需要用到空数组的地方调用 private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; //定义一个空数组,跟前面的区别就是这个空数组是用来判断ArrayList第一添加数据的时候要扩容多少。默认的构造器情况下返回这个空数组 transient Object[] elementData; //数据存的地方它的容量就是这个数组的长度,同时只要是使用默认构造器(DEFAULTCAPACITY_EMPTY_ELEMENTDATA )第一次添加数据的时候容量扩容为DEFAULT_CAPACITY = 10 private int size; //当前数组的长度 private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8; //允许分配的最大数组容量 public ArrayList() { this .elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;} //创建了一个空的elementData数组 //添加函数 public boolean add(E e) { ensureCapacityInternal(size + 1); //确定是否要扩容 elementData[size++] = e; //赋值,size++ return true ; } /** * 确定minCapacity */ private void ensureCapacityInternal( int minCapacity){ ensureExplicitCapacity(calculateCapacity(elementData,minCapacity)); } /** /计算minCapacity */ private static int calculateCapacity(Object[] elementData, int minCapacity) { //若elementData为空,那么第一次扩容为10 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) { return Math.max(DEFAULT_CAPACITY, minCapacity); } //否则返回size+1 return minCapacity; } /** * */ private void ensureExplicitCapacity( int minCapacity) { modCount++; //操作次数+1 // 如果要当前elementData的长度小于要求的minCapacity则扩容 if (minCapacity - elementData.length > 0) grow(minCapacity); //扩容函数 } private void grow( int minCapacity) { //将原本的长度记录下来 int oldCapacity = elementData.length; int newCapacity = oldCapacity + (oldCapacity >> 1); //原长度的1.5倍 if (newCapacity - minCapacity < 0) //判断扩充后的容量是否满足要求,如果不满足要求,直接将容量扩大到指定大小<br>{<br>newCapacity = minCapacity;<br><br> if (newCapacity - MAX_ARRAY_SIZE > 0)//如果1.5倍超出数组最大长度即INT_MAX-8<em id="__mceDel"><em id="__mceDel"> newCapacity = hugeCapacity(minCapacity);//抛出异常 // minCapacity is usually close to size, so this is a win: elementData = Arrays.copyOf(elementData, newCapacity); //扩容使用的函数 } </em></em> |
当创建ArrayList对象的时候,如果使用的是无参构造器,则初始elementData容量为0,第一次添加,则扩容elementData为10,如需再次扩容, 则扩容elementData1.5倍
如果使用的是指定大小的构造器,则初始化elementData容量为指定大小,如果需要扩容,则直接扩容elementData为1.5倍 // 例:8->12->18
3.3 ArrayList 和 Vector
- Vector与ArrayList一样,也是通过数组实现的,Vector类的所有方法都是同步的。它也是线程安全的,而Arraylist是线程异步(ASynchronized)的,是不安全的
如果不考虑到线程的安全因素,一般用Arraylist效率比较高。
- 使用ArrayList时,如果不指定大小,会生成一个空的数组;
- 使用Vector时,如果不指定大小,会默认生成一个10个元素大小的数组
- Vector 实现类中有一个变量 capacityIncrement 用来表示每次容量自增时应该增加多少,如果不指定,默认为0
- 在扩容时,会判断,如果指定了capacityIncrement,会先把数组容量扩大到oldCapacity + capacityIncrement,如果没有指定capacityIncrement,会先把数组容量扩大到2倍的oldCapacity, 然后再进行判断扩充后的容量是否满足要求,如果不满足要求,直接将容量扩大到指定大小
3.4 LinkedList
LinkedList是一个实现了List接口和Deque接口的双端链表。 LinkedList底层的链表结构使它支持高效的插入和删除操作,另外它实现了Deque接口,使得LinkedList类也具有队列的特性; LinkedList不是线程安全的,如果想使LinkedList变成线程安全的,可以调用静态类Collections类中的synchronizedList方法:
List list=Collections.synchronizedList(new LinkedList(...));
看LinkedList类中的一个内部私有类Node就很好理解了:
private static class Node<E> { Node<E> prev;//前驱节点 E item;//节点值 Node<E> next;//后继节点 Node(Node<E> prev, E element, Node<E> next) { this.item = element; this.next = next; this.prev = prev; } }
linkedList.remove(); // 这里默认删除的是第一个结点 1. 执行 removeFirst public E remove() { return removeFirst(); } 2. 执行 public E removeFirst() { final Node<E> f = first; if (f == null) throw new NoSuchElementException(); return unlinkFirst(f); } 3. 执行 unlinkFirst, 将 f 指向的双向链表的第一个结点拿掉 private E unlinkFirst(Node<E> f) { // assert f == first && f != null; final E element = f.item; final Node<E> next = f.next; f.item = null; f.next = null; // help GC first = next; if (next == null)// last = null; else next.prev = null; size--; modCount++; return element; } */
/* *get(index) */ public E get(int index) { checkElementIndex(index);//确定是否符合范围 return node(index).item; } private void checkElementIndex(int index) { if (!isElementIndex(index))//错误则抛出异常 throw new IndexOutOfBoundsException(outOfBoundsMsg(index)); } private boolean isElementIndex(int index) { return index >= 0 && index < size;//判断 } //取元素,遍历取 Node<E> node(int index) { // assert isElementIndex(index); if (index < (size >> 1)) { Node<E> x = first; for (int i = 0; i < index; i++) x = x.next; return x; } else { //超过1/2则从后往前取 Node<E> x = last; for (int i = size - 1; i > index; i--) x = x.prev; return x; } }
3.5 ArrayList与LinkedList的区别
- 是否保证线程安全: ArrayList 和 LinkedList 都是不同步的,也就是不保证线程安全;
- 底层数据结构: Arraylist 底层使用的是Object数组;LinkedList 底层使用的是双向链表数据结构;
- 插入和删除是否受元素位置的影响: ① ArrayList 采用数组存储,所以插入和删除元素的时间复杂度受元素位置的影响。 比如:执行add(E e) 方法的时候, ArrayList 会默认在将指定的元素追加到此列表的末尾,这种情况时间复杂度就是O(1)。但是如果要在指定位置 i 插入和删除元素的话(add(int index, E element) )时间复杂度就为 O(n-i)。因为在进行上述操作的时候集合中第 i 和第 i 个元素之后的(n-i)个元素都要执行向后位/向前移一位的操作。 ② LinkedList 采用链表存储,所以插入,删除元素时间复杂度不受元素位置的影响,都是近似 O(1)而数组为近似 O(n)。
- 是否支持快速随机访问: LinkedList 不支持高效的随机元素访问,而 ArrayList 支持。快速随机访问就是通过元素的序号快速获取元素对象(对应于get(int index) 方法)。
- 内存空间占用: ArrayList的空间浪费主要体现在在list列表的结尾会预留一定的容量空间,而LinkedList的空间花费则体现在它的每一个元素都需要消耗比ArrayList更多的空间(因为要存放直接后继和直接前驱以及数据)。
3.6 ArrayList 与Array
1.Array可以包含基本类型和对象类型,ArrayList只能包含对象类型;(所以ArrayList有个自动装箱的过程)
2.Array(数组)的大小是固定的,ArrayList(列表)的大小是动态变化的;
3.ArrayList提供了更多的方法和特性:addAll()、removeAll()、iterator等;
4.对于基本类型数据,集合使用自动装箱来减少编码工作量。但是,当处理固定大小的基本数据类型的时候,这种方式相对比较慢。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器
· 面试官:你是如何进行SQL调优的?