Java 集合框架

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_2⁡n ),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方法

  1. 首次使用才创建数组,懒惰初始化
  2. 计算索引
  3. 如果桶未占用,创建Node占位返回
  4. 如果已经被占用
    1. 是普通节点,走链表的添加或更新逻辑,并判断是否需要树化
    2. 是树节点,走红黑树的添加或更新逻辑
  5. 返回前检查是否需要扩容

加载因子为什么是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)]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值