集合的类图关系:
什么是集合,为什么需要:
可以用来存储数据,扩展了数组的功能, 数组和集合存储引用数据类型,存的都是地址值
数组长度是固定,当添加的元素超过了数组的长度时需要对数组重新定义,太麻烦,java内部给我们提供了集合类,能存储任意对象,长度是可以改变的,随着元素的增加而增加,随着元素的减少而减少
List
ArrayList:
ArrayList 数据结构:底层实现是object数组(动态数组,可以扩容),它的随机访问速度极快,但是插入和删除的操作需要移动数组中的元素,比较麻烦。
- 它在没有初始化长度的时候,默认长度是10
- 如果向ArrayLIst添加元素,超过了原来的容量,那么扩容方案:扩容到原数组的1.5倍,如果扩容完之后还是小于mincapacity(最小容量),就扩容到mincapacity。宗旨就是将新加入的元素,能够放置到数组中
- ArrayList是线程不安全的,在多线程的情况下不要使用
Arraylist添加元素的原理
0、创建一个初始化长度的数组,若没有初始化长度的时候,不是在初始化Arraylist就创建好数组,而是在第一次add时创建
1、调用add方法,在插入数据之前,先要判断数组是否能够容下元素(调用方法参数是size+1,是当前数组中所有存在元素+1,也就是所需的最小容量(mincapacity,可能是批量添加)),容纳不下的话,此时要判断是空数组还是自定义长度数组,如果是空数组先判断mincapacity和默认数组长度10谁大,谁大就创建大的容量数组;进行第二步的扩容。
2、扩容:先根据原来的数组的长度,进行扩容到原来数组长度的1.5倍。如果扩容之后的容量小于mincapacity,则将mincapacity赋值给新数组的容量。如果扩容之后的容量大于数组定义最大容量,那么赋值给新数组Integer.MAX_VALUE-8
3、调用copyof方法,新建了一个数组,将原数组的对象复制到新的数组中,并且将现有的引用指向新的数组。
4、在调用element[size++]=e添加元素
arraylist add(index,E element)在某个位置添加元素实现
首先判断下标是否越界,检查当前容量是否可以容下一个元素,不够则扩容
通过arraycopy方法将elementdata从index开始将后面的元素往后移一位,在指定下标位置添加元素
arraylist get(int index)方法
功能:返回指定下标的元素
调用get(index)方法,之后先检查index的值是否大于Arraylist的大小
如果超过arraylist大小,会抛出异常,index<0了,系统会抛出异常
返回指定下标的元素
Linkedlist:
LinkedList 数据结构:双向链表 随机访问速度慢,查找元素是从头开始一个一个查找,但是适合于频繁的插入和删除操作。线程不安全
LinkedList内部实现原理:是一个Entry内部类,它包含了前一个元素的地址引用和后一个元素的地址引用,类似C语言的指针。进行操作元素
linkedlist的add方法添加元素的操作:
LInkedList添加操作时每个新添加的对象都会被放到新建的Node对象中,Node对象中包含加入的对象和前后指针属性(pred和next,pred和next其实都是Node对象,直接存放的就是当前对象的前一个节点对象和后一个节点对象,pred为最后一个及节点的next值为null)-》将last(指向最后一个节点)赋值为该节点-》判断最近一次last是否等于null-》若等于null将first(指向第一个节点)赋值到该节点-》若last不为null将原来最近一次last节点的next指针指向新加入的对象。
linkedlist的get方法,查询元素:
判断给定的索引值,若索引值大于整个链表长度的一半,则从后往前找,若索引值小于整个链表的长度的一般,则从前往后找。 这样就可以保证,不管链表长度有多大,搜索的时候最多只搜索链表长度的一半就可以找到,大大提升了效率。
list删除
正确的删除方式
-
使用Iterator进行删除:
当使用Iterator
遍历List
时,应该使用Iterator
的remove()
方法来删除元素,而不是使用List
的remove()
方法。Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String item = iterator.next();
if (someConditionToRemove(item)) {
iterator.remove();
}
}
-
使用List的remove()方法(通过索引):
如果你知道要删除元素的索引,可以使用List
的remove(int index)
方法。java复制代码
list.remove(index);
注意:删除元素后,后续元素的索引会发生变化。
-
使用List的remove()方法(通过对象):
如果List
中没有重复元素,你可以使用List
的remove(Object o)
方法根据元素的值进行删除。java复制代码
list.remove(element);
注意:这个方法只会删除第一个匹配的元素(如果有多个)。
-
使用Java 8的removeIf()方法(仅对
Collection
接口中的removeIf
方法,它在List
接口中没有直接定义,但可以通过继承Collection
接口的类使用,比如ArrayList
或LinkedList
):
如果你的列表是一个支持removeIf
的集合(比如ArrayList
或LinkedList
),你可以使用Java 8引入的removeIf
方法。java复制代码
list.removeIf(item -> someConditionToRemove(item));
错误的删除方式
-
在for-each循环中直接使用List的remove()方法:
这是一个常见的错误,因为for-each循环(也称为增强的for循环)在语法上不允许在循环体中修改集合的大小。java复制代码
// 错误的方式
for (String item : list) {
if (someConditionToRemove(item)) {
list.remove(item); // 这会引发ConcurrentModificationException
}
}
这个错误会导致
ConcurrentModificationException
,因为for-each循环内部使用了一个迭代器来遍历集合,而直接在循环体内调用List
的remove()
方法会修改集合的状态,从而与迭代器发生冲突。 -
在for循环中通过索引删除元素时未更新索引:
如果在for循环中通过索引删除元素,并且没有相应地更新索引,可能会导致跳过某些元素或引发IndexOutOfBoundsException
。java复制代码
// 错误的方式,可能会导致问题
for (int i = 0; i < list.size(); i++) {
if (someConditionToRemove(list.get(i))) {
list.remove(i); // 删除后应该更新i,否则可能会跳过下一个元素
}
}
为了避免这个问题,可以在删除元素后将索引
i
减1,或者从后往前遍历列表并删除元素。