java集合框架面试宝藏级资料(超详细)

1.1 概述

一方面,java作为面向对象语言对事物的操作都是以对象的方式,为了方便对多个对象进行操作,就要将对象存储到像集合这样的容器中

另一方面,常用的数组在存储对象方面有一些弊端,而java集合就像一个容器,可以动态的把多个对象的引用存放在这个容器中

1.2 组成

集合主要有两大接口,分别是collection接口,map接口
collection接口下的子接口主要包括list接口,set接口
list接口的常用实现类主要有 ArrayList,LinkedList以及Vector
set接口的常用实现类主要有 HashSet,TreeSet,LinkedHashSet等
Map接口的实现类主要有:Hashtable,HashMap,TreeMap等

1.3 各自特点

1.3.1 List集合

List集合的主要特点是元素有序,使用 List 能够精确地控制每个元素插入的位置,用户能够通过索引直接访问到 List 中的元素 ,并且可以存放相同元素.

其中ArrayList和Vector都是基于数组结构存储数据.而linkedList是基于双向链表实现,因为数组是用索引取值,可以快速定位到某个元素,所以ArrayList的查找遍历的效率很高,但是由于数组在进行删除和插入操作时,如果删除和插入的位置很靠前并且数组元素多的情况下,那么进行删除插入操作后,该位置之后的所有元素都需要向后移位或向前移位,效率会变得很低.

而LinkedList由于是基于双向链表实现的,中间的节点都存储了指向前一个节点的指针和指向后一个节点的指针,通过这种方式把每一个节点链接起来,使我们在进行插入和删除操作时,只需要在找到相应的位置之后,改变更改相应节点的前后指针即可,大大提高了插入和删除的效率,当然LinkedList也存在它的缺点,用链表结构存储,节点没有索引,所以在查找元素时需要靠指针在整个链表上移动查找,速度慢.

ArrayList与Vector的区别在于Vector是线程安全的,因此ArrayList的各方面效率高于Vector,所以当我们在查询较多的情况下推荐使用ArrayList,要考虑线程安全就用Vector,当插入和删除操作较多时使用LinkedList.

1.3.2 Set集合

Set集合的主要特点是元素不能重复且无下标,在Set]集合中,LinkedHashSet属于有序集合,HashSet,TreeSet都属于无序的.

其中HashSet集合是Set接口的典型实现,HashSet底层是基于hashMap实现的,元素不能重复且只能有一个null,HashSet可以看做HashMap的key部分,为了保证元素的不重复性,当新插入一个元素时,先判断该元素的hashcode值与HashSet中元素的hashcode值进行比较,如果hashcode值不一样则直接插入HashSet中,如果相同,再接着调用该元素的equal()方法与对应元素进行比较,如果返回true,则表示两者为同一元素不允许添加,返回false则在当前hashcode值处创建一个链表,元素存入链表中.

LInkedHashSet底层使用了LinkedHashMap,在HashSet的结构上增加了一个双向链表来记录元素插入顺序,所以在频繁遍历的场景下效率高于HashSet.

TreeSet的特点是元素有序,TreeSet的元素有两种排序方式,根据自然循序或者根据指定的Comparator(比较器)进行比较.TreeSet中的元素必须实现Comparable接口并重写compareTo()方法来指定元素的比较规则.

1.3.3Map集合

Map集合是一种比较特殊的集合,基于键值对实现,Map中的key不能重复,value可以重复.key可以唯一确定一个value,Map常用集合包括HashMap,HashTable

两者区别在于HashTable是线程安全的,因为HashTable的各个方法都被synchronized修饰了,HashTable的key不能是null,而HashMap的key可以为null,因为HashMap在底层实现hash函数时,对null进行了处理,会将key的hash值设置0.

在我们开发过程中,当我们发现存储的数据满足键值对的特性时,我们可以考虑使用map集合存储数据,在平时开发中,我发现hashmap与前后端进行交互的一种数据格式json的结构较为类似,在很多时候,可以通过它作为前后端的数据进行转换,称之为orm 。当多线程中要求线程安全时我们使用hashtable ,但鉴于效率考虑,推荐使⽤ConcurrentHashMap .

2.1 ArrayList的部分底层实现

ArrayList底层是基于数组进行动态扩容,由于数组长度在创建后是固定的,ArrayList进行优化实现了动态扩容.

2.1.1初始化

当我们第一次创建ArrayList时,如果使用无参构造器,则会将一个空的数组赋值给elementData数组,(此时数组没有被创建)然后在第一次添加元素时将创建数组并将数组扩容为10,当使用有参构造器时,如果参数为0则跟无参构造一样创建一个空数组赋值给elementData,参数不为0时,则会创建一个指定大小的Object数组赋值给elementData数组,为什么要分配两个空数组赋值给element数组呢,主要是为了区分最开始创建数组是用的无参构造器还是,参数为0的有参构造器.在第一次添加元素时如果检测到是无参构造器则将数组扩容到10.对比1.8之前,当我们创建有参构造器时直接new一个指定容量大小的数组,当我们创建了很多容量为0的集合时,会在内存中产生很多指定容量的数组占用空间影响性能,优化后直接将底层数组设置为一个空数组更加节省内存提高性能.

2.1.2 ArrayList扩容

当集合放不下元素时触发扩容机制.扩容的新数组的容量是原数组的1.5倍,执⾏扩容时使⽤系统类System的数组复制⽅法arraycopy()将原数组的元素复制到新数组.进行删除时,也是使用数组的拷贝方法,将要删除的元素后的数组拷贝 至原来的前一位,再将最后的元素设置为null并交给gc回收。插入方法类似于上述方式实现.

2.2 HashMap的部分底层实现

2.2.1 HashMap的结构差异

hashMap在1.8之前使用数组加链表实现,在jdk1.8之后使用数组加链表加红黑树实现.

在1.8之前:根据hash(key)确定存储位置后,以链表的形式在该位置存储数据.此时数组的该位置的链表存在多个数据也称为桶.存放的数据是以Entry描述,entry中包含了该数据的key,value,hash,next(指向下一个entry).

在1.8版本:数据以Node描述,node同样包含了该数据的key,value,hash,next(指向下一个node),当链表的长度大于8,数组长度大于64时,链表会转换为红黑树,当红黑树节点数小于6时转换为链表.

2.2.2 插入数据

hashMap在插入数据时,先判断数组是否为空,为空则先初始化容量为16数组再通过(n-1)&hash计算存储位置,如果不为空则直接通过(n-1)&hash计算存储位置,判断指定位置是否存在数据,不存在则直接存放节点,存在数据则表示发生hash碰撞,再判断key是否相等,相等则新的value覆盖旧的value并返回旧的value,key不相等则判断是否为红黑树节点以及是否要转换为红黑树,以及判断是否扩容等操作.

2.2.3初始化

初始化如果没有指定容量则默认创建大小为16的数组,负载因子为0.75.

如果指定了容量大小则会创建大于指定容量,最近的2的整数次方数.

2.2.4Hash函数的设计

jdk1.7的hash函数做了四次移位四次异或,效率比1.8要低
jdk1.8的hash函数⽤key的hashCode()与其低16位做异或运算 (称为扰动函数)
扰动函数总的来说就是可以使hash值尽量分散,降低hash碰撞的概率.
hash运算的目的是⽤来定位该数据存放在数组的位置,jdk1.8是通过n-1的操作跟原hash值做“与”运算来定位的,相当于是更高效的取模运算

2.2.5 那么为什么不能直接⽤key的hashCode()作为hash值,⽽⼀定要^ (h >>> 16)?

因为如果直接⽤key的hashCode()作为hash值,很容易发⽣hash碰撞。

使⽤扰动函数^ (h >>> 16),就是为了混淆原始哈希码的⾼位和低位,以此来加⼤低位的随机性。且低位中参杂了⾼位的信息,这样⾼位的信息也作为扰动函数的关键信息

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值