java基础: 集合框架与实现

在这里插入图片描述


1. List

特点

  1. 有序
  2. 允许多个null

1.1 ArrayList

在这里插入图片描述

ArrayList是一个可变对象数组, 默认构造创建了一个空数组,默认初始容量为10

在这里插入图片描述

add()

在这里插入图片描述

先让modCount(结构性修改次数)自增再调用add(E e, Object[] elementData, int s)

在这里插入图片描述
保证方法字节码大小小于35,使得在循环调用时的速度变快
可以发现 grow()在数组长度满时扩容
在这里插入图片描述这里的grow()当老数组长度大于0并且,不为空时是扩容数组,新数组的长度为老数组长的1.5倍(右移1位是除以2)
为空时是初始化数组,初始长度为默认(10)与输入取最大值
在这里插入图片描述

remove()

  • remove 索引
    在这里插入图片描述

  • remove 对象
    在这里插入图片描述
    found:{}是可以用break跳出循环语句块的循环
    先遍历数组找到对象的索引,在调用fastRemove()方法

  • fastRemove()
    在这里插入图片描述
    如果是末尾位置直接设为null,
    不是末尾位置,利用数组复制的方式,将索引之后的元素左移
    在这里插入图片描述

sort()

在这里插入图片描述
sort的实现调用了Arrays.sort(),并利用modCount判断了并发修改异常

contains()

在这里插入图片描述在这里插入图片描述
contains的本质就是遍历数组,会根据空与非空进行不同的判断

subList()

在这里插入图片描述在这里插入图片描述
可以看到subList方法是构造了一个静态内部类的对象

forEach()

在这里插入图片描述ArrayList的forEach遍历就是最常规的for循环数组遍历

1.2 Vector

在这里插入图片描述可以看到Vector的构造需要初始长度(默认10),与 溢出时的增量(默认0)

add()

在这里插入图片描述
可以发现,Vector的add是通过加锁来保证线程安全的,这种做法是不推荐的,我们应该使用CopyOnWriteArrayList

在这里插入图片描述
Vectorgrow方法也与ArrayList不同,当没有给出默认增量时,默认增量是原数组的长度,所以默认扩容长度是2倍

remove()

在这里插入图片描述
在这里插入图片描述
Vector的大部分方法与ArrayList一致,但是通过加锁来保证线程安全

1.3 LinkedList

在这里插入图片描述
第一个构造函数初始化了一个空链表,第二个调用了addAll方法,将参数集合添加到链表中

add

  • add(E e)
    在这里插入图片描述
    在这里插入图片描述
    add()调用了linkLast(尾插),在其中初始化了一个Node节点,将last指向Node,调整first和next

在这里插入图片描述
这里我们可以看出,LinkedList实现了一个双向链表

  • add(int index, E element)
    在这里插入图片描述
    如果插入位置与链表长度相等则直接尾插,如果不等,则在索引位置前插入
    在这里插入图片描述
    通过遍历链表的方式找到索引处的节点并返回(会根据索引位置判断,是从头遍历还是从末尾遍历)

remove

在这里插入图片描述在这里插入图片描述
默认的remove会删除链表的第一个节点
在这里插入图片描述
最后的函数是将删除的节点至空,以便及时的垃圾回收

getset

在这里插入图片描述

这两个方法都采用遍历的方式来更改或者获取节点 可以看出 链表在查找的时间复杂度高

forEach()

在这里插入图片描述
链表的forEach采用了迭代器的遍历方式

2. Map<K, V>

特点

  1. 不能包含重复的键; 每个键最多可以映射一个值
  2. HashMap只允许一条记录的键为NULL,TreeMap不允许键为NULL
  3. 没有继承Collection接口,无序

2.1 HashMap<K,​V>

jdk1.8以上的 HashMap 由数组+链表(红黑树) 组成
桶(数组)的长度一定为2的整数次幂,手动设置的也会扩大到2的整数次幂,每次扩容重新哈希也是如此
在这里插入图片描述
在这里插入图片描述

默认构造了一个初始容量16 装载因子为0.75的HashMap,当然也可以手动提供 容量和装载因子
在这里插入图片描述
tableSizeFor会根据初始容量计算出扩容阈值threshold (2的次方) 例如初始容量100 阈值为2的次方数 128

直到这里,HashMap还没有初始化数组

put(k,v)

在这里插入图片描述

这里利用hash()计算了key的hash值,并放到putVal
在这里插入图片描述

hash()的取值要结合数组槽位(n - 1) & hash来分析

^ 按位异或运算,只要位不同结果为1,不然结果为0;
>>> 无符号右移:右边补0
在这里插入图片描述
将h无符号右移16为相当于将高区16位移动到了低区的16位,再与原hashcode做异或运算,可以将高低位二进制特征混合起来
从上文可知高区的16位与原hashcode相比没有发生变化,低区的16位发生了变化
我们可知通过上面(h = key.hashCode()) ^ (h >>> 16)进行运算可以把高区与低区的二进制特征混合到低区,那么为什么要这么做呢?
我们都知道重新计算出的新哈希值在后面将会参与hashmap中数组槽位的计算,计算公式:(n - 1) & hash,假如这时数组槽位有16个,则槽位计算如下:
在这里插入图片描述

高区的16位很有可能会被数组槽位数的二进制码锁屏蔽,如果我们不做刚才移位异或运算,那么在计算槽位时将丢失高区特征
也许你可能会说,即使丢失了高区特征不同hashcode也可以计算出不同的槽位来,但是细想当两个哈希码很接近时,那么这高区的一点点差异就可能导致一次哈希碰撞,所以这也是将性能做到极致的一种体现

在这里插入图片描述
可以发现,HashMap中的数组由Node<K,V>组成

在这里插入图片描述
可以看到Node的数据结构,其中有keyvaluehash和指向下一个节点的指针
在这里插入图片描述
从上面可以看到 jdk1.8后的hashmap采用了尾插法

下面看看resize()是如何扩容的
在这里插入图片描述在这里插入图片描述

get()

在这里插入图片描述
调用getNode()获取节点,如果没找到返回null

在这里插入图片描述

remove()

在这里插入图片描述这里调用了removeNode()删除指定节点,删除成功返回节点的值,失败返回null

在这里插入图片描述

forEach()

在这里插入图片描述
HashMap的forEach遍历为 遍历数组节点(数组中有链表或红黑树则时间复杂度增加)的方式

2.2 LinkedHashMap<K,​V>

在这里插入图片描述

Map接口的哈希表和链表实现,具有可预测的迭代顺序。
此实现与HashMap不同之处在于它额外维护了一个贯穿其所有条目的双向链表。 使得数据变得有序
此链表定义迭代排序,通常是键插入映射的顺序 ( 插入顺序 )。
请注意,如果将键重新插入Map,则不会影响插入顺序。

默认构造的初始大小为16 默认负载因子为0.75
在这里插入图片描述
除了默认构造,也可以手动设置容量,负载因子和排序模式accsessOrder
在这里插入图片描述
在这里插入图片描述

put(k,v)

LinkedHashMap的put方法与HashMap的put大致相同,但是改变了初始化新节点的方式,初始化完的节点会被连接到链表尾部
在这里插入图片描述
LinkedHashMap中的节点为Entry,它继承了HashMap中的Node

在这里插入图片描述在这里插入图片描述

get()

LinkedHashMap重新实现了get方法 利用HashMap的getNode获取节点的值,如果此时accessOrder为true,则将访问的节点置于链表末尾
在这里插入图片描述

remove()

在这里插入图片描述

forEach()

在这里插入图片描述可以看到LinkedHashMap的遍历方式为链表的遍历的方式,是有序的

2.3 TreeMap

TreeMap是通过红黑树实现的,其中的comparator会在插入数据时进行排序,如果未传入comparator则会进行自然排序,所以他是有序的
TreeMap是有Entry节点组成的,每次操作完节点后都要重新平衡树
在这里插入图片描述在这里插入图片描述
Key为无法比较的对象时,构造函数必须传入比较器
在这里插入图片描述

put()

在这里插入图片描述

get()

这里getEntry寻找指定节点,当未找到会返回null
在这里插入图片描述

当传入构造器用传入的构造器遍历比较寻找,未传入用自然排序比较寻找
在这里插入图片描述

remove()

先调用getEntry遍历找到要删除的节点再调用deleteEntry删除
在这里插入图片描述

forEach()

采用了迭代遍历的方式对树进行遍历,当modCount不统一时会抛出并发修改异常
在这里插入图片描述

3. Set

3.1 HashSet

没有重复元素的无序集合
默认构造,构造了一个默认容量为16,负载因子为0.75的HashMap
在这里插入图片描述
可以看到,HashSet的本质就是一个只有Key,值为Object的HashMap
同样,hashSet也是可以自己设置负载因子及容量的

在这里插入图片描述

add()

add直接调用了HashMap的put,传了键与一个空对象
在这里插入图片描述

remove()
remove直接调用了HashMap的remove

在这里插入图片描述

forEach()

在这里插入图片描述
在这里插入图片描述

遍历方式为迭代器遍历

3.2 TreeSet

TreeSet是一个有序集
在这里插入图片描述里面维护了一个可排序Map

add()

在这里插入图片描述

remove()

在这里插入图片描述

forEach()
遍历方式为迭代器遍历

在这里插入图片描述

4. Queue

4.1 PriorityQueue

PriorityQueue中的数据必须是有序的,或者是可排序的
PriorityQueue不允许添加null

默认构造 声明了一个容量为11 不带比较器的优先队列
在这里插入图片描述

它的其他构造方法还能指定容量,指定比较器,或者用其他的集合构造它

add() ,offer()

在这里插入图片描述

在这里插入图片描述

这两个方法都是入队操作,在判断是否需要扩容后队尾入队

在这里插入图片描述
从上面可以看到,当长度低于64时,扩容为2n+2,当大于等于64时 扩容1.5倍

在这里插入图片描述
插入函数对是否有比较器进行了区分,有比较器是用比较器,无比较器用Comparable比较

remove()

在这里插入图片描述
remove先遍历找出节点位置再调用removeAt()删除

在这里插入图片描述
removeAt在删除最后一个元素时直接将对应元素置为null,其他时候将最小的元素移动到根,再对其进行删除操作

forEach()

在这里插入图片描述遍历采用了for循环遍历

在这里插入图片描述

5. 视图

5.1 不可修改视图

Xxx.of(" “,” ")

用这个方法可以将对象组成集合,这个方法组成的集合是小集合,是不可修改的名,如果想让他们可以修改可以将其传入对应集合的构造器中

在这里插入图片描述
Arrays.asList() 实现的集合也与此类似

subXxx(E from,E to)

左开右闭,返回大于等于from到to所有元素构成的子集

Collections.unmodifiableXxx()

返回一个对应的接口对象,但是所有的更改器方法已经重新定义为抛出异常

5.2 同步视图

Collections.synchronizedXxx()

可以返回一个线程安全的结合对象 确保安全的方式为加synchronized

5.3 检查型视图

Collections.checkedXxx()

可以检查集合的泛型与所给的泛型是否一致,这个错误在add时无法被找到,可以用这个检查

5.4 方法

排序 sort()

集合类库的排序比快速排序慢 需要传入可排序集合对象 或者传入对象与比较器

removeof replaceAll

这两个方法可以传入lamda表达式对集合进行操作

集合转数组 toArray

这个方法有双重职责,不仅要填充一个已有的数组(如果它足够长),还要创建一个新数组
必须 xxx.toArray(new xx[0])

发布了22 篇原创文章 · 获赞 0 · 访问量 577

猜你喜欢

转载自blog.csdn.net/lixiang19971019/article/details/105363086