文章目录
1、单列集合Collection
1.1 Collection
Collection接口的方法
boolean add(E e) | 添加给定对象到集合中 |
---|---|
boolean remove(Object o) | 删除给定元素 |
boolean contains(Object o) | 判断是否包含给定元素 |
void clear() | 清空集合 |
boolean isEmpty() | 判断集合是否为空 |
int size() | 返回集合中元素的个数 |
三种通用的遍历方式
-
迭代器
在遍历时如果需要删除元素,使用这种方式,必须使用迭代器的删除方法才可以,不能使用集合的删除方法
迭代器遍历完毕,指针不会复位
如果需要第二次遍历,需要重新获取一个新的迭代器对象
-
增强for
底层调用的是迭代器,目的是简化迭代器遍历的书写
单列集合和数组可以使用增强for进行遍历
-
lambda
Iterator<Apple> iterator = list.iterator();
while (iterator.hasNext()){
Apple apple = iterator.next(); //循环中只能调用一次next方法,该方法将获取元素并且移动指针
System.out.println(apple);
}
//================================
for (Apple apple : list) {
System.out.println(apple);
}
//================================
list.forEach(System.out::println);
1.2 List
有序、有索引、可重复
list集合中删除元素的方法,一个是继承自Collection的,一个是重载的根据索引来删除,实际调用时,会根据就近原则选择
注意:Java中如果就近原则还不能确定,就会选择类型确定的方法,如果还是混淆的,则需要本类重写方法
遍历方式:
- 迭代器遍历 遍历时需要删除元素
- 列表迭代器遍历 遍历时需要添加元素,可以获取前一个元素
- 增强for遍历
- lambda
- 普通for遍历 如果遍历时需要操作索引
1.2.1 ArrayList
- 就是一个动态数组
- add(Object o)首次扩容为 10,之后每次扩容1.5倍
- addAll(Collection c) 没有元素时,扩容为 Math.max(10, 实际元素个数),有元素时为 Math.max(原容量 1.5 倍, 实际元素个数)
- ArrayList实现了RandomAccess接口,随机访问快,可以通过索引快速查到某个元素
- 尾部插入、删除性能可以,其它部分插入、删除都会移动数据,因此性能会低
- Fail-Fast:遍历的同时不能修改,尽快失败,CopyOnWriteArrayList 是 fail-safe 的典型代表,遍历的同时可以修改
- 如果需要集合中元素可重复
1.2.2 LinkedList
- 本质上是一个双向链表。它的顺序访问效率非常高,而随机访问的效率会比较低
- 头尾插入删除性能高
- 如果需要集合中元素可重复,增删操作多余查询操作
1.3 Set
无序、无索引、不可重复
1.3.1 HashSet
- 本质上是一个哈希表,允许Null元素。
- 通过HashMap实现,元素都保存在Key中
- 去重是根据hashCode和equals方法判断的
- 如果需要对集合中的元素去重
1.3.2 LinkedHashSet
- 存入和取出有序,是HashSet的子类,使用双链表记录添加记录(效率低于HashSet)
- 如果需要对集合中的元素去重,并且保证存取顺序
1.3.3 TreeSet
- 本质上是一个红黑树,是一个可排序的集合
- 基于TreeMap的 NavigableSet实现,增删改查性能较好
- 排序可以是类实现Comparable接口指定比较规则,也可以在创建集合对象时传递比较器Comparator
- 如果需要对集合中的元素去重,并且进行排序
2、双列集合Map<K,V>
- Key不能重复,Value可以重复,键值一一对应
- 键的唯一性是根据hashCode和equals方法判断的,因此如果存储自定义对象(键),需要重写这两个方法
- put方法添加时,如果键不存在,则直接添加,返回null,如果键存在,则会覆盖,返回旧值
2.1 HashMap
- jdk1.7通过哈希表+链表实现,jdk1.8通过哈希表+链表+红黑树实现
- jdk1.8之后,当链表长度超过8,并且数组长度大于等于64时,自动转换为红黑树
- 键可以为null,但其它的Map实现则不然
- 键是自定义对象,需要重写hashCode和equals方法,并且键的内容不能修改,如果修改了内容,会导致无法查询到
- 键的hashCode应该有较好的散列性
- jdk1.7的put方法可能会导致扩容死链的产生
树化意义
- 红黑树用来避免 DoS 攻击,防止链表超长时性能下降,树化应当是偶然情况,是保底策略
- hash 表的查找,更新的时间复杂度是 O(1),而红黑树的查找,更新的时间复杂度是 O(log_2n ),TreeNode 占用空间也比普通 Node 的大,如非必要,尽量还是使用链表
- hash 值如果足够随机,则在 hash 表内按泊松分布,在负载因子 0.75 的情况下,长度超过 8 的链表出现概率是 0.00000006,树化阈值选择 8 就是为了让树化几率足够小
树化规则
- 当链表长度超过树化阈值 8 时,先尝试扩容来减少链表长度,如果数组容量已经 >=64,才会进行树化
退化规则
- 情况1:在扩容时如果拆分树时,树元素个数 <= 6 则会退化链表
- 情况2:remove 树节点时,若 root、root.left、root.right、root.left.left 有一个为 null ,也会退化为链表
索引计算方法
- 首先计算hashCode(),再调用hash(),目的是为了使分布更加均匀,最后&(capacity-1)得到索引
数组容量为何是2的n次幂
- 计算索引时效率更高:2的n次幂可以使用&运算符取代%运算符。但是哈希分散性就不好,所以需要二次哈希来补偿。(Hashtable并没有使用二次hash这种做法)
- 扩容时计算索引效率更高:hash&oldCap==0的元素位置不变,否则新位置=旧位置+oldCap
put方法
- 首次使用才创建数组,懒惰初始化
- 计算索引
- 如果桶未占用,创建Node占位返回
- 如果已经被占用
- 是普通节点,走链表的添加或更新逻辑,并判断是否需要树化
- 是树节点,走红黑树的添加或更新逻辑
- 返回前检查是否需要扩容
加载因子为什么是0,75f
在空间和时间上取得了较好的平衡,大于该值,空间节省了,但是链表就会比较长,影响性能。小于这个值,冲突减少了,但是扩容会更频繁,空间占用也会更多
2.2 LinkedHashMap
是HashMap的子类,存入和取出是有序的,通过双向链表记录存储的顺序
2.3 TreeMap
- 底层是红黑树,键不重复、无索引、可排序
- 默认按照自然顺序排序,可传入比较器指定排序规则
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-C3Twkxrm-1692550286115)(…\Pictures\blog\red_black_1.png)]
edHashMap
是HashMap的子类,存入和取出是有序的,通过双向链表记录存储的顺序
2.3 TreeMap
- 底层是红黑树,键不重复、无索引、可排序
- 默认按照自然顺序排序,可传入比较器指定排序规则
[外链图片转存中…(img-C3Twkxrm-1692550286115)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Bk33Hxh5-1692550286116)(…\Pictures\blog\red_black_2.png)]