集合的基本介绍

本文详细介绍了Java集合框架中的List(如ArrayList和LinkedList)、Set(如HashSet和TreeSet)和Map(如HashMap,LinkedHashMap和TreeMap)的原理、特点和操作,特别关注了它们的增删和查询效率,以及HashMap的哈希算法和扩容策略。
摘要由CSDN通过智能技术生成

一.Collection

集合可以分为单列集合Collection和双列集合Map。而Collection又可以根据元素是否可以重复,分为List和Set。

1.1List

List接口继承自Collection接口,是有序,可重复的单列集合。主要的常用实现类有ArrayList和LinkedList。

1.1.1ArrayList

ArrayList的底层是一个数组,它通过在类中定义了一个变量名为elementData的Object数组,再用这个数组去存储元素实现的。

在调用ArrayList的无参构造器时,它会为elementData赋值为一个长度为0的默认空数组,并且在这个时候不会给定列表的长度。

当我们第一次调用add或者addAll方法的时候,ArrayList会进行初始化容量。

如果是使用add方法添加一个元素,最量小容量是原来的size+1,计算出来就是1,然后它会和默认的容量10进行比较,得到他们两个之间的最大值,这个最大值就是ArrayList初始化的容量,这也是为什么我们说ArrayList默认的集合容量是10;但是如果是使用addAll方法一次性添加多个元素,并且一次性添加的元素超过了10个,那么这时他的最小容量就超过了10,会比默认的集合容量大,此时ArrayList的初始化容量就是这个最小容量,不再是默认的10了。

如果不是第一次使用add方法添加集合元素,并且数组长度不够的时候,会调用grow方法让集合扩容。这个新的集合容量是通过原来集合的容量加上原来集合容量右移一位计算得到的,这也是我们为什么说ArrayList扩容后容量是原来的1.5倍的原因。在改变集合容量后,我们还需要先将原来的集合中的元素对象通过copyof方法拷贝到新集合中去,再添加新的元素。

  • 优点:

ArrayList的优点就是查询快,因为它的底层是数组结构,我们可以通过它的下标索引快速的查找到我们想要查找的数据。

  • 缺点:

ArrayList的增删效率较低,这里的增删慢是指在集合的中部插入或者移除元素。因为它的底层是基于数组的,数组在内存中的是连续存储的,如果我们想要在指定位置添加一个元素,我们需要先将这个位置及这个位置之后的元素统统先往后移动一位,然后才能将这个元素插入到指定的索引位置,这个移动操作是耗时较高的操作。如果是一个删除操作,我们需要将这个元素先进行删除,然后将这个元素位置以后的所有元素统统前移一位。

1.1.2LinkedList

LinkedList的底层是一个双向链表,它的每一个结点是由Node对象构成的。这个Node对象主要存储了3个信息,链表中指向的上一个结点对象,当前结点所要存储的数据以及链表中指向的下一个结点对象。也可以理解为指向前结点的指针、存储数据以及指向后结点的指针。

  • 优点:

由于LinkedList是基于双向链表进行存储的,他的增删数据比较方便。比如现在要在A、B两个节点之间插入一个结点N,我们只需要让N的前结点指向A,N的后结点指向B,A的后结点指向N,B的前节点指向N就可以了,不需要像ArrayList一样进行元素的移动。如果是要删除A、B、C中的B元素,就更简单了,只需让C的前结点指向A,A的后结点指向C就完成了。

  • 缺点:

同样是由于它的双向链表结构,它的查询效率比较低,它只能通过挨个遍历双向链表中的每一个结点,直到找到想要查找的元素。

1.2Set

Set也是继承于Collection的一个接口,是无序不重复的单列集合,常见的有HashSet和TreeSet。

1.2.1HashSet

HashSet是基于HashMap实现的,它底层的HashMap里面的Key就是HashSet中需要存储的对象,他的Value是一个常量Object对象。

1.2.2TreeSet

TreeSet是一种不重复元素之间可以进行排序的单列集合,我们可以通过让要放入TreeSet中的泛型对象实现Comparable接口中的compareTo方法,实现自然排序。也可以在使用构造器创建TreeSet对象时,传入一个Comparator比较器对象,重写比较器中的compare方法,来实现比较器排序。

二.Map

Map是双列集合的顶级父接口,他的存储形式是以Key:value键值对的形式进行存储的。它的常见实现类有HashMap、LinkedHashMap以及TreeMap。

2.1HashMap

HashMap在JDK7及之前的版本是采用数组+单向链表的方式实现的。这种情况下,在扩容时会采用头插法,在单线程时表现正常,但在多线程时存在着链表循环的问题。

HashMap在JDK8及之后的版本中采用了数组+单向链表+红黑树实现。并且采用了尾插法,可以尽量避免链表的循环引用。

当我们调用HashMap无参构造器创建一个HashMap对象时,他会把加载因子设置为默认的0.75。

当我们调用put方法向HashMap中添加元素时,会先调用hash这个方法,对Key的值进行计算。如果Key是null,那么就直接返回为0,这也是为什么Key为null时,就会把这个键值对放入到索引为0的位置上去。如果不为null,会计算Key的哈希值,并将它的高16位与低16位进行异或运算进行返回,进行二次哈希运算主要是为了让哈希的分布更均匀。

然后调用putVal这个方法,把刚刚计算得到的哈希值作为参数传递进去,当数组为空或者长度为0时,对数组进扩容为16,并设置阈值为数组容量与加载因子的乘积,也就是12。

我们再通过这个哈希值与这个HashMap的数组长度-1进行与运算,来确定这个Key在数组中的下标索引位置。这里有一个细节,就是HashMap底层的数组长度都是2的整数次幂,如果我们有一个数要对2的整数次幂进行取模运算,就可以把这个数与2的整数次幂减一进行与运算进行代替,来提高计算效率。因为这样的值减一转换为二进制之后,就会变成前面都是0,后面都是1,与另一个数进行与运算之后就是该数对2的整数次幂取模的结果。

得到要插入索引的下标位置之后,我们还需要对这个位置上是否为null进行判断。如果是null,我们就直接插入这个新键值对。如果不为null,我们还需要再判断Key是否相同,如果相同,则进行value的替换;如果不同,我们继续根据这个数组索引位置上的是红黑树还是链表,进行对应方式的插入。如果是链表的插入,还会先判断链表长度是否到达8且数组长度到达64,如果是就会把链表转换成红黑树。

如果此时数组的size超过了之前计算的阈值,还会进行数组扩容。如果数组发生扩容(长度翻倍),它就会根据每个Key的二次哈希值和原来的数组容量做一次与运算,如果结果是0,就把这个元素放到原来的位置,否则就将元素放到旧位置+原数组容量的值对应的位置上,这样就能实现数组中的元素大概只有一半需要切换到另一半中,另一半留在原先的数组位置上就可以,这里就也是数组长度为2的正整数幂的第二个优势。

如果是要删除元素时,会根据key先找到元素,再删除,当红黑树节点小于等于6时,会转成链表。

2.2LinkedHashMap

LinkedHashMap继承于HashMap,基于HashMap和双向链表来实现的。使用LinkedList主要是为了维护插入顺序,LinkedHashMap是线程不安全的。

2.3TreeMap

与HashMap相比,TreeMap是一个能比较元素大小的Map集合,会对传入的key进行了大小排序。其中,可以使用元素的自然顺序(默认Key升序排列),也可以使用集合中自定义的比较器Comparator接口来进行排序;结构上,TreeMap不同于HashMap的哈希映射,TreeMap实现了红黑树的结构,形成了一颗二叉树。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值