一定要会的Java集合问题

常用的集合类有哪些

Map接口和Collection接口时所有集合框架的父接口,其中接口于接口之间为继承关系,类与类之间为继承关系,类与接口之间则是类实现接口。重点掌握的抽象类HashMap,LinkdeList,HashTable,ArrayList,HashSet,Stack,TreeSet,TreeMap。 注意:collection接口不是Map的父接口。
collection
Map

List,Set,Map三者的区别

  • List: 有序集合,有序指存入的顺序和取出的顺序相同,不是按照元素的某些特性排序,可存储重复元素,可存储多个null
  • Set: 无序元素 ,元素存入和取出的顺序不一定相同,不可存储重复元素,只能存储一个null。
  • Map: 使用键值对的方式进行存储,Key是无无序的,且是唯一的。Value值不唯一。 不同的Key值可以对应相同的value值。

常用集合框架底层数据结构

  • List:
    1.ArrayList:数组
    2.LinkedList:双线链表
  • Set:
    1.HashSet:底层基于HashMap实现,HashSet存入读取元素的方式和HashMap中的Key是一致的。
    2.TreeSet:红黑树
  • Map:
    1.HashMap:JDK1.8之前HashMap由数组+链表组成的,JDK1.8之后由数组+链表/红黑树组成,当链表长度大于8时,链表转化为红黑树,当长度小于6时,从红黑树转化为链表。 这样做的目的是为了提高HashMap的性能,因为红黑树的查找元素的时间复杂度远小于链表。
    2.HashTable:数组+链表
    3.TreeMap:红黑树

哪些集合是线程安全的,和不安全的

当多个并发同时对非线程安全的集合进行增删改查的时候会破坏这些集合的数据完整性。

线程安全的集合

  • Vector:相当于有同步机制的ArrayList
  • Stack:栈
  • HashTable
  • enumeration:枚举
    为了保证集合是线程安全的,对应的效率也比较低。

线程不安全的集合

  • ArrayList
  • LinkdeList
  • HashSet
  • TreeSet
  • HashMap
  • TreeMap…
    线程不安全的集合效率会高一些。

Array和ArrayList的区别

  • Array可以包含基本类型和对象类型,ArrayList只能包含对象类型。
  • Array大小是固定的,ArrayList的大小是动态变化的。(重点:ArrayList的扩容机制)
  • 相对比Array,ArrayList有更多的内置方法,比如:addAll(),remove()。
  • 对比基本数据类型,ArrayList使用自动装箱来减少编码工作量;而当处理固定大小的基本数据类型的时候,这种方式相对于比较慢,这时候应该用Array。

comparable和comparator的区别

  • comparable理解为一个内比较器,实现了Comparable接口的类可以和自己比较,要和其它实现了Comparable接口类比较,可以使用compareTo(Object obj)方法。compareTo方法的返回值是int,有三种情况:
    1.返回正整数(比较者大于被比较者)
    2.返回0(比较者等于被比较者)
    3.返回负整数(比较者小于被比较者)
  • comparator接口,他用一个compare(Object obj1,Object obj2)方法用来排序,返回值为int,有三种情况,和compareTo类似。

它们之间的区别:很多包装类都实现了comparable接口,所以直接调用collections.sort()直接可以使用。如果对类的自然排序不满意,可以使用Comparator就比较合适,此外使用Comparator可以避免添加额外代码与我们的目标类耦合,可以定义多种排序规则,这是Comparable没法做到的。所以在自定义排序时,优先考虑Comparator接口。

Collection和Collections的区别

  • Collection是一个集合接口 ,提供了对集合进行基本操作的通用接口方法。
  • Collections是一个包装类。它包含有各种有关集合操作的静态多态方法,此类不能实例化,就像是一个工具类 ,服务于Java的Collection框架。

List集合

遍历List集合的方式

常见的元素在内存中的存储方式,主要有两种:
1.顺序存储:相邻的数据元素在内存的位置也是相邻的(如:ArrayList),可以根据元素的位置读取元素。
2.链式存储:每个数据元素包含下一个元素的内存地址,在内存中不要求相邻。(例如:LinkedList)。
主要的遍历方式有三种:
1.for循环遍历:遍历者自己在集合外部维护一个计数器,依次读取每一个位置的元素。
2.Iterator遍历:基于顺序存储集合的Iterator可以直接按位置访问数据。基于链式存储集合的Iterator,需要保存当前遍历的位置,然后根据当前的位置来向前或者向后移动指针。
3.foreach遍历:foreach内部也是采用了Iterator的方式实现,但使用时不需要显示地声明Iterator。

ArrayList的扩容机制

ArrayList的初始容量为10,扩容是对旧的容量值加上旧的容量数值进行右移一位(位运算,相当于除以2,位运算的效率更高),所以每次扩容都是旧的容量的1.5倍。

ArrayList和LinkedList的区别

  • 是否线程安全:ArrayList和LinkdeList都是不保证线程安全的
  • 底层实现:ArrayList的底层实现是数组,LinkedList的底层是双向链表。
  • 内存占用:ArrayList会存在一定的空间浪费,因为每次扩容都是之前的1.5倍,而LinkedList中的每个元素要存放直接后继和直接前驱以及数据,所以对于每个元素的存储都要比ArrayList花费更多的空间。
  • 应用场景:ArrayList的底层数据结构是数组,所以在插入和删除元素时的时候的时间复杂度都受到位置的影响,平均时间复杂度为O(n),在读取元素的时候可以根据下标直接查到元素,不受位置影响,平均时间复杂度为O(1),所以ArrayList更加适用于多读,少增删的场景。 LinkedList的底层数据结构是双向链表,所以插入和删除元素不受位置的影响,平均时间复杂度为O(1),如果是在指定位置插入则是O(n),因为在插入之前需要先找到该位置,读取元素的平时时间复杂度为O(n),所以LinkedList更加适用于多增删,少读写的场景。

ArrayList和Vector的区别

  • 相同点
    1.都实现了List接口
    2.底层数据结构都是数组
  • 不同点
    1.线程安全:Vector使用了Synchronized来实现线程同步,所以是线程安全的,而ArrayList是线程不安全的。
    2.性能:由于Vector使用了Synchronized进行加锁,所以性能不如ArrayList。
    3.扩容:ArrayList和Vector都会根据需要动态调整容量,但是ArrayList每次扩容的是1.5倍,而Vector每次扩容为旧容量的2倍。

简述ArrayList、Vector、LinkedList的存储性能和特性

  • ArrayList底层数据结构为数组,对元素的读取速度快,而增删数据慢,线程不安全。
  • LinkedList底层为双向链表,对元素的增删数据块,读取慢,线程不安全。
  • Vector的底层数据结构为数组,用Synchronize来保证线程安全,性能较差,但线程安全。

Set集合

HashSet的实现原理

HashSet的底层是HashMap,默认构造函数是构建一个初始容量为16,负载因子为0.75的HashMap。HashSet的值存放于HashMap的Key上,HashMap的value统一为PRESENT。

HashSet如何检查重复(HashSet如何保证数据不可重复)

这里面涉及到了HashCode() 和equals() 两个方法。
从源码中可以看到:
equals():
1.equals 会比较内存地址、对象类型、以及值,内存地址相同, equals 一定返回true ;对象类型和值相同, equals 方法一定返回true 。
2.如果没有重写equals方法,那么与==的作用相同,比较的是对象的地址值。
HashCode方法返回对象的散列码,返回值是int类型的散列码,散列码的作用是确定该对象在哈希表中的索引位置。
关于hashCode有一些约定:
1.两个对象相等,HashCode一定相同。
2.两个对象有相同的hashCode值,它们不一定相等。
3.hashCode()方法默认是对堆上的对象产生独特值,如果没有重写hashCode()方法,则该类的两个对象的hashCode值肯定不同。
HashSet的特点是存储元素时无序且唯一,在向HashSet中添加对象时,首先会计算对象的HashCode值来确定对象的存储位置,如果该位置没有其他对象,直接将该对象添加到该位置;如果该存储位置有存储其他对象(新添加的对象和存储位置的对象的HashCode值相同),调用equals方法判断两个对象是否相同,如果相同,则添加对象失败,如果不相同,则会将该对象重新散列到其他位置。

HashSet和HashMap的区别

区别

Map集合

HashMap在JDK1.7和JDK1.8中有哪些不同

  • JDK1.7的底层数据结构(数组+链表)
  • JDK1.8的底层数据结构(数组+链表/红黑树) 当链表长度大于8时转化为红黑树
    重点 :JDK1.8的Hash函数经过了一次异或一次位运算一共两次扰动,key.hashCode()与Key.hashCode() 右移16位进行异或。这样做的目的是,高16位不变,低16位与高16位进行异或操作,进而减少碰撞的发生,高低Bit都参与到Hash的计算。如何不进行扰动处理,因为hash值有32位,直接对数组的长度求余,起作用只是hash值的几个低位。
    不同

HashMap的长度为什么时2的幂次方

因为HashMap是通过Key的hash值来确定存储的位置,但Hash值的范围是-2147483648到2147483647,不可能建立一个这么大的数组来覆盖所有的hash值。所以在计算完hash值后会对数组的长度进行取余操作,如果数组的长度是2的幂次方,(length -1)&hash 等同于hash%length,可以用(length-1)&hash这种位运算来代替%取余的操作进而提高性能。

HashMap的扩容操作怎么实现

  • 初始值为16,负载因子为0.75,阈值为负载因子 * 容量
  • resize()方法是在hashmap中键值对大于阈值时或者初始化时,就调用resize()方法进行扩容。
  • 每次扩容,容量都是之前的两倍
  • 扩容时有个判断e.hash & oldCap是否为零,也就是相当于hash值对数组长度的取余操作,若等于0,则位置不变,若等于1,位置变为原位置加旧容量。

HashMap怎么解决哈希冲突

哈希冲突:hashMap在存储元素时会先计算Key的hash值来确定存储位置,因为Key的hash值计算最后有个对数组长度取余的操作,所以即使不同的key也可能计算出相同的hash值,这样就引起了hash冲突。hashMap的底层结构中的链表/红黑树就是用来解决这个问题的。
拉链法:
HashMap中的数据结构为数组+链表/红黑树,当不同的key计算出的hash值相同时,就用链表的形式将Node节点(冲突的Key以及Key对象的value)挂在数组后面。
hash函数
key的hash值经过两次扰动,key的hashCode值与key的hashCode值的右移16位进行异或,然后对数组的长度取余,这样做可以让hashCode取值出的高位也参与运算,进一步降低hash冲突的概率,使得数据分布更平均。
红黑树
在拉链法中,如果hash冲突特别严重,则会导致数组上挂的链表长度过长,性能变差,因此在链表长度大于8时,将链表转化为红黑树,可以提高遍历链表的速度。

HashMap为什么不直接使用hashCode()处理后的哈希值直接作为table的下标

hashCode()处理后的哈希值范围太大,不可能在内存建立这么大的数组。

能否使用任何类作为Map的Key

可以,但是要注意两点:
1.如果类重写了equals()方法,也该重写hashCode()方法
2.最好定义Key类是不可变的,这样的Key对应的hashCode()值可以被缓存起来,性能好,这也是为什么String特别适合作为HashMap的Key

为什么HashMap中String、Integer这样的包装类适合作为Key

  • 这些包装类都是finad修饰,是不可变的,保证了key的不可变性,不会出现放入和获取时哈希值不同的情况。
  • 他们的内部已经重写了equale(),hashCode()等方法。

如果使用Object作为HashMap的Key,该怎么办

  • 重写hashCode()方法,因为需要计算hash值确定存储位置
  • 重写equals()方法,因为需要保证key的唯一性

ConcurrentHashMap底层具体实现

JDK1.7
在JDK1.7中,ConcurrentHashMap采用Segment数组 + HashEntry数组的方式进行实现。Segment实现了ReentrantLock,所以Segment有锁的性质,HashEntry用于存储键值对。一个ConcurrentHashMap包含着一个Segmet数组,一个Segment包含着一个HashEntry数组,HashEntry是一个链表结构,如果要获取HashEntry中的元素,要先获得Segment的锁。
JDK1.8
在JDK1.8中,不是Segment + HashEntry的结构了,而是HashMap类似的结构,Node数组+链表/红黑树,采用CAS+synchronize来保证线程安全,当链表长度大于8,链表转化为红黑树,在JDK1.8中synchronized只锁链表或者红黑树的头节点,是一种相对于segment更为细粒度的锁,锁的竞争变小,所以效率更高。
总结:

  • JDK1.7底层是ReentrantLock + Segment + HashEntry,JDK1.8底层是synchronized + CAS +链表/红黑树
  • JDK1.7采用的是分段锁,同时锁住几个HashEntry,JDK1.8锁的是Node头节点,只要没有发生哈希冲突,就不会产生锁的竞争。所以JDK1.8比JDK1.7提供了一种粒度更小的锁,减少了锁的竞争,提高了ConcurrentHashMap的并发能力。

HashTable的底层实现

HashTable的底层数据结构是数组+链表,链表主要是为了解决哈希冲突,并且整个数组都是synchronized修饰的,所以HashTable是线程安全的,但锁的粒度太大,锁的竞争非常激烈,效率很低。

HashMap、ConcurrentHashMap及Hashtable的区别

区别

Java集合常用方法

Collection常用方法

在这里插入图片描述

List特有方法

在这里插入图片描述

LinkedList特有方法

在这里插入图片描述

Map

在这里插入图片描述

Stack

在这里插入图片描述

Queue

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

As_theWind

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值